ltkx

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltkx.git
Log | Files | Refs | README | LICENSE

commit 1f9c27cd41ea59996f06c0ac265ef8f7ac6a7856
parent a8d438e533db40481c75ddd952a14f14b6f68c94
Author: lumidify <nobody@lumidify.org>
Date:   Fri, 20 Jul 2018 20:11:34 +0200

Add current WIP on complex text rendering; misc. changes

Note: the text rendering is in a very early stage - I didn't even bother to add delete functions for anything yet.
The current up-to-date file for the text rendering engine is text-hb.ubernew.c.

/me shall now go on hiatus for a while in order to learn a lot about Xlib and other things so he can come back and actually have a clue what he's doing.

Diffstat:
MLICENSE | 2+-
ALumidify_Casual.ttf | 0
MMakefile | 8++++----
MREADME.md | 14+++++++++-----
Mbutton.c | 280++++++++++++++++++++++++++++++++++++-------------------------------------------
Mbutton.h | 98++++++++++++++++++++++++++-----------------------------------------------------
DcJSON.c | 1492-------------------------------------------------------------------------------
DcJSON.h | 160-------------------------------------------------------------------------------
Mgrid.c | 521++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mgrid.h | 34+++++++++++++++++-----------------
Aini.c | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aini.h | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mltk.c | 650+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mltk.h | 303++++++++++++++++++++-----------------------------------------------------------
Mmain.c | 83+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Astb_truetype.h | 4021+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atemp | 1+
Mtest1.c | 63++++++++++++++++++++++++++++++++++++---------------------------
Atext.c | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext.c.bak | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/: | 249+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/Awami_beta3.ttf | 0
Atext/FONTLOG.txt | 284+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/GENTIUM-FAQ.txt | 205+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/GentiumPlus-I.ttf | 0
Atext/GentiumPlus-R.ttf | 0
Atext/LICENSE_OFL.txt | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/Makefile | 2++
Atext/NotoNastaliqUrdu-Regular.ttf | 0
Atext/OFL-FAQ.txt | 427+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/OFL.txt | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/README | 5+++++
Atext/README.txt | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/bob.c | 28++++++++++++++++++++++++++++
Atext/documentation/AwamiNastaliq-Features.odt | 0
Atext/documentation/AwamiNastaliq-Features.pdf | 0
Atext/documentation/AwamiNastaliq-TypeSample.odt | 0
Atext/documentation/AwamiNastaliq-TypeSample.pdf | 0
Atext/documentation/DOCUMENTATION.txt | 11+++++++++++
Atext/documentation/GentiumPlus-features.odt | 0
Atext/documentation/GentiumPlus-features.pdf | 0
Atext/font.ttf | 0
Atext/font1.ttf | 0
Atext/font2.ttf | 0
Atext/hashtest.c | 27+++++++++++++++++++++++++++
Atext/khash.h | 627+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/main.c | 200+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/main1.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/new.c | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/stb_image_write.h | 1048+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/stb_truetype.h | 4018+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/test data/RandomWords.odt | 0
Atext/test data/RandomWords.pdf | 0
Atext/test data/RandomWords.xml | 30++++++++++++++++++++++++++++++
Atext/test data/ftml_wf.xsl | 253+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/test data/language data/Kalami-gwc_UDHR.odt | 0
Atext/test data/language data/Kalami-gwc_UDHR.pdf | 0
Atext/test data/language data/Khowar-khw_UDHR.odt | 0
Atext/test data/language data/Khowar-khw_UDHR.pdf | 0
Atext/test data/language data/Palula-phl_UDHR.odt | 0
Atext/test data/language data/Palula-phl_UDHR.pdf | 0
Atext/test data/language data/Saraiki-skr_UDHR.odt | 0
Atext/test data/language data/Saraiki-skr_UDHR.pdf | 0
Atext/test data/language data/SaraikiWords_Awami.odt | 0
Atext/test data/language data/SaraikiWords_Awami.pdf | 0
Atext/test data/language data/Shina-scl_UDHR.odt | 0
Atext/test data/language data/Shina-scl_UDHR.pdf | 0
Atext/test data/language data/Urdu-urd_UDHR.odt | 0
Atext/test data/language data/Urdu-urd_UDHR.pdf | 0
Atext/test data/language data/UrduWords_Awami.odt | 0
Atext/test data/language data/UrduWords_Awami.pdf | 0
Atext/test data/letter combinations/ftml.xsl | 251+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/test data/letter combinations/test_allbasechars.pdf | 0
Atext/test data/letter combinations/test_allbasechars.xml | 2324+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/test data/letter combinations/test_basic_somediac.pdf | 0
Atext/test data/letter combinations/test_basic_somediac.xml | 6792+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/test data/letter combinations/test_basicforms.pdf | 0
Atext/test data/letter combinations/test_basicforms.xml | 2626+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/test.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/test23.c | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/test3.c | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/text-hb.c | 362+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/text-hb.new.c | 386+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/text-hb.ubernew.c | 452+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/text.c | 247+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/text1.c | 312+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/text2.c | 282+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/utf8.c | 730+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/utf8.h | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atext/utf8_new.c | 22++++++++++++++++++++++
Atheme.c | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atheme.h | 23+++++++++++++++++++++++
Mthemes/default.ini | 35+++++++++++++++++++----------------
94 files changed, 28797 insertions(+), 2776 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -1,7 +1,7 @@ MIT/X Consortium License The Lumidify ToolKit (LTK) -Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> +Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.org> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Lumidify_Casual.ttf b/Lumidify_Casual.ttf Binary files differ. diff --git a/Makefile b/Makefile @@ -1,7 +1,7 @@ -LIBS = -lX11 -lm -ldl -STD = -std=c89 -FLAGS = -g -w -Wall -Werror -Wextra -pedantic -CFILES = ltk.c cJSON.c grid.c button.c test1.c +LIBS = -lX11 -lm -L/usr/X11R6/lib +STD = -std=c99 +FLAGS = -g -w -fcommon -Wall -Werror -Wextra -I/usr/X11R6/include #-pedantic +CFILES = text.c ltk.c ini.c grid.c button.c test1.c all: test1.c gcc $(STD) $(FLAGS) $(LIBS) $(CFILES) -o test diff --git a/README.md b/README.md @@ -1,11 +1,15 @@ -# LTK - Lumidify ToolKit +#LTK - Lumidify ToolKit -This is work in progress. - -Please do not attempt to actually use any of the code. +This is work in progress. Please do not attempt to actually use any of the code. ## Licenses of Other Libraries Used [uthash](https://troydhanson.github.io/uthash/) by Troy D. Hanson: [BSD Revised](https://troydhanson.github.io/uthash/license.html) -[cJSON](https://github.com/DaveGamble/cJSON) by Dave Gamble: [MIT/X](https://github.com/DaveGamble/cJSON/blob/master/LICENSE) +[inih](https://github.com/benhoyt/inih) by Ben Hoyt: [New BSD](https://github.com/benhoyt/inih/blob/master/LICENSE.txt) + +[stb_truetype](https://github.com/nothings/stb/blob/master/stb_truetype.h) by Sean T. Barrett: [MIT/Public Domain](https://github.com/nothings/stb/blob/e6afb9cbae4064da8c3e69af3ff5c4629579c1d2/stb_truetype.h#L4815) + +[cutef8](https://github.com/JeffBezanson/cutef8/) by Jeff Bezanson: [Public Domain](https://github.com/JeffBezanson/cutef8/blob/ce8607864ef59ceef39fc20c9653265f6b91d4bc/utf8.c#L4) + +Note: LTK is in no way affiliated with any of the projects listed above. diff --git a/button.c b/button.c @@ -23,178 +23,154 @@ #include "ltk.h" -LtkButtonTheme *ltk_parse_button_theme(cJSON *button_json) +void ltk_button_ini_handler(LtkTheme *theme, const char *prop, const char *value) { - LtkButtonTheme *button_theme = malloc(sizeof(LtkButtonTheme)); - cJSON *normal_json = cJSON_GetObjectItem(button_json, "normal"); - if (!normal_json) - { - printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); - } - cJSON *border_width = cJSON_GetObjectItem(normal_json, "border-width"); - cJSON *font_size = cJSON_GetObjectItem(normal_json, "font-size"); - cJSON *border_color = cJSON_GetObjectItem(normal_json, "border-color"); - cJSON *fill_color = cJSON_GetObjectItem(normal_json, "fill-color"); - cJSON *padding_left = cJSON_GetObjectItem(normal_json, "padding-left"); - cJSON *padding_right = cJSON_GetObjectItem(normal_json, "padding-right"); - cJSON *padding_top = cJSON_GetObjectItem(normal_json, "padding-top"); - cJSON *padding_bottom = cJSON_GetObjectItem(normal_json, "padding-bottom"); - - button_theme->border_width = border_width != NULL ? border_width->valueint : 0; - button_theme->font_size = font_size != NULL ? font_size->valueint : 20; - button_theme->border_color = ltk_create_xcolor(border_color->valuestring); - button_theme->fill_color = ltk_create_xcolor(fill_color->valuestring); - button_theme->padding_left = padding_left != NULL ? padding_left->valueint : 0; - button_theme->padding_right = padding_right != NULL ? padding_right->valueint : 0; - button_theme->padding_top = padding_top != NULL ? padding_top->valueint : 0; - button_theme->padding_bottom = padding_bottom != NULL ? padding_bottom->valueint : 0; - - cJSON *hover_json = cJSON_GetObjectItem(button_json, "hover"); - if (!hover_json) - { - printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); - } - cJSON *border_width_hover = cJSON_GetObjectItem(hover_json, "border-width"); - cJSON *font_size_hover = cJSON_GetObjectItem(hover_json, "font-size"); - cJSON *border_color_hover = cJSON_GetObjectItem(hover_json, "border-color"); - cJSON *fill_color_hover = cJSON_GetObjectItem(hover_json, "fill-color"); - - button_theme->border_width_hover = border_width_hover != NULL ? border_width_hover->valueint : button_theme->border_width; - button_theme->font_size_hover = font_size_hover != NULL ? font_size_hover->valueint : button_theme->font_size; - button_theme->border_color_hover = border_color_hover != NULL ? ltk_create_xcolor(border_color_hover->valuestring) : button_theme->border_color; - button_theme->fill_color_hover = fill_color_hover != NULL ? ltk_create_xcolor(fill_color_hover->valuestring) : button_theme->fill_color; - - cJSON *pressed_json = cJSON_GetObjectItem(button_json, "pressed"); - if (!pressed_json) - { - printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); - } - cJSON *border_width_pressed = cJSON_GetObjectItem(pressed_json, "border-width"); - cJSON *font_size_pressed = cJSON_GetObjectItem(pressed_json, "font-size"); - cJSON *border_color_pressed = cJSON_GetObjectItem(pressed_json, "border-color"); - cJSON *fill_color_pressed = cJSON_GetObjectItem(pressed_json, "fill-color"); - - button_theme->border_width_pressed = border_width_pressed != NULL ? border_width_pressed->valueint : button_theme->border_width; - button_theme->font_size_pressed = font_size_hover != NULL ? font_size_pressed->valueint : button_theme->font_size; - button_theme->border_color_pressed = border_color_pressed != NULL ? ltk_create_xcolor(border_color_pressed->valuestring) : button_theme->border_color; - button_theme->fill_color_pressed = fill_color_pressed != NULL ? ltk_create_xcolor(fill_color_pressed->valuestring) : button_theme->fill_color; - - cJSON *active_json = cJSON_GetObjectItem(button_json, "active"); - if (!active_json) - { - printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); - } - cJSON *border_width_active = cJSON_GetObjectItem(active_json, "border-width"); - cJSON *font_size_active = cJSON_GetObjectItem(active_json, "font-size"); - cJSON *border_color_active = cJSON_GetObjectItem(active_json, "border-color"); - cJSON *fill_color_active = cJSON_GetObjectItem(active_json, "fill-color"); - - button_theme->border_width_active = border_width_active != NULL ? border_width_active->valueint : button_theme->border_width; - button_theme->font_size_active = font_size_active != NULL ? font_size_active->valueint : button_theme->font_size; - button_theme->border_color_active = border_color_active != NULL ? ltk_create_xcolor(border_color_active->valuestring) : button_theme->border_color; - button_theme->fill_color_active = fill_color_active != NULL ? ltk_create_xcolor(fill_color_active->valuestring) : button_theme->fill_color; - - cJSON *disabled_json = cJSON_GetObjectItem(button_json, "disabled"); - if (!disabled_json) - { - printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); - } - cJSON *border_width_disabled = cJSON_GetObjectItem(disabled_json, "border-width"); - cJSON *font_size_disabled = cJSON_GetObjectItem(disabled_json, "font-size"); - cJSON *border_color_disabled = cJSON_GetObjectItem(disabled_json, "border-color"); - cJSON *fill_color_disabled = cJSON_GetObjectItem(disabled_json, "fill-color"); - - button_theme->border_width_disabled = border_width_disabled != NULL ? border_width_disabled->valueint : button_theme->border_width; - button_theme->font_size_disabled = font_size_disabled != NULL ? font_size_disabled->valueint : button_theme->font_size; - button_theme->border_color_disabled = border_color_disabled != NULL ? ltk_create_xcolor(border_color_disabled->valuestring) : button_theme->border_color; - button_theme->fill_color_disabled = fill_color_disabled != NULL ? ltk_create_xcolor(fill_color_disabled->valuestring) : button_theme->fill_color; + if (strcmp(prop, "border_width") == 0) { + theme->button->border_width = atoi(value); + } else if (strcmp(prop, "font_size") == 0) { + theme->button->font_size = atoi(value); + } else if (strcmp(prop, "padl") == 0) { + theme->button->padl = atoi(value); + } else if (strcmp(prop, "padr") == 0) { + theme->button->padr = atoi(value); + } else if (strcmp(prop, "padt") == 0) { + theme->button->padt = atoi(value); + } else if (strcmp(prop, "padb") == 0) { + theme->button->padb = atoi(value); + } else if (strcmp(prop, "border") == 0) { + theme->button->border = ltk_create_xcolor(value); + } else if (strcmp(prop, "fill") == 0) { + theme->button->fill = ltk_create_xcolor(value); + } else if (strcmp(prop, "border_hover") == 0) { + theme->button->border_hover = ltk_create_xcolor(value); + } else if (strcmp(prop, "fill_hover") == 0) { + theme->button->fill_hover = ltk_create_xcolor(value); + } else if (strcmp(prop, "border_pressed") == 0) { + theme->button->border_pressed = ltk_create_xcolor(value); + } else if (strcmp(prop, "fill_pressed") == 0) { + theme->button->fill_pressed = ltk_create_xcolor(value); + } else if (strcmp(prop, "border_active") == 0) { + theme->button->border_active = ltk_create_xcolor(value); + } else if (strcmp(prop, "fill_active") == 0) { + theme->button->fill_active = ltk_create_xcolor(value); + } else if (strcmp(prop, "border_disabled") == 0) { + theme->button->border_disabled = ltk_create_xcolor(value); + } else if (strcmp(prop, "fill_disabled") == 0) { + theme->button->fill_disabled = ltk_create_xcolor(value); + } else if (strcmp(prop, "text_color") == 0) { + theme->button->text_color = ltk_create_xcolor(value); + } else { + printf("WARNING: Unknown property \"%s\" for button style.\n"); + } +} - return button_theme; +/* FIXME: this is probably really slow and there is probably a really simple alternative */ +unsigned long ltk_blend_pixel(XColor fg, XColor bg, double a) +{ + XColor blended; + if (a == 1.0) + return fg.pixel; + else if (a == 0.0) + return bg.pixel; + blended.red = (int)((fg.red - bg.red) * a + bg.red); + blended.green = (int)((fg.green - bg.green) * a + bg.green); + blended.blue = (int)((fg.blue - bg.blue) * a + bg.blue); + XAllocColor(ltk_global->display, ltk_global->colormap, &blended); + + return blended.pixel; } void ltk_draw_button(LtkButton *button) { - LtkButtonTheme *theme = ltk_global->theme->button; - LtkWindow *window = button->widget.window; - LtkRect rect = button->widget.rect; - XColor border_color; - XColor fill_color; - int border_width; - switch (button->widget.state) - { - case LTK_NORMAL: - border_color = theme->border_color; - fill_color = theme->fill_color; - border_width = theme->border_width; - break; - case LTK_HOVERACTIVE: - case LTK_HOVER: - border_color = theme->border_color_hover; - fill_color = theme->fill_color_hover; - border_width = theme->border_width_hover; - break; - case LTK_PRESSED: - border_color = theme->border_color_pressed; - fill_color = theme->fill_color_pressed; - border_width = theme->border_width_pressed; - break; - case LTK_ACTIVE: - border_color = theme->border_color_active; - fill_color = theme->fill_color_active; - border_width = theme->border_width_active; - break; - case LTK_DISABLED: - border_color = theme->border_color_disabled; - fill_color = theme->fill_color_disabled; - border_width = theme->border_width_disabled; - break; - default: - ltk_fatal("No style found for button!\n"); - } - XSetForeground(ltk_global->display, window->gc, fill_color.pixel); - XFillRectangle(ltk_global->display, window->xwindow, window->gc, rect.x, rect.y, rect.w, rect.h); - if (border_width < 1) return; - XSetForeground(ltk_global->display, window->gc, border_color.pixel); - XSetLineAttributes(ltk_global->display, window->gc, border_width, LineSolid, CapButt, JoinMiter); - XDrawRectangle(ltk_global->display, window->xwindow, window->gc, rect.x, rect.y, rect.w, rect.h); + LtkButtonTheme *theme = ltk_global->theme->button; + LtkWindow *window = button->widget.window; + LtkRect rect = button->widget.rect; + XColor border; + XColor fill; + switch (button->widget.state) { + case LTK_NORMAL: + border = theme->border; + fill = theme->fill; + break; + case LTK_HOVERACTIVE: + case LTK_HOVER: + border = theme->border_hover; + fill = theme->fill_hover; + break; + case LTK_PRESSED: + border = theme->border_pressed; + fill = theme->fill_pressed; + break; + case LTK_ACTIVE: + border = theme->border_active; + fill = theme->fill_active; + break; + case LTK_DISABLED: + border = theme->border_disabled; + fill = theme->fill_disabled; + break; + default: + ltk_fatal("No style found for button!\n"); + } + XSetForeground(ltk_global->display, window->gc, fill.pixel); + XFillRectangle(ltk_global->display, window->xwindow, window->gc, + rect.x, rect.y, rect.w, rect.h); + if (theme->border_width < 1) return; + XSetForeground(ltk_global->display, window->gc, border.pixel); + XSetLineAttributes(ltk_global->display, window->gc, theme->border_width, + LineSolid, CapButt, JoinMiter); + XDrawRectangle(ltk_global->display, window->xwindow, window->gc, + rect.x, rect.y, rect.w, rect.h); + int height = theme->font_size; + int width = button->text_width; + int i, j; + for (i = 0; i < height; i++) + { + for (j = 0; j < width; j++) + { +// XSetForeground(ltk_global->display, window->gc, (button->text_bitmap[i * width + j] / 255.0) * theme->text_color.pixel + ((255 - button->text_bitmap[i * width + j]) / 255.0) * fill.pixel); + XSetForeground(ltk_global->display, window->gc, ltk_blend_pixel(theme->text_color, fill, (button->text_bitmap[i * width + j] / 255.0))); + XDrawPoint(ltk_global->display, window->xwindow, window->gc, rect.x + j, rect.y + i); + } + } } -LtkButton *ltk_create_button(LtkWindow *window, const char *text, void (*callback)(void)) +LtkButton *ltk_create_button(LtkWindow *window, const char *text, + void (*callback) (void)) { - LtkButton *button = malloc(sizeof(LtkButton)); + LtkButton *button = malloc(sizeof(LtkButton)); - if (button == NULL) - { - printf("Button could not be created.\n"); - exit(1); - } + if (button == NULL) { + ltk_fatal("ERROR: Unable to allocate memory for LtkButton.\n"); + } - button->widget = ltk_create_widget(window, &ltk_draw_button, &ltk_destroy_button, 1); - button->widget.mouse_release = &ltk_button_mouse_release; + button->widget = ltk_create_widget(window, &ltk_draw_button, &ltk_destroy_button, 1); + button->widget.mouse_release = &ltk_button_mouse_release; - button->callback = callback; - button->text = strdup(text); + button->callback = callback; + LtkTheme *theme = ltk_global->theme; + button->text_width = ltk_text_width(text, theme->window->font, theme->button->font_size); + button->text_bitmap = ltk_render_text(text, theme->window->font, theme->button->font_size, button->text_width); - return button; + return button; } void ltk_destroy_button(void *widget) { - LtkButton *button = (LtkButton *)widget; - if (!button) - { - printf("Tried to destroy NULL button.\n"); - } - free(button->text); - free(button); + LtkButton *button = (LtkButton *) widget; + if (!button) { + printf("WARNING: Tried to destroy NULL button.\n"); + } + free(button->text_bitmap); + free(button); } +/* FIXME: is the fixme below supposed to be for the function above? */ /* FIXME: ungrid button if gridded */ void ltk_button_mouse_release(void *widget, XEvent event) { - LtkButton *button = widget; - if (button->widget.state == LTK_HOVERACTIVE && button->callback) - { - button->callback(); - } + LtkButton *button = widget; + if (button->widget.state == LTK_HOVERACTIVE && button->callback) { + button->callback(); + } } diff --git a/button.h b/button.h @@ -24,85 +24,51 @@ #ifndef _LTK_BUTTON_H_ #define _LTK_BUTTON_H_ -/* - * Struct to represent a button widget. - */ -typedef struct -{ - LtkWidget widget; - void (*callback)(void); - char *text; +typedef struct { + LtkWidget widget; + void (*callback) (void); + + int text_width; + char *text_raw; + unsigned char *text_bitmap; + Pixmap text; + Pixmap text_hover; + Pixmap text_pressed; + Pixmap text_active; + Pixmap text_disabled; } LtkButton; -/* - * Struct to contain all style information for buttons. - */ -typedef struct LtkButtonTheme -{ - int border_width; - int font_size; - XColor border_color; - XColor fill_color; - int padding_left; - int padding_right; - int padding_top; - int padding_bottom; +typedef struct LtkButtonTheme { + int border_width; + int font_size; + XColor text_color; + int padl; + int padr; + int padt; + int padb; - int border_width_hover; - int font_size_hover; - XColor border_color_hover; - XColor fill_color_hover; + XColor border; + XColor fill; - int border_width_pressed; - int font_size_pressed; - XColor border_color_pressed; - XColor fill_color_pressed; + XColor border_hover; + XColor fill_hover; - int border_width_active; - int font_size_active; - XColor border_color_active; - XColor fill_color_active; + XColor border_pressed; + XColor fill_pressed; - int border_width_disabled; - int font_size_disabled; - XColor border_color_disabled; - XColor fill_color_disabled; + XColor border_active; + XColor fill_active; + XColor border_disabled; + XColor fill_disabled; } LtkButtonTheme; -/* - * Extract style information for buttons. - * button_json: A cJSON struct containing the JSON for the button style. - * Returns: An LtkButtonTheme struct containing the style for a button. - */ -LtkButtonTheme *ltk_parse_button_theme(cJSON *button_json); - -/* - * Draw a button in its current state. - * button: Pointer to the button to draw. - */ -void ltk_draw_button(LtkButton *button); +void ltk_draw_button(LtkButton * button); -/* - * Create a button widget. - * window: The window the button will be shown on. - * text: The text to be shown on the button. - * callback: The function to be called when the button is clicked. - * Returns: A pointer to the newly created button. - */ -LtkButton *ltk_create_button(LtkWindow *window, const char *text, void (*callback)(void)); +LtkButton *ltk_create_button(LtkWindow * window, const char *text, void (*callback) (void)); -/* - * Destroy a button. - * widget: Pointer to the button. - */ void ltk_destroy_button(void *widget); -/* - * Default mouse button release handler for buttons. - * widget: Pointer to the button. - * event: The event to be handled. - */ void ltk_button_mouse_release(void *widget, XEvent event); #endif diff --git a/cJSON.c b/cJSON.c @@ -1,1492 +0,0 @@ -/* - Copyright (c) 2009 Dave Gamble - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -/* cJSON */ -/* JSON parser in C. */ - -#include <string.h> -#include <stdio.h> -#include <math.h> -#include <stdlib.h> -#include <float.h> -#include <limits.h> -#include <ctype.h> -#include "cJSON.h" - -static const char *global_ep; - -const char *cJSON_GetErrorPtr(void) -{ - return global_ep; -} - -static int cJSON_strcasecmp(const char *s1, const char *s2) -{ - if (!s1) - return (s1 == s2) ? 0 : 1; - if (!s2) - return 1; - for (; tolower(*s1) == tolower(*s2); ++s1, ++s2) - if (*s1 == 0) - return 0; - return tolower(*(const unsigned char *)s1) - - tolower(*(const unsigned char *)s2); -} - -static void *(*cJSON_malloc) (size_t sz) = malloc; -static void (*cJSON_free) (void *ptr) = free; - -static char *cJSON_strdup(const char *str) -{ - size_t len; - char *copy; - - len = strlen(str) + 1; - if (!(copy = (char *)cJSON_malloc(len))) - return 0; - memcpy(copy, str, len); - return copy; -} - -void cJSON_InitHooks(cJSON_Hooks * hooks) -{ - if (!hooks) { /* Reset hooks */ - cJSON_malloc = malloc; - cJSON_free = free; - return; - } - - cJSON_malloc = (hooks->malloc_fn) ? hooks->malloc_fn : malloc; - cJSON_free = (hooks->free_fn) ? hooks->free_fn : free; -} - -/* Internal constructor. */ -static cJSON *cJSON_New_Item(void) -{ - cJSON *node = (cJSON *) cJSON_malloc(sizeof(cJSON)); - if (node) - memset(node, 0, sizeof(cJSON)); - return node; -} - -/* Delete a cJSON structure. */ -void cJSON_Delete(cJSON * c) -{ - cJSON *next; - while (c) { - next = c->next; - if (!(c->type & cJSON_IsReference) && c->child) - cJSON_Delete(c->child); - if (!(c->type & cJSON_IsReference) && c->valuestring) - cJSON_free(c->valuestring); - if (!(c->type & cJSON_StringIsConst) && c->string) - cJSON_free(c->string); - cJSON_free(c); - c = next; - } -} - -/* Parse the input text to generate a number, and populate the result into item. */ -static const char *parse_number(cJSON * item, const char *num) -{ - double n = 0, sign = 1, scale = 0; - int subscale = 0, signsubscale = 1; - - if (*num == '-') - sign = -1, num++; /* Has sign? */ - if (*num == '0') - num++; /* is zero */ - if (*num >= '1' && *num <= '9') - do - n = (n * 10.0) + (*num++ - '0'); - while (*num >= '0' && *num <= '9'); /* Number? */ - if (*num == '.' && num[1] >= '0' && num[1] <= '9') { - num++; - do - n = (n * 10.0) + (*num++ - '0'), scale--; - while (*num >= '0' && *num <= '9'); - } /* Fractional part? */ - if (*num == 'e' || *num == 'E') { /* Exponent? */ - num++; - if (*num == '+') - num++; - else if (*num == '-') - signsubscale = -1, num++; /* With sign? */ - while (*num >= '0' && *num <= '9') - subscale = (subscale * 10) + (*num++ - '0'); /* Number? */ - } - - n = sign * n * pow(10.0, (scale + subscale * signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */ - - item->valuedouble = n; - item->valueint = (int)n; - item->type = cJSON_Number; - return num; -} - -static int pow2gt(int x) -{ - --x; - x |= x >> 1; - x |= x >> 2; - x |= x >> 4; - x |= x >> 8; - x |= x >> 16; - return x + 1; -} - -typedef struct { - char *buffer; - int length; - int offset; -} printbuffer; - -static char *ensure(printbuffer * p, int needed) -{ - char *newbuffer; - int newsize; - if (!p || !p->buffer) - return 0; - needed += p->offset; - if (needed <= p->length) - return p->buffer + p->offset; - - newsize = pow2gt(needed); - newbuffer = (char *)cJSON_malloc(newsize); - if (!newbuffer) { - cJSON_free(p->buffer); - p->length = 0, p->buffer = 0; - return 0; - } - if (newbuffer) - memcpy(newbuffer, p->buffer, p->length); - cJSON_free(p->buffer); - p->length = newsize; - p->buffer = newbuffer; - return newbuffer + p->offset; -} - -static int update(printbuffer * p) -{ - char *str; - if (!p || !p->buffer) - return 0; - str = p->buffer + p->offset; - return p->offset + strlen(str); -} - -/* Render the number nicely from the given item into a string. */ -static char *print_number(cJSON * item, printbuffer * p) -{ - char *str = 0; - double d = item->valuedouble; - if (d == 0) { - if (p) - str = ensure(p, 2); - else - str = (char *)cJSON_malloc(2); /* special case for 0. */ - if (str) - strcpy(str, "0"); - } else if (fabs(((double)item->valueint) - d) <= DBL_EPSILON - && d <= INT_MAX && d >= INT_MIN) { - if (p) - str = ensure(p, 21); - else - str = (char *)cJSON_malloc(21); /* 2^64+1 can be represented in 21 chars. */ - if (str) - sprintf(str, "%d", item->valueint); - } else { - if (p) - str = ensure(p, 64); - else - str = (char *)cJSON_malloc(64); /* This is a nice tradeoff. */ - if (str) { - if (d * 0 != 0) - sprintf(str, "null"); /* This checks for NaN and Infinity */ - else if (fabs(floor(d) - d) <= DBL_EPSILON - && fabs(d) < 1.0e60) - sprintf(str, "%.0f", d); - else if (fabs(d) < 1.0e-6 || fabs(d) > 1.0e9) - sprintf(str, "%e", d); - else - sprintf(str, "%f", d); - } - } - return str; -} - -static unsigned parse_hex4(const char *str) -{ - unsigned h = 0; - if (*str >= '0' && *str <= '9') - h += (*str) - '0'; - else if (*str >= 'A' && *str <= 'F') - h += 10 + (*str) - 'A'; - else if (*str >= 'a' && *str <= 'f') - h += 10 + (*str) - 'a'; - else - return 0; - h = h << 4; - str++; - if (*str >= '0' && *str <= '9') - h += (*str) - '0'; - else if (*str >= 'A' && *str <= 'F') - h += 10 + (*str) - 'A'; - else if (*str >= 'a' && *str <= 'f') - h += 10 + (*str) - 'a'; - else - return 0; - h = h << 4; - str++; - if (*str >= '0' && *str <= '9') - h += (*str) - '0'; - else if (*str >= 'A' && *str <= 'F') - h += 10 + (*str) - 'A'; - else if (*str >= 'a' && *str <= 'f') - h += 10 + (*str) - 'a'; - else - return 0; - h = h << 4; - str++; - if (*str >= '0' && *str <= '9') - h += (*str) - '0'; - else if (*str >= 'A' && *str <= 'F') - h += 10 + (*str) - 'A'; - else if (*str >= 'a' && *str <= 'f') - h += 10 + (*str) - 'a'; - else - return 0; - return h; -} - -/* Parse the input text into an unescaped cstring, and populate item. */ -static const unsigned char firstByteMark[7] = - { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; -static const char *parse_string(cJSON * item, const char *str, const char **ep) -{ - const char *ptr = str + 1, *end_ptr = str + 1; - char *ptr2; - char *out; - int len = 0; - unsigned uc, uc2; - if (*str != '\"') { - *ep = str; - return 0; - } - /* not a string! */ - while (*end_ptr != '\"' && *end_ptr && ++len) - if (*end_ptr++ == '\\') - end_ptr++; /* Skip escaped quotes. */ - - out = (char *)cJSON_malloc(len + 1); /* This is how long we need for the string, roughly. */ - if (!out) - return 0; - item->valuestring = out; /* assign here so out will be deleted during cJSON_Delete() later */ - item->type = cJSON_String; - - ptr = str + 1; - ptr2 = out; - while (ptr < end_ptr) { - if (*ptr != '\\') - *ptr2++ = *ptr++; - else { - ptr++; - switch (*ptr) { - case 'b': - *ptr2++ = '\b'; - break; - case 'f': - *ptr2++ = '\f'; - break; - case 'n': - *ptr2++ = '\n'; - break; - case 'r': - *ptr2++ = '\r'; - break; - case 't': - *ptr2++ = '\t'; - break; - case 'u': /* transcode utf16 to utf8. */ - uc = parse_hex4(ptr + 1); - ptr += 4; /* get the unicode char. */ - if (ptr >= end_ptr) { - *ep = str; - return 0; - } - /* invalid */ - if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) { - *ep = str; - return 0; - } - /* check for invalid. */ - if (uc >= 0xD800 && uc <= 0xDBFF) { /* UTF16 surrogate pairs. */ - if (ptr + 6 > end_ptr) { - *ep = str; - return 0; - } /* invalid */ - if (ptr[1] != '\\' || ptr[2] != 'u') { - *ep = str; - return 0; - } /* missing second-half of surrogate. */ - uc2 = parse_hex4(ptr + 3); - ptr += 6; - if (uc2 < 0xDC00 || uc2 > 0xDFFF) { - *ep = str; - return 0; - } /* invalid second-half of surrogate. */ - uc = 0x10000 + - (((uc & 0x3FF) << 10) | - (uc2 & 0x3FF)); - } - - len = 4; - if (uc < 0x80) - len = 1; - else if (uc < 0x800) - len = 2; - else if (uc < 0x10000) - len = 3; - ptr2 += len; - - switch (len) { - case 4: - *--ptr2 = ((uc | 0x80) & 0xBF); - uc >>= 6; - case 3: - *--ptr2 = ((uc | 0x80) & 0xBF); - uc >>= 6; - case 2: - *--ptr2 = ((uc | 0x80) & 0xBF); - uc >>= 6; - case 1: - *--ptr2 = (uc | firstByteMark[len]); - } - ptr2 += len; - break; - default: - *ptr2++ = *ptr; - break; - } - ptr++; - } - } - *ptr2 = 0; - if (*ptr == '\"') - ptr++; - return ptr; -} - -/* Render the cstring provided to an escaped version that can be printed. */ -static char *print_string_ptr(const char *str, printbuffer * p) -{ - const char *ptr; - char *ptr2, *out; - int len = 0, flag = 0; - unsigned char token; - - if (!str) { - if (p) - out = ensure(p, 3); - else - out = (char *)cJSON_malloc(3); - if (!out) - return 0; - strcpy(out, "\"\""); - return out; - } - - for (ptr = str; *ptr; ptr++) - flag |= ((*ptr > 0 && *ptr < 32) || (*ptr == '\"') - || (*ptr == '\\')) ? 1 : 0; - if (!flag) { - len = ptr - str; - if (p) - out = ensure(p, len + 3); - else - out = (char *)cJSON_malloc(len + 3); - if (!out) - return 0; - ptr2 = out; - *ptr2++ = '\"'; - strcpy(ptr2, str); - ptr2[len] = '\"'; - ptr2[len + 1] = 0; - return out; - } - - ptr = str; - while ((token = *ptr) && ++len) { - if (strchr("\"\\\b\f\n\r\t", token)) - len++; - else if (token < 32) - len += 5; - ptr++; - } - - if (p) - out = ensure(p, len + 3); - else - out = (char *)cJSON_malloc(len + 3); - if (!out) - return 0; - - ptr2 = out; - ptr = str; - *ptr2++ = '\"'; - while (*ptr) { - if ((unsigned char)*ptr > 31 && *ptr != '\"' && *ptr != '\\') - *ptr2++ = *ptr++; - else { - *ptr2++ = '\\'; - switch (token = *ptr++) { - case '\\': - *ptr2++ = '\\'; - break; - case '\"': - *ptr2++ = '\"'; - break; - case '\b': - *ptr2++ = 'b'; - break; - case '\f': - *ptr2++ = 'f'; - break; - case '\n': - *ptr2++ = 'n'; - break; - case '\r': - *ptr2++ = 'r'; - break; - case '\t': - *ptr2++ = 't'; - break; - default: - sprintf(ptr2, "u%04x", token); - ptr2 += 5; - break; /* escape and print */ - } - } - } - *ptr2++ = '\"'; - *ptr2++ = 0; - return out; -} - -/* Invote print_string_ptr (which is useful) on an item. */ -static char *print_string(cJSON * item, printbuffer * p) -{ - return print_string_ptr(item->valuestring, p); -} - -/* Predeclare these prototypes. */ -static const char *parse_value(cJSON * item, const char *value, - const char **ep); -static char *print_value(cJSON * item, int depth, int fmt, printbuffer * p); -static const char *parse_array(cJSON * item, const char *value, - const char **ep); -static char *print_array(cJSON * item, int depth, int fmt, printbuffer * p); -static const char *parse_object(cJSON * item, const char *value, - const char **ep); -static char *print_object(cJSON * item, int depth, int fmt, printbuffer * p); - -/* Utility to jump whitespace and cr/lf */ -static const char *skip(const char *in) -{ - while (in && *in && (unsigned char)*in <= 32) - in++; - return in; -} - -/* Parse an object - create a new root, and populate. */ -cJSON *cJSON_ParseWithOpts(const char *value, const char **return_parse_end, - int require_null_terminated) -{ - const char *end = 0, **ep = - return_parse_end ? return_parse_end : &global_ep; - cJSON *c = cJSON_New_Item(); - *ep = 0; - if (!c) - return 0; /* memory fail */ - - end = parse_value(c, skip(value), ep); - if (!end) { - cJSON_Delete(c); - return 0; - } - - /* parse failure. ep is set. */ - /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ - if (require_null_terminated) { - end = skip(end); - if (*end) { - cJSON_Delete(c); - *ep = end; - return 0; - } - } - if (return_parse_end) - *return_parse_end = end; - return c; -} - -/* Default options for cJSON_Parse */ -cJSON *cJSON_Parse(const char *value) -{ - return cJSON_ParseWithOpts(value, 0, 0); -} - -/* Render a cJSON item/entity/structure to text. */ -char *cJSON_Print(cJSON * item) -{ - return print_value(item, 0, 1, 0); -} - -char *cJSON_PrintUnformatted(cJSON * item) -{ - return print_value(item, 0, 0, 0); -} - -char *cJSON_PrintBuffered(cJSON * item, int prebuffer, int fmt) -{ - printbuffer p; - p.buffer = (char *)cJSON_malloc(prebuffer); - p.length = prebuffer; - p.offset = 0; - return print_value(item, 0, fmt, &p); -} - -/* Parser core - when encountering text, process appropriately. */ -static const char *parse_value(cJSON * item, const char *value, const char **ep) -{ - if (!value) - return 0; /* Fail on null. */ - if (!strncmp(value, "null", 4)) { - item->type = cJSON_NULL; - return value + 4; - } - if (!strncmp(value, "false", 5)) { - item->type = cJSON_False; - return value + 5; - } - if (!strncmp(value, "true", 4)) { - item->type = cJSON_True; - item->valueint = 1; - return value + 4; - } - if (*value == '\"') { - return parse_string(item, value, ep); - } - if (*value == '-' || (*value >= '0' && *value <= '9')) { - return parse_number(item, value); - } - if (*value == '[') { - return parse_array(item, value, ep); - } - if (*value == '{') { - return parse_object(item, value, ep); - } - - *ep = value; - return 0; /* failure. */ -} - -/* Render a value to text. */ -static char *print_value(cJSON * item, int depth, int fmt, printbuffer * p) -{ - char *out = 0; - if (!item) - return 0; - if (p) { - switch ((item->type) & 255) { - case cJSON_NULL: - { - out = ensure(p, 5); - if (out) - strcpy(out, "null"); - break; - } - case cJSON_False: - { - out = ensure(p, 6); - if (out) - strcpy(out, "false"); - break; - } - case cJSON_True: - { - out = ensure(p, 5); - if (out) - strcpy(out, "true"); - break; - } - case cJSON_Number: - out = print_number(item, p); - break; - case cJSON_String: - out = print_string(item, p); - break; - case cJSON_Array: - out = print_array(item, depth, fmt, p); - break; - case cJSON_Object: - out = print_object(item, depth, fmt, p); - break; - } - } else { - switch ((item->type) & 255) { - case cJSON_NULL: - out = cJSON_strdup("null"); - break; - case cJSON_False: - out = cJSON_strdup("false"); - break; - case cJSON_True: - out = cJSON_strdup("true"); - break; - case cJSON_Number: - out = print_number(item, 0); - break; - case cJSON_String: - out = print_string(item, 0); - break; - case cJSON_Array: - out = print_array(item, depth, fmt, 0); - break; - case cJSON_Object: - out = print_object(item, depth, fmt, 0); - break; - } - } - return out; -} - -/* Build an array from input text. */ -static const char *parse_array(cJSON * item, const char *value, const char **ep) -{ - cJSON *child; - if (*value != '[') { - *ep = value; - return 0; - } - /* not an array! */ - item->type = cJSON_Array; - value = skip(value + 1); - if (*value == ']') - return value + 1; /* empty array. */ - - item->child = child = cJSON_New_Item(); - if (!item->child) - return 0; /* memory fail */ - value = skip(parse_value(child, skip(value), ep)); /* skip any spacing, get the value. */ - if (!value) - return 0; - - while (*value == ',') { - cJSON *new_item; - if (!(new_item = cJSON_New_Item())) - return 0; /* memory fail */ - child->next = new_item; - new_item->prev = child; - child = new_item; - value = skip(parse_value(child, skip(value + 1), ep)); - if (!value) - return 0; /* memory fail */ - } - - if (*value == ']') - return value + 1; /* end of array */ - *ep = value; - return 0; /* malformed. */ -} - -/* Render an array to text */ -static char *print_array(cJSON * item, int depth, int fmt, printbuffer * p) -{ - char **entries; - char *out = 0, *ptr, *ret; - int len = 5; - cJSON *child = item->child; - int numentries = 0, i = 0, fail = 0; - size_t tmplen = 0; - - /* How many entries in the array? */ - while (child) - numentries++, child = child->next; - /* Explicitly handle numentries==0 */ - if (!numentries) { - if (p) - out = ensure(p, 3); - else - out = (char *)cJSON_malloc(3); - if (out) - strcpy(out, "[]"); - return out; - } - - if (p) { - /* Compose the output array. */ - i = p->offset; - ptr = ensure(p, 1); - if (!ptr) - return 0; - *ptr = '['; - p->offset++; - child = item->child; - while (child && !fail) { - print_value(child, depth + 1, fmt, p); - p->offset = update(p); - if (child->next) { - len = fmt ? 2 : 1; - ptr = ensure(p, len + 1); - if (!ptr) - return 0; - *ptr++ = ','; - if (fmt) - *ptr++ = ' '; - *ptr = 0; - p->offset += len; - } - child = child->next; - } - ptr = ensure(p, 2); - if (!ptr) - return 0; - *ptr++ = ']'; - *ptr = 0; - out = (p->buffer) + i; - } else { - /* Allocate an array to hold the values for each */ - entries = (char **)cJSON_malloc(numentries * sizeof(char *)); - if (!entries) - return 0; - memset(entries, 0, numentries * sizeof(char *)); - /* Retrieve all the results: */ - child = item->child; - while (child && !fail) { - ret = print_value(child, depth + 1, fmt, 0); - entries[i++] = ret; - if (ret) - len += strlen(ret) + 2 + (fmt ? 1 : 0); - else - fail = 1; - child = child->next; - } - - /* If we didn't fail, try to malloc the output string */ - if (!fail) - out = (char *)cJSON_malloc(len); - /* If that fails, we fail. */ - if (!out) - fail = 1; - - /* Handle failure. */ - if (fail) { - for (i = 0; i < numentries; i++) - if (entries[i]) - cJSON_free(entries[i]); - cJSON_free(entries); - return 0; - } - - /* Compose the output array. */ - *out = '['; - ptr = out + 1; - *ptr = 0; - for (i = 0; i < numentries; i++) { - tmplen = strlen(entries[i]); - memcpy(ptr, entries[i], tmplen); - ptr += tmplen; - if (i != numentries - 1) { - *ptr++ = ','; - if (fmt) - *ptr++ = ' '; - *ptr = 0; - } - cJSON_free(entries[i]); - } - cJSON_free(entries); - *ptr++ = ']'; - *ptr++ = 0; - } - return out; -} - -/* Build an object from the text. */ -static const char *parse_object(cJSON * item, const char *value, - const char **ep) -{ - cJSON *child; - if (*value != '{') { - *ep = value; - return 0; - } - /* not an object! */ - item->type = cJSON_Object; - value = skip(value + 1); - if (*value == '}') - return value + 1; /* empty array. */ - - item->child = child = cJSON_New_Item(); - if (!item->child) - return 0; - value = skip(parse_string(child, skip(value), ep)); - if (!value) - return 0; - child->string = child->valuestring; - child->valuestring = 0; - if (*value != ':') { - *ep = value; - return 0; - } /* fail! */ - value = skip(parse_value(child, skip(value + 1), ep)); /* skip any spacing, get the value. */ - if (!value) - return 0; - - while (*value == ',') { - cJSON *new_item; - if (!(new_item = cJSON_New_Item())) - return 0; /* memory fail */ - child->next = new_item; - new_item->prev = child; - child = new_item; - value = skip(parse_string(child, skip(value + 1), ep)); - if (!value) - return 0; - child->string = child->valuestring; - child->valuestring = 0; - if (*value != ':') { - *ep = value; - return 0; - } /* fail! */ - value = skip(parse_value(child, skip(value + 1), ep)); /* skip any spacing, get the value. */ - if (!value) - return 0; - } - - if (*value == '}') - return value + 1; /* end of array */ - *ep = value; - return 0; /* malformed. */ -} - -/* Render an object to text. */ -static char *print_object(cJSON * item, int depth, int fmt, printbuffer * p) -{ - char **entries = 0, **names = 0; - char *out = 0, *ptr, *ret, *str; - int len = 7, i = 0, j; - cJSON *child = item->child; - int numentries = 0, fail = 0; - size_t tmplen = 0; - /* Count the number of entries. */ - while (child) - numentries++, child = child->next; - /* Explicitly handle empty object case */ - if (!numentries) { - if (p) - out = ensure(p, fmt ? depth + 4 : 3); - else - out = (char *)cJSON_malloc(fmt ? depth + 4 : 3); - if (!out) - return 0; - ptr = out; - *ptr++ = '{'; - if (fmt) { - *ptr++ = '\n'; - for (i = 0; i < depth; i++) - *ptr++ = '\t'; - } - *ptr++ = '}'; - *ptr++ = 0; - return out; - } - if (p) { - /* Compose the output: */ - i = p->offset; - len = fmt ? 2 : 1; - ptr = ensure(p, len + 1); - if (!ptr) - return 0; - *ptr++ = '{'; - if (fmt) - *ptr++ = '\n'; - *ptr = 0; - p->offset += len; - child = item->child; - depth++; - while (child) { - if (fmt) { - ptr = ensure(p, depth); - if (!ptr) - return 0; - for (j = 0; j < depth; j++) - *ptr++ = '\t'; - p->offset += depth; - } - print_string_ptr(child->string, p); - p->offset = update(p); - - len = fmt ? 2 : 1; - ptr = ensure(p, len); - if (!ptr) - return 0; - *ptr++ = ':'; - if (fmt) - *ptr++ = '\t'; - p->offset += len; - - print_value(child, depth, fmt, p); - p->offset = update(p); - - len = (fmt ? 1 : 0) + (child->next ? 1 : 0); - ptr = ensure(p, len + 1); - if (!ptr) - return 0; - if (child->next) - *ptr++ = ','; - if (fmt) - *ptr++ = '\n'; - *ptr = 0; - p->offset += len; - child = child->next; - } - ptr = ensure(p, fmt ? (depth + 1) : 2); - if (!ptr) - return 0; - if (fmt) - for (i = 0; i < depth - 1; i++) - *ptr++ = '\t'; - *ptr++ = '}'; - *ptr = 0; - out = (p->buffer) + i; - } else { - /* Allocate space for the names and the objects */ - entries = (char **)cJSON_malloc(numentries * sizeof(char *)); - if (!entries) - return 0; - names = (char **)cJSON_malloc(numentries * sizeof(char *)); - if (!names) { - cJSON_free(entries); - return 0; - } - memset(entries, 0, sizeof(char *) * numentries); - memset(names, 0, sizeof(char *) * numentries); - - /* Collect all the results into our arrays: */ - child = item->child; - depth++; - if (fmt) - len += depth; - while (child && !fail) { - names[i] = str = print_string_ptr(child->string, 0); - entries[i++] = ret = print_value(child, depth, fmt, 0); - if (str && ret) - len += - strlen(ret) + strlen(str) + 2 + (fmt ? 2 + - depth : 0); - else - fail = 1; - child = child->next; - } - - /* Try to allocate the output string */ - if (!fail) - out = (char *)cJSON_malloc(len); - if (!out) - fail = 1; - - /* Handle failure */ - if (fail) { - for (i = 0; i < numentries; i++) { - if (names[i]) - cJSON_free(names[i]); - if (entries[i]) - cJSON_free(entries[i]); - } - cJSON_free(names); - cJSON_free(entries); - return 0; - } - - /* Compose the output: */ - *out = '{'; - ptr = out + 1; - if (fmt) - *ptr++ = '\n'; - *ptr = 0; - for (i = 0; i < numentries; i++) { - if (fmt) - for (j = 0; j < depth; j++) - *ptr++ = '\t'; - tmplen = strlen(names[i]); - memcpy(ptr, names[i], tmplen); - ptr += tmplen; - *ptr++ = ':'; - if (fmt) - *ptr++ = '\t'; - strcpy(ptr, entries[i]); - ptr += strlen(entries[i]); - if (i != numentries - 1) - *ptr++ = ','; - if (fmt) - *ptr++ = '\n'; - *ptr = 0; - cJSON_free(names[i]); - cJSON_free(entries[i]); - } - - cJSON_free(names); - cJSON_free(entries); - if (fmt) - for (i = 0; i < depth - 1; i++) - *ptr++ = '\t'; - *ptr++ = '}'; - *ptr++ = 0; - } - return out; -} - -/* Get Array size/item / object item. */ -int cJSON_GetArraySize(cJSON * array) -{ - cJSON *c = array->child; - int i = 0; - while (c) - i++, c = c->next; - return i; -} - -cJSON *cJSON_GetArrayItem(cJSON * array, int item) -{ - cJSON *c = array ? array->child : 0; - while (c && item > 0) - item--, c = c->next; - return c; -} - -cJSON *cJSON_GetObjectItem(cJSON * object, const char *string) -{ - cJSON *c = object ? object->child : 0; - while (c && cJSON_strcasecmp(c->string, string)) - c = c->next; - return c; -} - -int cJSON_HasObjectItem(cJSON * object, const char *string) -{ - return cJSON_GetObjectItem(object, string) ? 1 : 0; -} - -/* Utility for array list handling. */ -static void suffix_object(cJSON * prev, cJSON * item) -{ - prev->next = item; - item->prev = prev; -} - -/* Utility for handling references. */ -static cJSON *create_reference(cJSON * item) -{ - cJSON *ref = cJSON_New_Item(); - if (!ref) - return 0; - memcpy(ref, item, sizeof(cJSON)); - ref->string = 0; - ref->type |= cJSON_IsReference; - ref->next = ref->prev = 0; - return ref; -} - -/* Add item to array/object. */ -void cJSON_AddItemToArray(cJSON * array, cJSON * item) -{ - cJSON *c = array->child; - if (!item) - return; - if (!c) { - array->child = item; - } else { - while (c && c->next) - c = c->next; - suffix_object(c, item); - } -} - -void cJSON_AddItemToObject(cJSON * object, const char *string, cJSON * item) -{ - if (!item) - return; - if (item->string) - cJSON_free(item->string); - item->string = cJSON_strdup(string); - cJSON_AddItemToArray(object, item); -} - -void cJSON_AddItemToObjectCS(cJSON * object, const char *string, cJSON * item) -{ - if (!item) - return; - if (!(item->type & cJSON_StringIsConst) && item->string) - cJSON_free(item->string); - item->string = (char *)string; - item->type |= cJSON_StringIsConst; - cJSON_AddItemToArray(object, item); -} - -void cJSON_AddItemReferenceToArray(cJSON * array, cJSON * item) -{ - cJSON_AddItemToArray(array, create_reference(item)); -} - -void -cJSON_AddItemReferenceToObject(cJSON * object, const char *string, cJSON * item) -{ - cJSON_AddItemToObject(object, string, create_reference(item)); -} - -cJSON *cJSON_DetachItemFromArray(cJSON * array, int which) -{ - cJSON *c = array->child; - while (c && which > 0) - c = c->next, which--; - if (!c) - return 0; - if (c->prev) - c->prev->next = c->next; - if (c->next) - c->next->prev = c->prev; - if (c == array->child) - array->child = c->next; - c->prev = c->next = 0; - return c; -} - -void cJSON_DeleteItemFromArray(cJSON * array, int which) -{ - cJSON_Delete(cJSON_DetachItemFromArray(array, which)); -} - -cJSON *cJSON_DetachItemFromObject(cJSON * object, const char *string) -{ - int i = 0; - cJSON *c = object->child; - while (c && cJSON_strcasecmp(c->string, string)) - i++, c = c->next; - if (c) - return cJSON_DetachItemFromArray(object, i); - return 0; -} - -void cJSON_DeleteItemFromObject(cJSON * object, const char *string) -{ - cJSON_Delete(cJSON_DetachItemFromObject(object, string)); -} - -/* Replace array/object items with new ones. */ -void cJSON_InsertItemInArray(cJSON * array, int which, cJSON * newitem) -{ - cJSON *c = array->child; - while (c && which > 0) - c = c->next, which--; - if (!c) { - cJSON_AddItemToArray(array, newitem); - return; - } - newitem->next = c; - newitem->prev = c->prev; - c->prev = newitem; - if (c == array->child) - array->child = newitem; - else - newitem->prev->next = newitem; -} - -void cJSON_ReplaceItemInArray(cJSON * array, int which, cJSON * newitem) -{ - cJSON *c = array->child; - while (c && which > 0) - c = c->next, which--; - if (!c) - return; - newitem->next = c->next; - newitem->prev = c->prev; - if (newitem->next) - newitem->next->prev = newitem; - if (c == array->child) - array->child = newitem; - else - newitem->prev->next = newitem; - c->next = c->prev = 0; - cJSON_Delete(c); -} - -void -cJSON_ReplaceItemInObject(cJSON * object, const char *string, cJSON * newitem) -{ - int i = 0; - cJSON *c = object->child; - while (c && cJSON_strcasecmp(c->string, string)) - i++, c = c->next; - if (c) { - newitem->string = cJSON_strdup(string); - cJSON_ReplaceItemInArray(object, i, newitem); - } -} - -/* Create basic types: */ -cJSON *cJSON_CreateNull(void) -{ - cJSON *item = cJSON_New_Item(); - if (item) - item->type = cJSON_NULL; - return item; -} - -cJSON *cJSON_CreateTrue(void) -{ - cJSON *item = cJSON_New_Item(); - if (item) - item->type = cJSON_True; - return item; -} - -cJSON *cJSON_CreateFalse(void) -{ - cJSON *item = cJSON_New_Item(); - if (item) - item->type = cJSON_False; - return item; -} - -cJSON *cJSON_CreateBool(int b) -{ - cJSON *item = cJSON_New_Item(); - if (item) - item->type = b ? cJSON_True : cJSON_False; - return item; -} - -cJSON *cJSON_CreateNumber(double num) -{ - cJSON *item = cJSON_New_Item(); - if (item) { - item->type = cJSON_Number; - item->valuedouble = num; - item->valueint = (int)num; - } - return item; -} - -cJSON *cJSON_CreateString(const char *string) -{ - cJSON *item = cJSON_New_Item(); - if (item) { - item->type = cJSON_String; - item->valuestring = cJSON_strdup(string); - if (!item->valuestring) { - cJSON_Delete(item); - return 0; - } - } - return item; -} - -cJSON *cJSON_CreateArray(void) -{ - cJSON *item = cJSON_New_Item(); - if (item) - item->type = cJSON_Array; - return item; -} - -cJSON *cJSON_CreateObject(void) -{ - cJSON *item = cJSON_New_Item(); - if (item) - item->type = cJSON_Object; - return item; -} - -/* Create Arrays: */ -cJSON *cJSON_CreateIntArray(const int *numbers, int count) -{ - int i; - cJSON *n = 0, *p = 0, *a = cJSON_CreateArray(); - for (i = 0; a && i < count; i++) { - n = cJSON_CreateNumber(numbers[i]); - if (!n) { - cJSON_Delete(a); - return 0; - } - if (!i) - a->child = n; - else - suffix_object(p, n); - p = n; - } - return a; -} - -cJSON *cJSON_CreateFloatArray(const float *numbers, int count) -{ - int i; - cJSON *n = 0, *p = 0, *a = cJSON_CreateArray(); - for (i = 0; a && i < count; i++) { - n = cJSON_CreateNumber(numbers[i]); - if (!n) { - cJSON_Delete(a); - return 0; - } - if (!i) - a->child = n; - else - suffix_object(p, n); - p = n; - } - return a; -} - -cJSON *cJSON_CreateDoubleArray(const double *numbers, int count) -{ - int i; - cJSON *n = 0, *p = 0, *a = cJSON_CreateArray(); - for (i = 0; a && i < count; i++) { - n = cJSON_CreateNumber(numbers[i]); - if (!n) { - cJSON_Delete(a); - return 0; - } - if (!i) - a->child = n; - else - suffix_object(p, n); - p = n; - } - return a; -} - -cJSON *cJSON_CreateStringArray(const char **strings, int count) -{ - int i; - cJSON *n = 0, *p = 0, *a = cJSON_CreateArray(); - for (i = 0; a && i < count; i++) { - n = cJSON_CreateString(strings[i]); - if (!n) { - cJSON_Delete(a); - return 0; - } - if (!i) - a->child = n; - else - suffix_object(p, n); - p = n; - } - return a; -} - -/* Duplication */ -cJSON *cJSON_Duplicate(cJSON * item, int recurse) -{ - cJSON *newitem, *cptr, *nptr = 0, *newchild; - /* Bail on bad ptr */ - if (!item) - return 0; - /* Create new item */ - newitem = cJSON_New_Item(); - if (!newitem) - return 0; - /* Copy over all vars */ - newitem->type = item->type & (~cJSON_IsReference), newitem->valueint = - item->valueint, newitem->valuedouble = item->valuedouble; - if (item->valuestring) { - newitem->valuestring = cJSON_strdup(item->valuestring); - if (!newitem->valuestring) { - cJSON_Delete(newitem); - return 0; - } - } - if (item->string) { - newitem->string = cJSON_strdup(item->string); - if (!newitem->string) { - cJSON_Delete(newitem); - return 0; - } - } - /* If non-recursive, then we're done! */ - if (!recurse) - return newitem; - /* Walk the ->next chain for the child. */ - cptr = item->child; - while (cptr) { - newchild = cJSON_Duplicate(cptr, 1); /* Duplicate (with recurse) each item in the ->next chain */ - if (!newchild) { - cJSON_Delete(newitem); - return 0; - } - if (nptr) { - nptr->next = newchild, newchild->prev = nptr; - nptr = newchild; - } /* If newitem->child already set, then crosswire ->prev and ->next and move on */ - else { - newitem->child = newchild; - nptr = newchild; - } /* Set newitem->child and move to it */ - cptr = cptr->next; - } - return newitem; -} - -void cJSON_Minify(char *json) -{ - char *into = json; - while (*json) { - if (*json == ' ') - json++; - else if (*json == '\t') - json++; /* Whitespace characters. */ - else if (*json == '\r') - json++; - else if (*json == '\n') - json++; - else if (*json == '/' && json[1] == '/') - while (*json && *json != '\n') - json++; /* double-slash comments, to end of line. */ - else if (*json == '/' && json[1] == '*') { - while (*json && !(*json == '*' && json[1] == '/')) - json++; - json += 2; - } /* multiline comments. */ - else if (*json == '\"') { - *into++ = *json++; - while (*json && *json != '\"') { - if (*json == '\\') - *into++ = *json++; - *into++ = *json++; - } - *into++ = *json++; - } /* string literals, which are \" sensitive. */ - else - *into++ = *json++; /* All other characters. */ - } - *into = 0; /* and null-terminate. */ -} diff --git a/cJSON.h b/cJSON.h @@ -1,160 +0,0 @@ -/* - Copyright (c) 2009 Dave Gamble - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -#ifndef cJSON__h -#define cJSON__h - -#ifdef __cplusplus -extern "C" { -#endif - -/* cJSON Types: */ -#define cJSON_False (1 << 0) -#define cJSON_True (1 << 1) -#define cJSON_NULL (1 << 2) -#define cJSON_Number (1 << 3) -#define cJSON_String (1 << 4) -#define cJSON_Array (1 << 5) -#define cJSON_Object (1 << 6) - -#define cJSON_IsReference 256 -#define cJSON_StringIsConst 512 - -/* The cJSON structure: */ - typedef struct cJSON { - struct cJSON *next, *prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ - struct cJSON *child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ - - int type; /* The type of the item, as above. */ - - char *valuestring; /* The item's string, if type==cJSON_String */ - int valueint; /* The item's number, if type==cJSON_Number */ - double valuedouble; /* The item's number, if type==cJSON_Number */ - - char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ - } cJSON; - - typedef struct cJSON_Hooks { - void *(*malloc_fn) (size_t sz); - void (*free_fn) (void *ptr); - } cJSON_Hooks; - -/* Supply malloc, realloc and free functions to cJSON */ - extern void cJSON_InitHooks(cJSON_Hooks * hooks); - -/* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */ - extern cJSON *cJSON_Parse(const char *value); -/* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */ - extern char *cJSON_Print(cJSON * item); -/* Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. */ - extern char *cJSON_PrintUnformatted(cJSON * item); -/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ - extern char *cJSON_PrintBuffered(cJSON * item, int prebuffer, int fmt); -/* Delete a cJSON entity and all subentities. */ - extern void cJSON_Delete(cJSON * c); - -/* Returns the number of items in an array (or object). */ - extern int cJSON_GetArraySize(cJSON * array); -/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ - extern cJSON *cJSON_GetArrayItem(cJSON * array, int item); -/* Get item "string" from object. Case insensitive. */ - extern cJSON *cJSON_GetObjectItem(cJSON * object, const char *string); - extern int cJSON_HasObjectItem(cJSON * object, const char *string); -/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ - extern const char *cJSON_GetErrorPtr(void); - -/* These calls create a cJSON item of the appropriate type. */ - extern cJSON *cJSON_CreateNull(void); - extern cJSON *cJSON_CreateTrue(void); - extern cJSON *cJSON_CreateFalse(void); - extern cJSON *cJSON_CreateBool(int b); - extern cJSON *cJSON_CreateNumber(double num); - extern cJSON *cJSON_CreateString(const char *string); - extern cJSON *cJSON_CreateArray(void); - extern cJSON *cJSON_CreateObject(void); - -/* These utilities create an Array of count items. */ - extern cJSON *cJSON_CreateIntArray(const int *numbers, int count); - extern cJSON *cJSON_CreateFloatArray(const float *numbers, int count); - extern cJSON *cJSON_CreateDoubleArray(const double *numbers, int count); - extern cJSON *cJSON_CreateStringArray(const char **strings, int count); - -/* Append item to the specified array/object. */ - extern void cJSON_AddItemToArray(cJSON * array, cJSON * item); - extern void cJSON_AddItemToObject(cJSON * object, const char *string, - cJSON * item); - extern void cJSON_AddItemToObjectCS(cJSON * object, const char *string, cJSON * item); /* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object */ -/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ - extern void cJSON_AddItemReferenceToArray(cJSON * array, cJSON * item); - extern void cJSON_AddItemReferenceToObject(cJSON * object, - const char *string, - cJSON * item); - -/* Remove/Detatch items from Arrays/Objects. */ - extern cJSON *cJSON_DetachItemFromArray(cJSON * array, int which); - extern void cJSON_DeleteItemFromArray(cJSON * array, int which); - extern cJSON *cJSON_DetachItemFromObject(cJSON * object, - const char *string); - extern void cJSON_DeleteItemFromObject(cJSON * object, - const char *string); - -/* Update array items. */ - extern void cJSON_InsertItemInArray(cJSON * array, int which, cJSON * newitem); /* Shifts pre-existing items to the right. */ - extern void cJSON_ReplaceItemInArray(cJSON * array, int which, - cJSON * newitem); - extern void cJSON_ReplaceItemInObject(cJSON * object, - const char *string, - cJSON * newitem); - -/* Duplicate a cJSON item */ - extern cJSON *cJSON_Duplicate(cJSON * item, int recurse); -/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will -need to be released. With recurse!=0, it will duplicate any children connected to the item. -The item->next and ->prev pointers are always zero on return from Duplicate. */ - -/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ -/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error. If not, then cJSON_GetErrorPtr() does the job. */ - extern cJSON *cJSON_ParseWithOpts(const char *value, - const char **return_parse_end, - int require_null_terminated); - - extern void cJSON_Minify(char *json); - -/* Macros for creating things quickly. */ -#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) -#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) -#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) -#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) -#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) -#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) - -/* When assigning an integer value, it needs to be propagated to valuedouble too. */ -#define cJSON_SetIntValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val)) -#define cJSON_SetNumberValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val)) - -/* Macro for iterating over an array */ -#define cJSON_ArrayForEach(pos, head) for(pos = (head)->child; pos != NULL; pos = pos->next) - -#ifdef __cplusplus -} -#endif -#endif diff --git a/grid.c b/grid.c @@ -25,304 +25,307 @@ #include "ltk.h" -void ltk_set_row_weight(LtkGrid *grid, int row, int weight) +void ltk_set_row_weight(LtkGrid * grid, int row, int weight) { - grid->row_weights[row] = weight; - ltk_recalculate_grid(grid); + grid->row_weights[row] = weight; + ltk_recalculate_grid(grid); } -void ltk_set_column_weight(LtkGrid *grid, int column, int weight) +void ltk_set_column_weight(LtkGrid * grid, int column, int weight) { - grid->column_weights[column] = weight; - ltk_recalculate_grid(grid); + grid->column_weights[column] = weight; + ltk_recalculate_grid(grid); } -void ltk_draw_grid(LtkGrid *grid) +void ltk_draw_grid(LtkGrid * grid) { - int i; - for (i = 0; i < grid->rows * grid->columns; i++) - { - if (!grid->widget_grid[i]) continue; - LtkWidget *ptr = grid->widget_grid[i]; - ptr->draw(ptr); - } + int i; + for (i = 0; i < grid->rows * grid->columns; i++) { + if (!grid->widget_grid[i]) + continue; + LtkWidget *ptr = grid->widget_grid[i]; + ptr->draw(ptr); + } } -LtkGrid *ltk_create_grid(LtkWindow *window, int rows, int columns) +LtkGrid *ltk_create_grid(LtkWindow * window, int rows, int columns) { - LtkGrid *grid = malloc(sizeof(LtkGrid)); + LtkGrid *grid = malloc(sizeof(LtkGrid)); - grid->widget = ltk_create_widget(window, &ltk_draw_grid, &ltk_destroy_grid, 0); - grid->widget.mouse_press = &ltk_grid_mouse_press; - grid->widget.mouse_release = &ltk_grid_mouse_release; - grid->widget.motion_notify = &ltk_grid_motion_notify; - grid->widget.resize = &ltk_recalculate_grid; + grid->widget = + ltk_create_widget(window, &ltk_draw_grid, &ltk_destroy_grid, + 0); + grid->widget.mouse_press = &ltk_grid_mouse_press; + grid->widget.mouse_release = &ltk_grid_mouse_release; + grid->widget.motion_notify = &ltk_grid_motion_notify; + grid->widget.resize = &ltk_recalculate_grid; - grid->rows = rows; - grid->columns = columns; - grid->widget_grid = malloc(rows * columns * sizeof(LtkWidget)); - grid->row_heights = malloc(rows * sizeof(int)); - grid->column_widths = malloc(rows * sizeof(int)); - grid->row_weights = malloc(rows * sizeof(int)); - grid->column_weights = malloc(columns * sizeof(int)); - /* Positions have one extra for the end */ - grid->row_pos = malloc((rows + 1) * sizeof(int)); - grid->column_pos = malloc((columns + 1) * sizeof(int)); - int i; - for (i = 0; i < rows; i++) - { - grid->row_heights[i] = 0; - grid->row_weights[i] = 0; - grid->row_pos[i] = 0; - } - grid->row_pos[rows] = 0; - for (i = 0; i < columns; i++) - { - grid->column_widths[i] = 0; - grid->column_weights[i] = 0; - grid->column_pos[i] = 0; - } - grid->column_pos[columns] = 0; - for (i = 0; i < rows * columns; i++) - { - grid->widget_grid[i] = NULL; - } + grid->rows = rows; + grid->columns = columns; + grid->widget_grid = malloc(rows * columns * sizeof(LtkWidget)); + grid->row_heights = malloc(rows * sizeof(int)); + grid->column_widths = malloc(rows * sizeof(int)); + grid->row_weights = malloc(rows * sizeof(int)); + grid->column_weights = malloc(columns * sizeof(int)); + /* Positions have one extra for the end */ + grid->row_pos = malloc((rows + 1) * sizeof(int)); + grid->column_pos = malloc((columns + 1) * sizeof(int)); + int i; + for (i = 0; i < rows; i++) { + grid->row_heights[i] = 0; + grid->row_weights[i] = 0; + grid->row_pos[i] = 0; + } + grid->row_pos[rows] = 0; + for (i = 0; i < columns; i++) { + grid->column_widths[i] = 0; + grid->column_weights[i] = 0; + grid->column_pos[i] = 0; + } + grid->column_pos[columns] = 0; + for (i = 0; i < rows * columns; i++) { + grid->widget_grid[i] = NULL; + } - ltk_recalculate_grid(grid); - return grid; + ltk_recalculate_grid(grid); + return grid; } void ltk_destroy_grid(void *widget) { - LtkGrid *grid = widget; - LtkWidget *ptr; - int i; - for (i = 0; i < grid->rows * grid->columns; i++) - { - if (grid->widget_grid[i]) - { - ptr = grid->widget_grid[i]; - ptr->destroy(ptr); - } - } - free(grid->widget_grid); - free(grid->row_heights); - free(grid->column_widths); - free(grid->row_weights); - free(grid->column_weights); - free(grid->row_pos); - free(grid->column_pos); - free(grid); + LtkGrid *grid = widget; + LtkWidget *ptr; + int i; + for (i = 0; i < grid->rows * grid->columns; i++) { + if (grid->widget_grid[i]) { + ptr = grid->widget_grid[i]; + ptr->destroy(ptr); + } + } + free(grid->widget_grid); + free(grid->row_heights); + free(grid->column_widths); + free(grid->row_weights); + free(grid->column_weights); + free(grid->row_pos); + free(grid->column_pos); + free(grid); } void ltk_recalculate_grid(void *widget) { - LtkGrid *grid = widget; - unsigned int height_static = 0, width_static = 0; - unsigned int total_row_weight = 0, total_column_weight = 0; - float height_unit = 0, width_unit = 0; - unsigned int currentx = 0, currenty = 0; - int i, j; - for (i = 0; i < grid->rows; i++) - { - total_row_weight += grid->row_weights[i]; - if (grid->row_weights[i] == 0) - { - height_static += grid->row_heights[i]; - } - } - for (i = 0; i < grid->columns; i++) - { - total_column_weight += grid->column_weights[i]; - if (grid->column_weights[i] == 0) - { - width_static += grid->column_widths[i]; - } - } - if (total_row_weight > 0) - { - height_unit = (float)(grid->widget.rect.h - height_static) / (float)total_row_weight; - } - if (total_column_weight > 0) - { - width_unit = (float)(grid->widget.rect.w - width_static) / (float)total_column_weight; - } - for (i = 0; i < grid->rows; i++) - { - grid->row_pos[i] = currenty; - if (grid->row_weights[i] > 0) - { - grid->row_heights[i] = grid->row_weights[i] * height_unit; - } - currenty += grid->row_heights[i]; - } - grid->row_pos[grid->rows] = currenty; - for (i = 0; i < grid->columns; i++) - { - grid->column_pos[i] = currentx; - if (grid->column_weights[i] > 0) - { - grid->column_widths[i] = grid->column_weights[i] * width_unit; - } - currentx += grid->column_widths[i]; - } - grid->column_pos[grid->columns] = currentx; - int orig_width, orig_height; - int end_column, end_row; - for (i = 0; i < grid->rows; i++) - { - for (j = 0; j < grid->columns; j++) - { - if (!grid->widget_grid[i * grid->columns + j]) - { - continue; - } - LtkWidget *ptr = grid->widget_grid[i * grid->columns + j]; - orig_width = ptr->rect.w; - orig_height = ptr->rect.h; - end_row = i + ptr->row_span; - end_column = j + ptr->column_span; - if ((ptr->sticky & (LTK_STICKY_LEFT | LTK_STICKY_RIGHT)) == - (LTK_STICKY_LEFT | LTK_STICKY_RIGHT)) - { - ptr->rect.w = grid->column_pos[end_column] - grid->column_pos[j]; - } - if ((ptr->sticky & (LTK_STICKY_TOP | LTK_STICKY_BOTTOM)) == - (LTK_STICKY_TOP | LTK_STICKY_BOTTOM)) - { - ptr->rect.h = grid->row_pos[end_row] - grid->row_pos[i]; - } - if (orig_width != ptr->rect.w || orig_height != ptr->rect.h) - { - if (ptr->resize) - { - ptr->resize(ptr); - } - } + LtkGrid *grid = widget; + unsigned int height_static = 0, width_static = 0; + unsigned int total_row_weight = 0, total_column_weight = 0; + float height_unit = 0, width_unit = 0; + unsigned int currentx = 0, currenty = 0; + int i, j; + for (i = 0; i < grid->rows; i++) { + total_row_weight += grid->row_weights[i]; + if (grid->row_weights[i] == 0) { + height_static += grid->row_heights[i]; + } + } + for (i = 0; i < grid->columns; i++) { + total_column_weight += grid->column_weights[i]; + if (grid->column_weights[i] == 0) { + width_static += grid->column_widths[i]; + } + } + if (total_row_weight > 0) { + height_unit = + (float) (grid->widget.rect.h - + height_static) / (float) total_row_weight; + } + if (total_column_weight > 0) { + width_unit = + (float) (grid->widget.rect.w - + width_static) / (float) total_column_weight; + } + for (i = 0; i < grid->rows; i++) { + grid->row_pos[i] = currenty; + if (grid->row_weights[i] > 0) { + grid->row_heights[i] = + grid->row_weights[i] * height_unit; + } + currenty += grid->row_heights[i]; + } + grid->row_pos[grid->rows] = currenty; + for (i = 0; i < grid->columns; i++) { + grid->column_pos[i] = currentx; + if (grid->column_weights[i] > 0) { + grid->column_widths[i] = + grid->column_weights[i] * width_unit; + } + currentx += grid->column_widths[i]; + } + grid->column_pos[grid->columns] = currentx; + int orig_width, orig_height; + int end_column, end_row; + for (i = 0; i < grid->rows; i++) { + for (j = 0; j < grid->columns; j++) { + if (!grid->widget_grid[i * grid->columns + j]) { + continue; + } + LtkWidget *ptr = + grid->widget_grid[i * grid->columns + j]; + orig_width = ptr->rect.w; + orig_height = ptr->rect.h; + end_row = i + ptr->row_span; + end_column = j + ptr->column_span; + if ((ptr-> + sticky & (LTK_STICKY_LEFT | LTK_STICKY_RIGHT)) + == (LTK_STICKY_LEFT | LTK_STICKY_RIGHT)) { + ptr->rect.w = + grid->column_pos[end_column] - + grid->column_pos[j]; + } + if ((ptr-> + sticky & (LTK_STICKY_TOP | LTK_STICKY_BOTTOM)) + == (LTK_STICKY_TOP | LTK_STICKY_BOTTOM)) { + ptr->rect.h = + grid->row_pos[end_row] - + grid->row_pos[i]; + } + if (orig_width != ptr->rect.w + || orig_height != ptr->rect.h) { + if (ptr->resize) { + ptr->resize(ptr); + } + } - if ((ptr->sticky & LTK_STICKY_RIGHT) == LTK_STICKY_RIGHT) - { - ptr->rect.x = grid->column_pos[end_column] - ptr->rect.w; - } - else if ((ptr->sticky & LTK_STICKY_LEFT) == LTK_STICKY_LEFT) - { - ptr->rect.x = grid->column_pos[j]; - } - else - { - ptr->rect.x = grid->column_pos[j] + ((grid->column_pos[end_column] - grid->column_pos[j]) / 2 - ptr->rect.w / 2); - } + if ((ptr->sticky & LTK_STICKY_RIGHT) == + LTK_STICKY_RIGHT) { + ptr->rect.x = + grid->column_pos[end_column] - + ptr->rect.w; + } else if ((ptr->sticky & LTK_STICKY_LEFT) == + LTK_STICKY_LEFT) { + ptr->rect.x = grid->column_pos[j]; + } else { + ptr->rect.x = + grid->column_pos[j] + + ((grid->column_pos[end_column] - + grid->column_pos[j]) / 2 - + ptr->rect.w / 2); + } - if ((ptr->sticky & LTK_STICKY_BOTTOM) == LTK_STICKY_BOTTOM) - { - ptr->rect.y = grid->row_pos[end_row] - ptr->rect.h; - } - else if ((ptr->sticky & LTK_STICKY_TOP) == LTK_STICKY_TOP) - { - ptr->rect.y = grid->row_pos[i]; - } - else - { - ptr->rect.y = grid->row_pos[i] + ((grid->row_pos[end_row] - grid->row_pos[i]) / 2 - ptr->rect.h / 2); - } - } - } + if ((ptr->sticky & LTK_STICKY_BOTTOM) == + LTK_STICKY_BOTTOM) { + ptr->rect.y = + grid->row_pos[end_row] - ptr->rect.h; + } else if ((ptr->sticky & LTK_STICKY_TOP) == + LTK_STICKY_TOP) { + ptr->rect.y = grid->row_pos[i]; + } else { + ptr->rect.y = + grid->row_pos[i] + + ((grid->row_pos[end_row] - + grid->row_pos[i]) / 2 - + ptr->rect.h / 2); + } + } + } } -void ltk_grid_widget(void *ptr, LtkGrid *grid, int row, int column, int row_span, int column_span, unsigned short sticky) +void ltk_grid_widget(void *ptr, LtkGrid * grid, int row, int column, + int row_span, int column_span, unsigned short sticky) { - LtkWidget *widget = ptr; - widget->sticky = sticky; - widget->row = row; - widget->column = column; - widget->row_span = row_span; - widget->column_span = column_span; - if (grid->column_weights[column] == 0 && widget->rect.w > grid->column_widths[column]) - { - grid->column_widths[column] = widget->rect.w; - } - if (grid->row_weights[row] == 0 && widget->rect.h > grid->row_heights[row]) - { - grid->row_heights[row] = widget->rect.h; - } - grid->widget_grid[widget->row * grid->columns + widget->column] = widget; - widget->parent = grid; - ltk_recalculate_grid(grid); + LtkWidget *widget = ptr; + widget->sticky = sticky; + widget->row = row; + widget->column = column; + widget->row_span = row_span; + widget->column_span = column_span; + if (grid->column_weights[column] == 0 + && widget->rect.w > grid->column_widths[column]) { + grid->column_widths[column] = widget->rect.w; + } + if (grid->row_weights[row] == 0 + && widget->rect.h > grid->row_heights[row]) { + grid->row_heights[row] = widget->rect.h; + } + grid->widget_grid[widget->row * grid->columns + widget->column] = + widget; + widget->parent = grid; + ltk_recalculate_grid(grid); } -int ltk_grid_find_nearest_column(LtkGrid *grid, int x) +int ltk_grid_find_nearest_column(LtkGrid * grid, int x) { - int i; - for (i = 0; i < grid->columns; i++) - { - if (grid->column_pos[i] <= x && grid->column_pos[i + 1] >= x) - { - return i; - } - } + int i; + for (i = 0; i < grid->columns; i++) { + if (grid->column_pos[i] <= x + && grid->column_pos[i + 1] >= x) { + return i; + } + } + return -1; } -int ltk_grid_find_nearest_row(LtkGrid *grid, int y) +int ltk_grid_find_nearest_row(LtkGrid * grid, int y) { - int i; - for (i = 0; i < grid->rows; i++) - { - if (grid->row_pos[i] <= y && grid->row_pos[i + 1] >= y) - { - return i; - } - } + int i; + for (i = 0; i < grid->rows; i++) { + if (grid->row_pos[i] <= y && grid->row_pos[i + 1] >= y) { + return i; + } + } + return -1; } -ltk_grid_mouse_press(void *widget, XEvent event) +void ltk_grid_mouse_press(void *widget, XEvent event) { - LtkGrid *grid = widget; - int x = event.xbutton.x; - int y = event.xbutton.y; - int row = ltk_grid_find_nearest_row(grid, y); - int column = ltk_grid_find_nearest_column(grid, x); - LtkWidget *ptr = grid->widget_grid[row * grid->columns + column]; - if (ptr && ltk_collide_rect(ptr->rect, x, y)) - { - ltk_mouse_press_event(ptr, event); - } + LtkGrid *grid = widget; + int x = event.xbutton.x; + int y = event.xbutton.y; + int row = ltk_grid_find_nearest_row(grid, y); + int column = ltk_grid_find_nearest_column(grid, x); + if (row == -1 || column == -1) + return; + LtkWidget *ptr = grid->widget_grid[row * grid->columns + column]; + if (ptr && ltk_collide_rect(ptr->rect, x, y)) { + ltk_mouse_press_event(ptr, event); + } } -ltk_grid_mouse_release(void *widget, XEvent event) +void ltk_grid_mouse_release(void *widget, XEvent event) { - LtkGrid *grid = widget; - int x = event.xbutton.x; - int y = event.xbutton.y; - int row = ltk_grid_find_nearest_row(grid, y); - int column = ltk_grid_find_nearest_column(grid, x); - LtkWidget *ptr = grid->widget_grid[row * grid->columns + column]; - if (ptr) - { - if (ltk_collide_rect(ptr->rect, x, y)) - { - ltk_mouse_release_event(ptr, event); - } - else - { - ltk_remove_hover_widget(grid); - ltk_change_active_widget_state(grid, LTK_ACTIVE); - } - } + LtkGrid *grid = widget; + int x = event.xbutton.x; + int y = event.xbutton.y; + int row = ltk_grid_find_nearest_row(grid, y); + int column = ltk_grid_find_nearest_column(grid, x); + if (row == -1 || column == -1) + return; + LtkWidget *ptr = grid->widget_grid[row * grid->columns + column]; + if (ptr) { + if (ltk_collide_rect(ptr->rect, x, y)) { + ltk_mouse_release_event(ptr, event); + } else { + ltk_remove_hover_widget(grid); + ltk_change_active_widget_state(grid, LTK_ACTIVE); + } + } } -ltk_grid_motion_notify(void *widget, XEvent event) +void ltk_grid_motion_notify(void *widget, XEvent event) { - LtkGrid *grid = widget; - int x = event.xbutton.x; - int y = event.xbutton.y; - int row = ltk_grid_find_nearest_row(grid, y); - int column = ltk_grid_find_nearest_column(grid, x); - LtkWidget *ptr = grid->widget_grid[row * grid->columns + column]; - if (ptr) - { - if (ltk_collide_rect(ptr->rect, x, y)) - ltk_motion_notify_event(ptr, event); - else if ((event.xmotion.state & Button1Mask) != Button1Mask) - ltk_remove_hover_widget(grid); - } + LtkGrid *grid = widget; + short pressed = (event.xmotion.state & Button1Mask) == Button1Mask; + if (pressed) + return; + int x = event.xbutton.x; + int y = event.xbutton.y; + int row = ltk_grid_find_nearest_row(grid, y); + int column = ltk_grid_find_nearest_column(grid, x); + if (row == -1 || column == -1) + return; + LtkWidget *ptr = grid->widget_grid[row * grid->columns + column]; + if (ptr) { + if (ltk_collide_rect(ptr->rect, x, y)) + ltk_motion_notify_event(ptr, event); + else if (!pressed) + ltk_remove_hover_widget(grid); + } } diff --git a/grid.h b/grid.h @@ -29,18 +29,17 @@ /* * Struct to represent a grid widget. */ -typedef struct LtkGrid -{ - LtkWidget widget; - unsigned int rows; - unsigned int columns; - void **widget_grid; - unsigned int *row_heights; - unsigned int *column_widths; - unsigned int *row_weights; - unsigned int *column_weights; - unsigned int *row_pos; - unsigned int *column_pos; +typedef struct LtkGrid { + LtkWidget widget; + unsigned int rows; + unsigned int columns; + void **widget_grid; + unsigned int *row_heights; + unsigned int *column_widths; + unsigned int *row_weights; + unsigned int *column_weights; + unsigned int *row_pos; + unsigned int *column_pos; } LtkGrid; /* @@ -49,7 +48,7 @@ typedef struct LtkGrid * row: The row. * weight: The weight to set the row to. */ -void ltk_set_row_weight(LtkGrid *grid, int row, int weight); +void ltk_set_row_weight(LtkGrid * grid, int row, int weight); /* * Set the weight of a column in a grid. @@ -57,13 +56,13 @@ void ltk_set_row_weight(LtkGrid *grid, int row, int weight); * column: The column. * weight: The weight to set the row to. */ -void ltk_set_column_weight(LtkGrid *grid, int column, int weight); +void ltk_set_column_weight(LtkGrid * grid, int column, int weight); /* * Draw all the widgets in a grid. * grid: The grid to draw the widgets of. */ -void ltk_draw_grid(LtkGrid *grid); +void ltk_draw_grid(LtkGrid * grid); /* * Create a grid. @@ -71,7 +70,7 @@ void ltk_draw_grid(LtkGrid *grid); * rows: The number of rows in the grid. * columns: The number of columns in the grid. */ -LtkGrid *ltk_create_grid(LtkWindow *window, int rows, int columns); +LtkGrid *ltk_create_grid(LtkWindow * window, int rows, int columns); /* * Destroy a grid. @@ -96,7 +95,8 @@ void ltk_recalculate_grid(void *widget); * columnspan: The amount of columns the widget should span. * sticky: Mask of the sticky values (LTK_STICKY_*). */ -void ltk_grid_widget(void *ptr, LtkGrid *grid, int row, int column, int rowspan, int columnspan, unsigned short sticky); +void ltk_grid_widget(void *ptr, LtkGrid * grid, int row, int column, + int rowspan, int columnspan, unsigned short sticky); /* * Delegate a mouse press event on the grid to the proper widget. diff --git a/ini.c b/ini.c @@ -0,0 +1,201 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include <stdio.h> +#include <ctype.h> +#include <string.h> + +#include "ini.h" + +#if !INI_USE_STACK +#include <stdlib.h> +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to null at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + strncpy(dest, src, size); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; +#else + char* line; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)malloc(INI_MAX_LINE); + if (!line) { + return -2; + } +#endif + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, INI_MAX_LINE, stream) != NULL) { + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (*start == ';' || *start == '#') { + /* Per Python configparser, allow both ; and # comments at the + start of a line */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!HANDLER(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = end + 1; +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + value = lskip(value); + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} diff --git a/ini.h b/ini.h @@ -0,0 +1,104 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef __INI_H__ +#define __INI_H__ + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> + +/* Nonzero if ini_handler callback should accept lineno parameter. */ +#ifndef INI_HANDLER_LINENO +#define INI_HANDLER_LINENO 0 +#endif + +/* Typedef for prototype of handler function. */ +#if INI_HANDLER_LINENO +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value, + int lineno); +#else +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); +#endif + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See http://code.google.com/p/inih/issues/detail?id=21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Maximum line length for any line in INI file. */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __INI_H__ */ diff --git a/ltk.c b/ltk.c @@ -22,399 +22,432 @@ */ #include "ltk.h" +#include "text.h" void ltk_init(const char *theme_path) { - ltk_global = malloc(sizeof(Ltk)); - Ltk *ltk = ltk_global; /* For convenience */ - ltk->display = XOpenDisplay(NULL); - ltk->screen = DefaultScreen(ltk->display); - ltk->colormap = DefaultColormap(ltk->display, ltk->screen); - ltk->theme = ltk_load_theme(theme_path); - ltk->window_hash = NULL; - ltk->wm_delete_msg = XInternAtom(ltk->display, "WM_DELETE_WINDOW", False); + ltk_global = malloc(sizeof(Ltk)); + Ltk *ltk = ltk_global; /* For convenience */ + ltk->display = XOpenDisplay(NULL); + ltk->screen = DefaultScreen(ltk->display); + ltk->colormap = DefaultColormap(ltk->display, ltk->screen); + ltk->theme = ltk_load_theme(theme_path); + ltk->window_hash = NULL; + ltk->wm_delete_msg = XInternAtom(ltk->display, "WM_DELETE_WINDOW", False); } void ltk_clean_up(void) { - LtkWindow *window; - for (window = ltk_global->window_hash; window != NULL; window = window->hh.next) - { - ltk_destroy_window(window); - } - XCloseDisplay(ltk_global->display); - ltk_destroy_theme(ltk_global->theme); - free(ltk_global); + LtkWindow *window; + for (window = ltk_global->window_hash; window != NULL; + window = window->hh.next) { + ltk_destroy_window(window); + } + XCloseDisplay(ltk_global->display); + ltk_destroy_theme(ltk_global->theme); + free(ltk_global); } void ltk_quit(void) { - ltk_clean_up(); - exit(0); + ltk_clean_up(); + exit(0); } void ltk_fatal(const char *msg) { - fprintf(stderr, msg); - ltk_clean_up(); - exit(1); + fprintf(stderr, msg); + ltk_clean_up(); + exit(1); }; XColor ltk_create_xcolor(const char *hex) { - XColor color; - XParseColor(ltk_global->display, ltk_global->colormap, hex, &color); - XAllocColor(ltk_global->display, ltk_global->colormap, &color); + XColor color; + XParseColor(ltk_global->display, ltk_global->colormap, hex, + &color); + XAllocColor(ltk_global->display, ltk_global->colormap, &color); - return color; + return color; } void ltk_mainloop(void) { - XEvent event; - KeySym key; - char text[255]; - - while(1) - { - XNextEvent(ltk_global->display, &event); - ltk_handle_event(event); - /* - if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) - { - if (text[0] == 'q') - { - XCloseDisplay(ltk_global->display); - exit(0); - } - } - */ - } + XEvent event; + KeySym key; + char text[255]; + + while (1) { + XNextEvent(ltk_global->display, &event); + ltk_handle_event(event); + /* + if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) + { + if (text[0] == 'q') + { + XCloseDisplay(ltk_global->display); + exit(0); + } + } + */ + } } -LtkWindowTheme *ltk_parse_window_theme(cJSON *window_json) +void ltk_redraw_window(LtkWindow * window) { - LtkWindowTheme *window_theme = malloc(sizeof(LtkWindowTheme)); - if (!window_theme) ltk_fatal("No memory for new LtkWindowTheme\n"); - cJSON *border_width = cJSON_GetObjectItem(window_json, "border-width"); - cJSON *fg = cJSON_GetObjectItem(window_json, "foreground"); - cJSON *bg = cJSON_GetObjectItem(window_json, "background"); - window_theme->border_width = border_width ? border_width->valueint : 0; - window_theme->fg = ltk_create_xcolor(fg->valuestring); - window_theme->bg = ltk_create_xcolor(bg->valuestring); - - return window_theme; + LtkWidget *ptr; + if (!window) { + return; + } + if (!window->root_widget) { + return; + } + ptr = window->root_widget; + ptr->draw(ptr); } -void ltk_redraw_window(LtkWindow *window) +LtkWindow *ltk_create_window(const char *title, int x, int y, + unsigned int w, unsigned int h) { - LtkWidget *ptr; - if (!window) - { - return; - } - if (!window->root_widget) - { - return; - } - ptr = window->root_widget; - ptr->draw(ptr); + LtkWindow *window = malloc(sizeof(LtkWindow)); + if (!window) + ltk_fatal("Not enough memory left for window!\n"); + LtkWindowTheme *wtheme = ltk_global->theme->window; /* For convenience */ + Display *display = ltk_global->display; /* For convenience */ + window->xwindow = + XCreateSimpleWindow(display, DefaultRootWindow(display), x, y, + w, h, wtheme->border_width, + wtheme->fg.pixel, wtheme->bg.pixel); + window->gc = XCreateGC(display, window->xwindow, 0, 0); + XSetForeground(display, window->gc, wtheme->fg.pixel); + XSetBackground(display, window->gc, wtheme->bg.pixel); + XSetStandardProperties(display, window->xwindow, title, NULL, None, + NULL, 0, NULL); + XSetWMProtocols(display, window->xwindow, + &ltk_global->wm_delete_msg, 1); + window->root_widget = NULL; + + window->other_event = &ltk_window_other_event; + + window->rect.w = 0; + window->rect.h = 0; + window->rect.x = 0; + window->rect.y = 0; + + XClearWindow(display, window->xwindow); + XMapRaised(display, window->xwindow); + XSelectInput(display, window->xwindow, + ExposureMask | KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | + StructureNotifyMask | PointerMotionMask); + + HASH_ADD_INT(ltk_global->window_hash, xwindow, window); + + return window; } -LtkWindow *ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int h) +void ltk_remove_window(LtkWindow * window) { - LtkWindow *window = malloc(sizeof(LtkWindow)); - if (!window) ltk_fatal("Not enough memory left for window!\n"); - LtkWindowTheme *wtheme = ltk_global->theme->window; /* For convenience */ - Display *display = ltk_global->display; /* For convenience */ - window->xwindow = XCreateSimpleWindow(display, DefaultRootWindow(display), x, y, w, h, wtheme->border_width, wtheme->fg.pixel, wtheme->bg.pixel); - window->gc = XCreateGC(display, window->xwindow, 0, 0); - XSetForeground(display, window->gc, wtheme->fg.pixel); - XSetBackground(display, window->gc, wtheme->bg.pixel); - XSetStandardProperties(display, window->xwindow, title, NULL, None, NULL, 0, NULL); - XSetWMProtocols(display, window->xwindow, &ltk_global->wm_delete_msg, 1); - window->root_widget = NULL; - - window->other_event = &ltk_window_other_event; - - window->rect.w = 0; - window->rect.h = 0; - window->rect.x = 0; - window->rect.y = 0; - - XClearWindow(display, window->xwindow); - XMapRaised(display, window->xwindow); - XSelectInput(display, window->xwindow, ExposureMask|KeyPressMask|KeyReleaseMask|ButtonPressMask|ButtonReleaseMask|StructureNotifyMask|PointerMotionMask); - - HASH_ADD_INT(ltk_global->window_hash, xwindow, window); - - return window; + ltk_destroy_window(window); + if (!ltk_global->window_hash) + ltk_quit(); } -void ltk_remove_window(LtkWindow *window) +void ltk_destroy_window(LtkWindow * window) { - ltk_destroy_window(window); - if (!ltk_global->window_hash) ltk_quit(); + HASH_DEL(ltk_global->window_hash, window); + LtkWidget *ptr = window->root_widget; + if (ptr) + ptr->destroy(ptr); + XDestroyWindow(ltk_global->display, window->xwindow); + free(window); } -void ltk_destroy_window(LtkWindow *window) +void ltk_window_other_event(void *widget, XEvent event) { - HASH_DEL(ltk_global->window_hash, window); - LtkWidget *ptr = window->root_widget; - if (ptr) ptr->destroy(ptr); - XDestroyWindow(ltk_global->display, window->xwindow); - free(window); + LtkWindow *window = widget; + LtkWidget *ptr = window->root_widget; + if (event.type == ConfigureNotify) { + unsigned int w, h; + w = event.xconfigure.width; + h = event.xconfigure.height; + if (ptr && ptr->resize + && (window->rect.w != w || window->rect.h != h)) { + window->rect.w = w; + window->rect.h = h; + ptr->rect.w = w; + ptr->rect.h = h; + ptr->resize(ptr); + ltk_redraw_window(window); + } + } + if (event.type == Expose && event.xexpose.count == 0) { + ltk_redraw_window(window); + } + if (event.type == ClientMessage + && event.xclient.data.l[0] == ltk_global->wm_delete_msg) { + ltk_remove_window(window); + } } -void ltk_window_other_event(void *widget, XEvent event) +void ltk_window_ini_handler(LtkTheme *theme, const char *prop, const char *value) +{ + if (strcmp(prop, "border_width") == 0) { + theme->window->border_width = atoi(value); + } else if (strcmp(prop, "bg") == 0) { + theme->window->bg = ltk_create_xcolor(value); + } else if (strcmp(prop, "fg") == 0) { + theme->window->fg = ltk_create_xcolor(value); + } else if (strcmp(prop, "font") == 0) { + theme->window->font = ltk_load_font(value); + } +} + +int ltk_ini_handler(void *theme, const char *widget, const char *prop, const char *value) { - LtkWindow *window = widget; - LtkWidget *ptr = window->root_widget; - if (event.type == ConfigureNotify) - { - unsigned int w, h; - w = event.xconfigure.width; - h = event.xconfigure.height; - if (ptr && ptr->resize && (window->rect.w != w || window->rect.h != h)) - { - window->rect.w = w; - window->rect.h = h; - ptr->rect.w = w; - ptr->rect.h = h; - ptr->resize(ptr); - ltk_redraw_window(window); - } - } - if (event.type == Expose && event.xexpose.count == 0) - { - ltk_redraw_window(window); - } - if (event.type == ClientMessage && event.xclient.data.l[0] == ltk_global->wm_delete_msg) - { - ltk_remove_window(window); - } + if (strcmp(widget, "window") == 0) { + ltk_window_ini_handler(theme, prop, value); + } else if (strcmp(widget, "button") == 0) { + ltk_button_ini_handler(theme, prop, value); + } } LtkTheme *ltk_load_theme(const char *path) { - char *file_contents = ltk_read_file(path); - - cJSON *json = cJSON_Parse(file_contents); - if (!json) - { - printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); - return NULL; - } - cJSON *button_json = cJSON_GetObjectItem(json, "button"); - if (!button_json) - { - printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); - return NULL; - } - cJSON *window_json = cJSON_GetObjectItem(json, "window"); - if (!window_json) - { - printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); - return NULL; - } - - LtkTheme *theme = malloc(sizeof(LtkTheme)); - theme->button = ltk_parse_button_theme(button_json); - theme->window = ltk_parse_window_theme(window_json); - - free(file_contents); - cJSON_Delete(json); - - return theme; + LtkTheme *theme = malloc(sizeof(LtkTheme)); + theme->window = malloc(sizeof(LtkWindowTheme)); + theme->button = malloc(sizeof(LtkButtonTheme)); + if (ini_parse(path, ltk_ini_handler, theme) < 0) { + fprintf(stderr, "ERROR: Can't load theme %s\n.", path); + exit(1); + } + /* + char *file_contents = ltk_read_file(path); + + cJSON *json = cJSON_Parse(file_contents); + if (!json) { + printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + cJSON *button_json = cJSON_GetObjectItem(json, "button"); + if (!button_json) { + printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + cJSON *window_json = cJSON_GetObjectItem(json, "window"); + if (!window_json) { + printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + + LtkTheme *theme = malloc(sizeof(LtkTheme)); + theme->button = ltk_parse_button_theme(button_json); + theme->window = ltk_parse_window_theme(window_json); + + free(file_contents); + cJSON_Delete(json); + */ + + return theme; } -void ltk_destroy_theme(LtkTheme *theme) +void ltk_destroy_theme(LtkTheme * theme) { - free(theme->button); - free(theme->window); - free(theme); + free(theme->button); + free(theme->window); + free(theme); } char *ltk_read_file(const char *path) { - FILE *f; - long len; - char *file_contents; - f = fopen(path, "rb"); - fseek(f, 0, SEEK_END); - len = ftell(f); - fseek(f, 0, SEEK_SET); - file_contents = malloc(len + 1); - fread(file_contents, 1, len, f); - file_contents[len] = '\0'; - fclose(f); - - return file_contents; + FILE *f; + long len; + char *file_contents; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + file_contents = malloc(len + 1); + fread(file_contents, 1, len, f); + file_contents[len] = '\0'; + fclose(f); + + return file_contents; } int ltk_collide_rect(LtkRect rect, int x, int y) { - return (rect.x <= x && (rect.x + rect.w) >= x && rect.y <= y && (rect.y + rect.h) >= y); + return (rect.x <= x && (rect.x + rect.w) >= x && rect.y <= y + && (rect.y + rect.h) >= y); } void ltk_remove_active_widget(void *widget) { - if (!widget) return; - LtkWidget *parent = widget; - LtkWidget *child; - while (parent->active_widget) - { - child = parent->active_widget; - child->state = LTK_NORMAL; - child->draw(child); - parent->active_widget = NULL; - parent = child; - } + if (!widget) + return; + LtkWidget *parent = widget; + LtkWidget *child; + while (parent->active_widget) { + child = parent->active_widget; + child->state = LTK_NORMAL; + if (child->needs_redraw) + child->draw(child); + parent->active_widget = NULL; + parent = child; + } } void ltk_change_active_widget_state(void *widget, LtkWidgetState state) { - if (!widget) return; - LtkWidget *ptr = widget; - while (ptr = ptr->active_widget) - { - ptr->state = state; - ptr->draw(ptr); - } + if (!widget) + return; + LtkWidget *ptr = widget; + while (ptr = ptr->active_widget) { + ptr->state = state; + ptr->draw(ptr); + } } void ltk_remove_hover_widget(void *widget) { - if (!widget) return; - LtkWidget *parent = widget; - LtkWidget *child; - while (parent->hover_widget) - { - child = parent->hover_widget; - child->state = child->state == LTK_HOVERACTIVE ? LTK_ACTIVE : LTK_NORMAL; - child->draw(child); - parent->hover_widget = NULL; - parent = child; - } + if (!widget) + return; + LtkWidget *parent = widget; + LtkWidget *child; + while (parent->hover_widget) { + child = parent->hover_widget; + child->state = + child->state == + LTK_HOVERACTIVE ? LTK_ACTIVE : LTK_NORMAL; + child->draw(child); + parent->hover_widget = NULL; + parent = child; + } } -LtkWidget ltk_create_widget(LtkWindow *window, void (*draw)(void *), void (*destroy)(void *), int needs_redraw) +LtkWidget ltk_create_widget(LtkWindow *window, void (*draw) (void *), + void (*destroy) (void *), + unsigned int needs_redraw) { - LtkWidget widget; - widget.window = window; - widget.active_widget = NULL; - widget.hover_widget = NULL; - widget.parent = NULL; - - widget.key_press = NULL; - widget.key_release = NULL; - widget.mouse_press = NULL; - widget.mouse_release = NULL; - widget.motion_notify = NULL; - - widget.resize = NULL; - widget.draw = draw; - widget.destroy = destroy; - - widget.needs_redraw = needs_redraw; - widget.state = LTK_NORMAL; - widget.row = 0; - widget.rect.x = 0; - widget.rect.y = 0; - widget.rect.w = 100; - widget.rect.h = 100; - - widget.row = NULL; - widget.column = NULL; - widget.row_span = NULL; - widget.column_span = NULL; - widget.sticky = NULL; - - return widget; + LtkWidget widget; + widget.window = window; + widget.active_widget = NULL; + widget.hover_widget = NULL; + widget.parent = NULL; + + widget.key_press = NULL; + widget.key_release = NULL; + widget.mouse_press = NULL; + widget.mouse_release = NULL; + widget.motion_notify = NULL; + + widget.resize = NULL; + widget.draw = draw; + widget.destroy = destroy; + + widget.needs_redraw = needs_redraw; + widget.state = LTK_NORMAL; + widget.row = 0; + widget.rect.x = 0; + widget.rect.y = 0; + widget.rect.w = 100; + widget.rect.h = 100; + + widget.row = NULL; + widget.column = NULL; + widget.row_span = NULL; + widget.column_span = NULL; + widget.sticky = 0; + + return widget; } void ltk_mouse_press_event(void *widget, XEvent event) { - LtkWidget *ptr = widget; - if (!ptr || ptr->state == LTK_DISABLED) return; - if (event.xbutton.button == 1) - { - LtkWidget *parent = ptr->parent; - if (parent) - { - ltk_remove_active_widget(parent); - parent->active_widget = ptr; - } - ptr->state = LTK_PRESSED; - if (ptr->needs_redraw) ptr->draw(ptr); - } - if (ptr->mouse_press) - { - ptr->mouse_press(ptr, event); - } + LtkWidget *ptr = widget; + if (!ptr || ptr->state == LTK_DISABLED) + return; + if (event.xbutton.button == 1) { + LtkWidget *parent = ptr->parent; + if (parent) { + ltk_remove_active_widget(parent); + parent->active_widget = ptr; + } + ptr->state = LTK_PRESSED; + if (ptr->needs_redraw) + ptr->draw(ptr); + } + if (ptr->mouse_press) { + ptr->mouse_press(ptr, event); + } } void ltk_mouse_release_event(void *widget, XEvent event) { - LtkWidget *ptr = widget; - if (!ptr || ptr->state == LTK_DISABLED) return; - if (ptr->state == LTK_PRESSED) - { - ptr->state = LTK_HOVERACTIVE; - if (ptr->needs_redraw) ptr->draw(ptr); - } - if (ptr->mouse_release) - { - ptr->mouse_release(ptr, event); - } + LtkWidget *ptr = widget; + if (!ptr || ptr->state == LTK_DISABLED) + return; + if (ptr->state == LTK_PRESSED) { + ptr->state = LTK_HOVERACTIVE; + if (ptr->needs_redraw) + ptr->draw(ptr); + } + if (ptr->mouse_release) { + ptr->mouse_release(ptr, event); + } } void ltk_motion_notify_event(void *widget, XEvent event) { - LtkWidget *ptr = widget; - if (ptr && (ptr->state == LTK_NORMAL || ptr->state == LTK_ACTIVE) && - (event.xmotion.state & Button1Mask) != Button1Mask) - { - ptr->state = ptr->state == LTK_ACTIVE ? LTK_HOVERACTIVE : LTK_HOVER; - LtkWidget *parent = ptr->parent; - if (parent) - { - ltk_remove_hover_widget(parent); - parent->hover_widget = ptr; - } - if (ptr->needs_redraw) ptr->draw(ptr); - } - if (ptr->motion_notify) - { - ptr->motion_notify(ptr, event); - } + LtkWidget *ptr = widget; + LtkWidget *parent; + if (!ptr) + return; + short pressed = (event.xmotion.state & Button1Mask) == Button1Mask; + if ((ptr->state == LTK_NORMAL || ptr->state == LTK_ACTIVE) + && !pressed) { + ptr->state = + ptr->state == LTK_ACTIVE ? LTK_HOVERACTIVE : LTK_HOVER; + parent = ptr->parent; + if (parent) { + ltk_remove_hover_widget(parent); + parent->hover_widget = ptr; + } + if (ptr->needs_redraw) + ptr->draw(ptr); + } + if (ptr->motion_notify) + ptr->motion_notify(ptr, event); } void ltk_handle_event(XEvent event) { - LtkWindow *window; - LtkWidget *root_widget; - HASH_FIND_INT(ltk_global->window_hash, &event.xany.window, window); - if (!window) return; - root_widget = window->root_widget; - switch (event.type) - { - case KeyPress: - break; - case KeyRelease: - break; - case ButtonPress: - if (root_widget) ltk_mouse_press_event(root_widget, event); - break; - case ButtonRelease: - if (root_widget) ltk_mouse_release_event(root_widget, event); - break; - case MotionNotify: - if (root_widget) ltk_motion_notify_event(root_widget, event); - break; - default: - /* FIXME: users should be able to register other events like closing the window */ - if (window->other_event) - window->other_event(window, event); - } -}- \ No newline at end of file + LtkWindow *window; + LtkWidget *root_widget; + HASH_FIND_INT(ltk_global->window_hash, &event.xany.window, window); + if (!window) + return; + root_widget = window->root_widget; + switch (event.type) { + case KeyPress: + break; + case KeyRelease: + break; + case ButtonPress: + if (root_widget) + ltk_mouse_press_event(root_widget, event); + break; + case ButtonRelease: + if (root_widget) + ltk_mouse_release_event(root_widget, event); + break; + case MotionNotify: + if (root_widget) + ltk_motion_notify_event(root_widget, event); + break; + default: + /* FIXME: users should be able to register other events like closing the window */ + if (window->other_event) + window->other_event(window, event); + } +} diff --git a/ltk.h b/ltk.h @@ -28,294 +28,141 @@ #include <stdlib.h> #include <X11/Xlib.h> #include <X11/Xutil.h> -#include "cJSON.h" +#include "ini.h" #include "uthash.h" +#include "stb_truetype.h" -/* - * Struct to represent a rectangle. - */ -typedef struct -{ - int x; - int y; - int w; - int h; +typedef struct { + int x; + int y; + int w; + int h; } LtkRect; typedef enum { - LTK_STICKY_LEFT = 1 << 0, - LTK_STICKY_RIGHT = 1 << 1, - LTK_STICKY_TOP = 1 << 2, - LTK_STICKY_BOTTOM = 1 << 3 + LTK_STICKY_LEFT = 1 << 0, + LTK_STICKY_RIGHT = 1 << 1, + LTK_STICKY_TOP = 1 << 2, + LTK_STICKY_BOTTOM = 1 << 3 } LtkStickyMask; -/* - * An enumeration of all widget states. - */ -typedef enum -{ - LTK_NORMAL = 0, - LTK_HOVER = 1, - LTK_PRESSED = 2, - LTK_ACTIVE = 3, - LTK_HOVERACTIVE = 4, - LTK_DISABLED = 5 +typedef enum { + LTK_NORMAL = 0, + LTK_HOVER = 1, + LTK_PRESSED = 2, + LTK_ACTIVE = 3, + LTK_HOVERACTIVE = 4, + LTK_DISABLED = 5 } LtkWidgetState; typedef struct LtkWindow LtkWindow; -/* - * A struct to contain all basic widget information. - * First element of every widget so the widget can - * be cast to LtkWidget. - */ -typedef struct LtkWidget -{ - /* The window the widget will be displayed on */ - LtkWindow *window; - /* For container widgets; the widget that is currently active */ - struct LtkWidget *active_widget; - /* For container widgets; the widget that is currently highlighted */ - struct LtkWidget *hover_widget; - /* Parent widget */ - struct LtkWidget *parent; - - /* Called on KeyPress events */ - void (*key_press)(void *, XEvent event); - /* Called on KeyRelease events */ - void (*key_release)(void *, XEvent event); - /* Called on ButtonPress events */ - void (*mouse_press)(void *, XEvent event); - /* Called on ButtonRelease event */ - void (*mouse_release)(void *, XEvent event); - /* Called on MotionNotify events */ - void (*motion_notify)(void *, XEvent event); - - /* Function to update the widget after its LtkRect has been modified */ - void (*resize)(void *); - /* Function to draw the widget */ - void (*draw)(void *); - /* Function to destroy the widget */ - void (*destroy)(void *); - - /* Position and size of the widget */ - LtkRect rect; - /* Row of widget if gridded */ - unsigned int row; - /* Column of widget if gridded */ - unsigned int column; - /* Row span of widget if gridded */ - unsigned int row_span; - /* Column span of widget if gridded */ - unsigned int column_span; - /* Specifies if the widget needs to be redrawn after a state change */ - int needs_redraw : 1; - /* State of the widget */ - LtkWidgetState state : 3; - /* Similar to sticky in tk */ - unsigned short sticky : 4; +/* FIXME: change row, column, etc. to void* struct so + other layout systems can be implemented */ +typedef struct LtkWidget { + LtkWindow *window; + struct LtkWidget *active_widget; + struct LtkWidget *hover_widget; + struct LtkWidget *parent; + + void (*key_press) (void *, XEvent event); + void (*key_release) (void *, XEvent event); + void (*mouse_press) (void *, XEvent event); + void (*mouse_release) (void *, XEvent event); + void (*motion_notify) (void *, XEvent event); + + void (*resize) (void *); + void (*draw) (void *); + void (*destroy) (void *); + + LtkRect rect; + unsigned int row; + unsigned int column; + unsigned int row_span; + unsigned int column_span; + unsigned int needs_redraw; + LtkWidgetState state; + unsigned short sticky; } LtkWidget; -/* - * Struct to represent a window. - */ -typedef struct LtkWindow -{ - Window xwindow; - GC gc; - void *root_widget; - void (*other_event)(void *, XEvent event); - LtkRect rect; - UT_hash_handle hh; +typedef struct LtkWindow { + Window xwindow; + GC gc; + void *root_widget; + void (*other_event) (void *, XEvent event); + LtkRect rect; + UT_hash_handle hh; } LtkWindow; -/* - * Struct to represent the border width, - * foreground color, and background color - * of a window. - */ -typedef struct LtkWindowTheme -{ - int border_width; - XColor fg; - XColor bg; +typedef struct LtkWindowTheme { + int border_width; + stbtt_fontinfo font; + XColor fg; + XColor bg; } LtkWindowTheme; #include "button.h" #include "grid.h" -/* - * Struct to contain all styles needed by LTK. - */ -typedef struct -{ - LtkWindowTheme *window; - LtkButtonTheme *button; +typedef struct { + LtkWindowTheme *window; + LtkButtonTheme *button; } LtkTheme; -/* - * Load a theme from a JSON file. - * path: The path to the file. - */ LtkTheme *ltk_load_theme(const char *path); -/* - * Struct to contain all global information. - */ -typedef struct -{ - /* The theme used by LTK */ - LtkTheme *theme; - /* The connection to the X server */ - Display *display; - /* The screen LTK is working on */ - int screen; - /* The colormap used by the colors in the theme */ - Colormap colormap; - /* A hash table of all windows */ - LtkWindow *window_hash; - /* Needed for handling the WM_DELETE_WINDOW signal */ - Atom wm_delete_msg; +typedef struct { + LtkTheme *theme; + Display *display; + int screen; + Colormap colormap; + LtkWindow *window_hash; + Atom wm_delete_msg; } Ltk; Ltk *ltk_global; -/* - * Initialize LTK. - * theme_path: The path to the theme. - */ void ltk_init(const char *theme_path); -/* - * Print a message, clean up, and quit. - * msg: The message to print. - */ void ltk_fatal(const char *msg); -/* - * Create an XColor struct from the hex code of a color. - * hex: The hex code. - */ XColor ltk_create_xcolor(const char *hex); -/* - * Main event loop. - */ void ltk_mainloop(void); -/* - * Create a window. - * title: The title of the window. - * x: The x position of the window. - * y: The y position of the window. - * w: The width of the window. - * h: The height of the window. - */ -LtkWindow *ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int h); +LtkWindow *ltk_create_window(const char *title, int x, int y, + unsigned int w, unsigned int h); -/* - * Redraw the widgets in a window. - * window: The window to redraw. - */ -void ltk_redraw_window(LtkWindow *window); +void ltk_redraw_window(LtkWindow * window); -/* - * Destroy a window and quit the application - * if no windows are left. - * window: The window to remove. - */ -void ltk_remove_window(LtkWindow *window); +void ltk_remove_window(LtkWindow * window); -/* - * Destroy a window, freeing its memory. - * window: The window to destroy. - */ -void ltk_destroy_window(LtkWindow *window); +void ltk_destroy_window(LtkWindow * window); -/* - * Handles any event other than mouse or keyboard events. - * widget: Pointer to the window. - * event: The XEvent to be handled. - */ void ltk_window_other_event(void *widget, XEvent event); -/* - * Destroy an LtkTheme struct. - * theme: Pointer to the struct. - */ -void ltk_destroy_theme(LtkTheme *theme); +void ltk_destroy_theme(LtkTheme * theme); -/* - * Check if a rectangle collides with a point. - * rect: The rectangle. - * x: The x coordinate of the point. - * y: The y coordinate of the point. - */ int ltk_collide_rect(LtkRect rect, int x, int y); -/* - * Read a file and return a null-terminated string with the contents. - * path: The path to the file. - */ char *ltk_read_file(const char *path); -/* - * Recursively set the state of all active_widgets in 'widget' to 'state'. - */ void ltk_change_active_widget_state(void *widget, LtkWidgetState state); -/* - * Recursively set the state of all active_widgets in 'widget' to LTK_NORMAL, - * redraw the widgets, and remove the references to them from their parents. - */ void ltk_remove_active_widget(void *widget); -/* - * Recursively set the state of all hover_widgets in 'widget' to LTK_NORMAL or - * LTK_ACTIVE, redraw the widgets, and remove the references to them from their parents. - */ void ltk_remove_hover_widget(void *widget); -/* - * Create a widget. - * window: The window the widget is to be shown on. - * draw: The function used to draw the widget. - * destroy: The function used to destroy the widget. - * needs_redraw: Flag to indicate if the widget needs to be - * be redrawn when it is resized. - * Returns: The new LtkWidget. - */ -LtkWidget ltk_create_widget(LtkWindow *window, void (*draw)(void *), void (*destroy)(void *), int needs_redraw); +LtkWidget ltk_create_widget(LtkWindow * window, void (*draw) (void *), + void (*destroy) (void *), + unsigned int needs_redraw); -/* - * Handles mouse press events for all widgets and calls - * specific widget handlers if set. - * widget: Pointer to the widget the mouse is currrently on. - * event: The event to be handled. - */ void ltk_mouse_press_event(void *widget, XEvent event); -/* - * Handles mouse release events for all widgets and calls - * specific widget handlers if set. - * widget: Pointer to the widget the mouse is currrently on. - * event: The event to be handled. - */ void ltk_mouse_release_event(void *widget, XEvent event); -/* - * Handles mouse motion events for all widgets and calls - * specific widget handlers if set. - * widget: Pointer to the widget the mouse is currrently on. - * event: The event to be handled. - */ void ltk_motion_notify_event(void *widget, XEvent event); -/* - * Handles all events and dispatches them to their - * respective handlers. - * event: The event to be handled. - */ void ltk_handle_event(XEvent event); #endif diff --git a/main.c b/main.c @@ -6,47 +6,50 @@ int main(int argc, char *argv[]) { - Display *display; - int screen; - Window window; - GC gc; + Display *display; + int screen; + Window window; + GC gc; - unsigned long black, white; - XColor green; - Colormap colormap; - display = XOpenDisplay((char *)0); - screen = DefaultScreen(display); - colormap = DefaultColormap(display, screen); - black = BlackPixel(display, screen); - white = WhitePixel(display, screen); - XParseColor(display, colormap, "#00FF00", &green); - XAllocColor(display, colormap, &green); - window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 200, 300, 0, white, green.pixel); - XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL); - XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask); - gc = XCreateGC(display, window, 0, 0); - XSetBackground(display, gc, white); - XSetForeground(display, gc, black); - XClearWindow(display, window); - XMapRaised(display, window); + unsigned long black, white; + XColor green; + Colormap colormap; + display = XOpenDisplay((char *) 0); + screen = DefaultScreen(display); + colormap = DefaultColormap(display, screen); + black = BlackPixel(display, screen); + white = WhitePixel(display, screen); + XParseColor(display, colormap, "#00FF00", &green); + XAllocColor(display, colormap, &green); + window = + XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, + 200, 300, 0, white, green.pixel); + XSetStandardProperties(display, window, "Random Window", NULL, + None, NULL, 0, NULL); + XSelectInput(display, window, + ExposureMask | ButtonPressMask | KeyPressMask); + gc = XCreateGC(display, window, 0, 0); + XSetBackground(display, gc, white); + XSetForeground(display, gc, black); + XClearWindow(display, window); + XMapRaised(display, window); - XEvent event; - KeySym key; - char text[255]; + XEvent event; + KeySym key; + char text[255]; - while(1) - { - XNextEvent(display, &event); - if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) - { - if (text[0] == 'q') - { - XFreeGC(display, gc); - XFreeColormap(display, colormap); - XDestroyWindow(display, window); - XCloseDisplay(display); - exit(0); - } - } - } + while (1) { + XNextEvent(display, &event); + if (event.type == KeyPress + && XLookupString(&event.xkey, text, 255, &key, + 0) == 1) { + if (text[0] == 'q') { + XFreeGC(display, gc); + XFreeColormap(display, colormap); + XDestroyWindow(display, window); + XCloseDisplay(display); + exit(0); + } + } + } } diff --git a/stb_truetype.h b/stb_truetype.h @@ -0,0 +1,4021 @@ +#ifndef BOB +#define BOB +// stb_truetype.h - v1.13 - public domain +// authored from 2009-2016 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket (with fix) +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// David Gow +// David Given +// Ivan-Assen Ivanov +// Anthony Pesch +// Johan Duparc +// Hou Qiming +// Fabian "ryg" Giesen +// Martins Mozeiko +// Cap Petschulat +// Omar Cornut +// github:aloucks +// Peter LaValle +// Sergey Popov +// Giumo X. Clanjor +// Higor Euripedes +// Thomas Fields +// Derek Vinyard +// +// VERSION HISTORY +// +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts, num-fonts-in-TTC function +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversample() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since they different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.6c, 2050 LOC) +// +// Documentation & header file 520 LOC \___ 660 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 620 LOC ---- 620 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 +// +// +// PERFORMANCE MEASUREMENTS FOR 1.06: +// +// 32-bit 64-bit +// Previous release: 8.83 s 7.68 s +// Pool allocations: 7.72 s 6.34 s +// Inline sort : 6.54 s 5.65 s +// New rasterizer : 5.63 s 5.00 s + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLuint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include <stdio.h> +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include <math.h> + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include <math.h> + #define STBTT_sqrt(x) sqrt(x) + #endif + + #ifndef STBTT_fabs + #include <math.h> + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include <stdlib.h> + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include <assert.h> + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include <string.h> + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include <memory.h> + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is weight x height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) { + *x0 = r ? c.min_x : 0; + *y0 = r ? c.min_y : 0; + *x1 = r ? c.max_x : 0; + *y1 = r ? c.max_y : 0; + } + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clear pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + float y1,y2; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + y1 = (x - x0) / dx + y_top; + y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + STBTT_assert(z->ey >= scan_y_top); + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshhold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && mid<n: 0>n => n; 0<n => 0 */ + /* 0<mid && mid>n: 0>n => 0; 0<n => n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + return k; +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// +#endif diff --git a/temp b/temp @@ -0,0 +1 @@ +https://tronche.com/gui/x/xlib/window-information/XGetGeometry.html diff --git a/test1.c b/test1.c @@ -2,39 +2,48 @@ void bob1(void) { - printf("bob\n"); + printf("bob\n"); } void bob2(void *widget, XEvent event) { - LtkButton *button = widget; - if (button->widget.state == LTK_HOVERACTIVE) - { - ltk_quit(); - } + LtkButton *button = widget; + if (button->widget.state == LTK_HOVERACTIVE) { + ltk_quit(); + } } int main(int argc, char *argv[]) { - ltk_init("themes/default.json"); - LtkWindow *window1 = ltk_create_window("Cool Window!", 0, 0, 500, 500); - LtkWindow *window2 = ltk_create_window("Cool Window!", 0, 0, 500, 500); - LtkGrid *grid1 = ltk_create_grid(window1, 2, 2); - window1->root_widget = grid1; - ltk_set_row_weight(grid1, 0, 1); - ltk_set_row_weight(grid1, 1, 1); - ltk_set_column_weight(grid1, 0, 1); - ltk_set_column_weight(grid1, 1, 1); - /* Test callback functions */ - LtkButton *button1 = ltk_create_button(window1, "I'm a button!", &bob1); - ltk_grid_widget(button1, grid1, 0, 0, 1, 1, LTK_STICKY_LEFT | LTK_STICKY_RIGHT); - /* Test manual callback functions */ - LtkButton *button2 = ltk_create_button(window1, "I'm a button!", NULL); - button2->widget.mouse_release = &bob2; - ltk_grid_widget(button2, grid1, 0, 1, 1, 1, LTK_STICKY_TOP | LTK_STICKY_BOTTOM); - LtkButton *button3 = ltk_create_button(window1, "I'm a button!", NULL); - ltk_grid_widget(button3, grid1, 1, 0, 1, 1, LTK_STICKY_TOP | LTK_STICKY_BOTTOM | LTK_STICKY_RIGHT); - LtkButton *button4 = ltk_create_button(window1, "I'm a button!", NULL); - ltk_grid_widget(button4, grid1, 1, 1, 1, 1, LTK_STICKY_LEFT | LTK_STICKY_BOTTOM); - ltk_mainloop(); + ltk_init("themes/default.ini"); + LtkWindow *window1 = + ltk_create_window("Cool Window!", 0, 0, 500, 500); +/* LtkWindow *window2 = ltk_create_window("Cool Window!", 0, 0, 500, 500);*/ + LtkGrid *grid1 = ltk_create_grid(window1, 2, 2); + window1->root_widget = grid1; + ltk_set_row_weight(grid1, 0, 1); + ltk_set_row_weight(grid1, 1, 1); + ltk_set_column_weight(grid1, 0, 1); + ltk_set_column_weight(grid1, 1, 1); + /* Test callback functions */ + LtkButton *button1 = + ltk_create_button(window1, "I'm a button!", &bob1); + ltk_grid_widget(button1, grid1, 0, 0, 1, 1, + LTK_STICKY_LEFT | LTK_STICKY_RIGHT); + /* Test manual callback functions */ + LtkButton *button2 = + ltk_create_button(window1, "I'm a button!", NULL); + button2->widget.mouse_release = &bob2; + ltk_grid_widget(button2, grid1, 0, 1, 1, 1, + LTK_STICKY_TOP | LTK_STICKY_BOTTOM); + LtkButton *button3 = + ltk_create_button(window1, "I'm a button!", NULL); + ltk_grid_widget(button3, grid1, 1, 0, 1, 1, + LTK_STICKY_TOP | LTK_STICKY_BOTTOM | + LTK_STICKY_RIGHT); + LtkButton *button4 = + ltk_create_button(window1, "I'm a button!", NULL); + ltk_grid_widget(button4, grid1, 1, 1, 1, 1, + LTK_STICKY_LEFT | LTK_STICKY_BOTTOM); + ltk_mainloop(); } diff --git a/text.c b/text.c @@ -0,0 +1,131 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include "text.h" + +uint32_t u8_nextmemchar(const char *s, size_t *i) +{ + uint32_t ch = 0; + size_t sz = 0; + do { + ch <<= 6; + ch += (unsigned char)s[(*i)++]; + sz++; + } while (!isutf(s[*i])); + ch -= offsetsFromUTF8[sz-1]; + + return ch; +} + +stbtt_fontinfo ltk_load_font(const char *path) +{ + FILE *f; + long len; + char *contents; + stbtt_fontinfo info; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + contents = malloc(len + 1); + fread(contents, 1, len, f); + contents[len] = '\0'; + fclose(f); + if (!stbtt_InitFont(&info, contents, 0)) + { + fprintf(stderr, "Failed to load font %s\n", path); + exit(1); + } + return info; +} + +int ltk_text_width(uint8_t *text, stbtt_fontinfo fontinfo, int height) +{ + float scale = stbtt_ScaleForPixelHeight(&fontinfo, height); + size_t i = 0; + int length = strlen(text); + if (length < 1) return 0; + int temp_x; + int kern_advance; + int width = 0; + uint32_t char1; + uint32_t char2; + char1 = u8_nextmemchar(text, &i); + while(i <= length) + { + stbtt_GetCodepointHMetrics(&fontinfo, char1, &temp_x, 0); + width += temp_x * scale; + + char2 = u8_nextmemchar(text, &i); + if (!char2) break; + kern_advance = stbtt_GetCodepointKernAdvance(&fontinfo, char1, char2); + width += kern_advance * scale; + char1 = char2; + } + + return width; +} + +unsigned char *ltk_render_text(uint8_t *text, stbtt_fontinfo fontinfo, int height, int width) +{ +/* int width = ltk_text_width(text, fontinfo, height);*/ + unsigned char *bitmap = calloc(sizeof(char), width * height); + float scale = stbtt_ScaleForPixelHeight(&fontinfo, height); + + int ascent, descent, line_gap; + stbtt_GetFontVMetrics(&fontinfo, &ascent, &descent, &line_gap); + ascent *= scale; + descent *= scale; + + size_t i = 0; + int length = strlen(text); + if (length < 1) + { + printf("WARNING: ltk_render_text: length of text is less than 1.\n"); + return bitmap; + } + uint32_t char1, char2; + char1 = u8_nextmemchar(text, &i); + int ax, x = 0, y, x1, y1, x2, y2, byte_offset, kern_advance; + while (i <= length) + { + stbtt_GetCodepointBitmapBox(&fontinfo, char1, scale, scale, &x1, &y1, &x2, &y2); + y = ascent + y1; + byte_offset = x + (y * width); + stbtt_MakeCodepointBitmap(&fontinfo, bitmap + byte_offset, x2 - x1, y2 - y1, width, scale, scale, char1); + + stbtt_GetCodepointHMetrics(&fontinfo, char1, &ax, 0); + x += ax * scale; + + char2 = u8_nextmemchar(text, &i); + if (!char2) break; + kern_advance = stbtt_GetCodepointKernAdvance(&fontinfo, char1, char2); + x += kern_advance * scale; + char1 = char2; + } + + return bitmap; +} diff --git a/text.c.bak b/text.c.bak @@ -0,0 +1,161 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include "text.h" + +uint32_t u8_nextmemchar(const char *s, size_t *i) +{ + uint32_t ch = 0; + size_t sz = 0; + do { + ch <<= 6; + ch += (unsigned char)s[(*i)++]; + sz++; + } while (!isutf(s[*i])); + ch -= offsetsFromUTF8[sz-1]; + + return ch; +} + +stbtt_fontinfo ltk_load_font(const char *path) +{ + FILE *f; + long len; + char *contents; + stbtt_fontinfo info; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + contents = malloc(len + 1); + fread(contents, 1, len, f); + contents[len] = '\0'; + fclose(f); + if (!stbtt_InitFont(&info, contents, 0)) + { + fprintf(stderr, "Failed to load font %s\n", path); + exit(1); + } + return info; +} + +int ltk_text_width(uint8_t *text, stbtt_fontinfo fontinfo, int height) +{ + float scale = stbtt_ScaleForPixelHeight(&fontinfo, height); + size_t i = 0; + int length = strlen(text); + if (length < 1) return 0; + int temp_x; + int kern_advance; + int width = 0; + uint32_t char1; + uint32_t char2; + char1 = u8_nextmemchar(text, &i); + while(i <= length) + { + stbtt_GetCodepointHMetrics(&fontinfo, char1, &temp_x, 0); + width += temp_x * scale; + + char2 = u8_nextmemchar(text, &i); + if (!char2) break; + kern_advance = stbtt_GetCodepointKernAdvance(&fontinfo, char1, char2); + width += kern_advance * scale; + char1 = char2; + } + + return width; +} + +Pixmap ltk_render_text( + Display *display, + Window window, + GC gc, + uint8_t *text, + stbtt_fontinfo fontinfo, + int height, + unsigned long fg, + unsigned long bg) +{ + XWindowAttributes attrs; + XGetWindowAttributes(display, window, &attrs); + int depth = attrs.depth; + + int width = ltk_text_width(text, fontinfo, height); + unsigned char *bitmap = calloc(sizeof(char), width * height); + float scale = stbtt_ScaleForPixelHeight(&fontinfo, height); + + int ascent, descent, line_gap; + stbtt_GetFontVMetrics(&fontinfo, &ascent, &descent, &line_gap); + ascent *= scale; + descent *= scale; + + size_t i = 0; + int length = strlen(text); + if (length < 1) + { + printf("WARNING: ltk_render_text: length of text is less than 1.\n"); + return XCreatePixmap(display, window, 0, 0, depth); + } + uint32_t char1, char2; + char1 = u8_nextmemchar(text, &i); + int ax, x = 0, y, x1, y1, x2, y2, byte_offset, kern_advance; + while (i <= length) + { + stbtt_GetCodepointBitmapBox(&fontinfo, char1, scale, scale, &x1, &y1, &x2, &y2); + y = ascent + y1; + byte_offset = x + (y * width); + stbtt_MakeCodepointBitmap(&fontinfo, bitmap + byte_offset, x2 - x1, y2 - y1, width, scale, scale, char1); + + stbtt_GetCodepointHMetrics(&fontinfo, char1, &ax, 0); + x += ax * scale; + + char2 = u8_nextmemchar(text, &i); + if (!char2) break; + kern_advance = stbtt_GetCodepointKernAdvance(&fontinfo, char1, char2); + x += kern_advance * scale; + char1 = char2; + } + + /* TODO: separate this into a separate function so that one function only creates + * the bitmap and other functions to turn that into a pixmap with solid color + * background, image background, etc. + */ + Pixmap rendered = XCreatePixmap(display, window, width, height, depth); + XSetForeground(display, gc, bg); + for (int i = 0; i < height; i++) + { + for (int j = 0; j < width; j++) + { + /* Yay! Magic! */ + XSetForeground(display, gc, (bitmap[i * width + j] / 255.0) * fg + ((255 - bitmap[i * width + j]) / 255.0) * bg); + XDrawPoint(display, rendered, gc, j, i); + } + } + XSetForeground(display, gc, bg); + return rendered; +} diff --git a/text.h b/text.h @@ -0,0 +1,50 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _LTK_TEXT_ +#define _LTK_TEXT_ + +/* These unicode routines are taken from + * https://github.com/JeffBezanson/cutef8 */ + +/* is c the start of a utf8 sequence? */ +#define isutf(c) (((c)&0xC0)!=0x80) + +static const uint32_t offsetsFromUTF8[6] = { + 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL +}; + +/* next character without NUL character terminator */ +uint32_t u8_nextmemchar(const char *s, size_t *i); + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */ + +stbtt_fontinfo ltk_load_font(const char *path); + +int ltk_text_width(uint8_t *text, stbtt_fontinfo fontinfo, int height); + +unsigned char *ltk_render_text(uint8_t *text, stbtt_fontinfo fontinfo, int height, int width); + +#endif diff --git a/text/: b/text/: @@ -0,0 +1,249 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <limits.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <harfbuzz/hb.h> +#include <harfbuzz/hb-ot.h> +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */ + +char * +ltk_load_file(const char *path, unsigned long *len) +{ + FILE *f; + char *contents; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + *len = ftell(f); + fseek(f, 0, SEEK_SET); + contents = malloc(*len + 1); + fread(contents, 1, *len, f); + contents[*len] = '\0'; + fclose(f); + return contents; +} + +unsigned long +ltk_blend_pixel(Display *display, Colormap colormap, XColor fg, XColor bg, double a) +{ + if (a >= 1) { + return fg.pixel; + } else if (a == 0.0) { + return bg.pixel; + } + + XColor blended; + blended.red = (int)((fg.red - bg.red) * a + bg.red); + blended.green = (int)((fg.green - bg.green) * a + bg.green); + blended.blue = (int)((fg.blue - bg.blue) * a + bg.blue); + XAllocColor(display, colormap, &blended); + + return blended.pixel; +} + +typedef struct { + stbtt_fontinfo font_info; + hb_font_t *font; +} LtkFont; + +LtkFont * +ltk_load_font(char *path) +{ + long len; + LtkFont *font = malloc(sizeof(LtkFont)); + if (!font) { + fprintf(stderr, "Out of memory!\n"); + exit(1); + } + char *contents = ltk_load_file(path, &len); + if (!stbtt_InitFont(&font->font_info, contents, 0)) + { + fprintf(stderr, "Failed to load font %s\n", path); + exit(1); + } + hb_blob_t *blob = hb_blob_create(contents, len, HB_MEMORY_MODE_READONLY, NULL, NULL); + hb_face_t *face = hb_face_create(blob, 0); + hb_blob_destroy(blob); + font->font = hb_font_create(face); + hb_face_destroy(face); + hb_ot_font_set_funcs(font->font); + return font; +} + +unsigned char * +ltk_render_text_bitmap( + uint8_t *text, + LtkFont *font, + int size, + int *width, + int *height) +{ + /* (x1*, y1*): top left corner (relative to origin and absolute) + * (x2*, y2*): bottom right corner (relative to origin and absolute) + */ + int x1, x2, y1, y2, x1_abs, x2_abs, y1_abs, y2_abs, x_abs = 0, y_abs = 0; + int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN; + int char_w, char_h, x_off, y_off; + + int byte_offset; + int alpha; + unsigned char *bitmap; + unsigned char *char_bitmap; + hb_buffer_t *buf; + hb_glyph_info_t *ginf, *gi; + hb_glyph_position_t *gpos, *gp; + unsigned int text_len = 0; + int text_bytes = strlen(text); + if (text_bytes < 1) { + printf("WARNING: ltk_render_text: length of text is less than 1.\n"); + return ""; + } + + buf = hb_buffer_create(); + hb_buffer_set_flags(buf, HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT); + hb_buffer_add_utf8(buf, text, text_bytes, 0, text_bytes); + hb_buffer_guess_segment_properties(buf); + hb_shape(font->font, buf, NULL, 0); + ginf = hb_buffer_get_glyph_infos(buf, &text_len); + gpos = hb_buffer_get_glyph_positions(buf, &text_len); + float scale = stbtt_ScaleForMappingEmToPixels(&font->font_info, size); + + /* Calculate size of bitmap */ + for (int i = 0; i < text_len; i++) { + gi = &ginf[i]; + gp = &gpos[i]; + stbtt_GetGlyphBitmapBox(&font->font_info, gi->codepoint, scale, scale, &x1, &y1, &x2, &y2); + x1_abs = (int)(x_abs + x1 + gp->x_offset * scale); + y1_abs = (int)(y_abs + y1 - gp->y_offset * scale); + x2_abs = x1_abs + (x2 - x1); + y2_abs = y1_abs + (y2 - y1); + if (x1_abs < x_min) x_min = x1_abs; + if (y1_abs < y_min) y_min = y1_abs; + if (x2_abs > x_max) x_max = x2_abs; + if (y2_abs > y_max) y_max = y2_abs; + x_abs += (gp->x_advance * scale); + y_abs -= (gp->y_advance * scale); + } + x_abs = -x_min; + y_abs = -y_min; + *width = x_max - x_min; + *height = y_max - y_min; + /* FIXME: calloc checks for integer overflow, right? */ + /* FIXME: check if null returned */ + bitmap = calloc(*width * *width, sizeof(char)); + if (!bitmap) { + fprintf(stderr, "Can't allocate memory for bitmap!\n"); + exit(1); + } + for (int i = 0; i < text_len; i++) { + gi = &ginf[i]; + gp = &gpos[i]; + stbtt_GetGlyphBitmapBox(&font->font_info, gi->codepoint, scale, scale, &x1, &y1, &x2, &y2); + char_bitmap = stbtt_GetGlyphBitmap(&font->font_info, scale, scale, gi->codepoint, &char_w, &char_h, &x_off, &y_off); + + x1_abs = (int)(x_abs + x1 + gp->x_offset * scale); + y1_abs = (int)(y_abs + y1 - gp->y_offset * scale); + for (int k = 0; k < char_h; k++) + { + for (int j = 0; j < char_w; j++) + { + byte_offset = (y1_abs + k) * *width + x1_abs + j; + alpha = bitmap[byte_offset] + char_bitmap[k * char_w + j]; + if (alpha < 0) printf("%d|", alpha); + /* Cap at 255 so char doesn't overflow */ + bitmap[byte_offset] = alpha > 255 ? 255 : alpha; + } + } + free(char_bitmap); + + x_abs += gp->x_advance * scale; + y_abs -= gp->y_advance * scale; + } + return bitmap; +} + +Pixmap +ltk_render_text( + Display *dpy, + Window window, + GC gc, + XColor fg, + XColor bg, + Colormap colormap, + char *bitmap, + int width, + int height) +{ + XWindowAttributes attrs; + XGetWindowAttributes(dpy, window, &attrs); + int depth = attrs.depth; + Pixmap pix = XCreatePixmap(dpy, window, width, height, depth); + XSetForeground(dpy, gc, bg.pixel); + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + XSetForeground(dpy, gc, ltk_blend_pixel(dpy, colormap, fg, bg, bitmap[i * width + j] / 255.0)); + XDrawPoint(dpy, pix, gc, j, i); + } + } + return pix; +} + +int main(int argc, char *argv[]) +{ + Display *display; + int screen; + Window window; + GC gc; + + unsigned long black, white; + Colormap colormap; + display = XOpenDisplay((char *)0); + screen = DefaultScreen(display); + colormap = DefaultColormap(display, screen); + black = BlackPixel(display, screen); + white = WhitePixel(display, screen); + window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 1366, 512, 0, black, white); + XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL); + XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask); + gc = XCreateGC(display, window, 0, 0); + XSetBackground(display, gc, black); + XSetForeground(display, gc, black); + XClearWindow(display, window); + XMapRaised(display, window); + XColor c1, c2; + XParseColor(display, colormap, "#FFFFFF", &c1); + XParseColor(display, colormap, "#FF0000", &c2); + XAllocColor(display, colormap, &c1); + XAllocColor(display, colormap, &c2); + + LtkFont *font = ltk_load_font("NotoNastaliqUrdu-Regular.ttf"); + int w, h; + unsigned char *bitmap = ltk_render_text_bitmap("چشمہ میڈیا", font, 256, &w, &h); + Pixmap pix = ltk_render_text(display, window, gc, c1, c2, colormap, bitmap, w, h); + XCopyArea(display, pix, window, gc, 0, 0, w, h, 0, 0); + + XEvent event; + KeySym key; + char text[255]; + + while(1) + { + XNextEvent(display, &event); + if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) + { + XCopyArea(display, pix, window, gc, 0, 0, w, h, 0, 0); + if (text[0] == 'q') + { + XFreeGC(display, gc); + XFreeColormap(display, colormap); + XDestroyWindow(display, window); + XCloseDisplay(display); + exit(0); + } + } + } + + return 0; +} diff --git a/text/Awami_beta3.ttf b/text/Awami_beta3.ttf Binary files differ. diff --git a/text/FONTLOG.txt b/text/FONTLOG.txt @@ -0,0 +1,284 @@ +FONTLOG +Gentium Plus font family +======================== + + +This file provides detailed information on the Gentium Plus family of fonts. +This information should be distributed along with the Gentium Plus fonts and +any derivative works. + + +Basic Font Information +---------------------- + +Gentium ("belonging to the nations" in Latin) is a Unicode typeface family +designed to enable the many diverse ethnic groups around the world who use +the Latin script to produce readable, high-quality publications. The +design is intended to be highly readable, reasonably compact, and visually +attractive. Gentium has won a "Certificate of Excellence in Typeface +Design" in two major international typeface design competitions: +bukva:raz! (2001), TDC2003 (2003). + +The Gentium Plus font family is based on the original design. It currently +comes with regular and italic face only, although additional weights are in +development. + +The goal for this product is to provide a single Unicode-based font family +that contains a comprehensive inventory of glyphs needed for almost any +Roman- or Cyrillic-based writing system, whether used for phonetic or +orthographic needs, and provide a matching Greek face. In addition, there +is provision for other characters and symbols useful to linguists. This +font makes use of state-of-the-art font technologies to support complex +typographic issues, such as the need to position arbitrary combinations +of base glyphs and diacritics optimally. + +Two fonts from this typeface family are included in this release: + + * Gentium Plus Regular + * Gentium Plus Italic + +Work is ongoing to provide bold and bold-italic weights, as well as a +complete book-weight family. + +For detailed documentation see the contents of the 'documentation' folder. + + +A Note Regarding the Open Font License and TypeTuner +---------------------------------------------------- + +The OFL prohibits the use of Reserved Font Names "Gentium" and "SIL" in +the name of any font that is derived from the Original Version of Gentium +Plus. However, SIL International (the Copyright Holder) grants through +this separate written agreement the right to use the Reserved Font Names +in any Modified Version of this font created by using TypeTuner as long +as the feature information contained in the Original Version is used, +unmodified, as the source for TypeTuner feature information, and +"Gentium Plus" is not the resulting font family name. + +TypeTuner-produced fonts created in this manner are still bound by the +terms of the OFL. Specifically, the use of Reserved Font Names is +prohibited when making derivatives of a TypeTuner-produced font. Anyone +making a derivative of a TypeTuner font will need to find a different +name. For example a TypeTuner-produced font may be named +"Gentium Plus Literacy SomeFarAwayPlace" but a derivative of that font made +with other tools would not be allowed to use the "Gentium" and "SIL" +Reserved Font Names and would have to be named something like "Nations Serif". + + +ChangeLog +--------- +(This should list both major and minor changes, most recent first.) + +27 Oct 2014 (SIL NRSI team) Gentium Plus version 5.000 +- Added Stylistic Sets to the font for OpenType support + of previously Graphite-only features +- Added Character Variants to the font for OpenType support + of previously Graphite-only features +- Added Serbian feature when Serbian language is turned on +- Added hook D variant feature (for U+018A/U+0257) +- Removed "Show deprecated PUA" feature +- Removed "Romanian-style diacritics" feature + (because glyphs are now encoded) +- Removed "Diacritic selection" feature +- Added U+039E, U+03BC, U+03C6 and U+03C9 in the Greek and Coptic block +- Added U+0528..U+052F in the Cyrillic Supplement block +- Added U+2041 in the General Punctuation block +- Added U+2095..U+209C in the Superscripts and Subscripts block +- Added U+20B6..U+20BD in the Currency Symbols block +- Added U+210C, U+2113, U+2117, U+212D, U+2135, U+214F in the + Letterlike Symbols block +- Added U+2150..U+2152 and U+2189 in the Number Forms block +- Added U+2226, U+2234..U+2235, U+2262, U+2282..U+2287 in the + Mathematical Operators block +- Added U+2640, U+2642, U+266D, U+266F in the Miscellaneous Symbols block +- Added U+27E8..U+27E9 in the Miscellaneous Mathematical Symbols-A block +- Added U+2C7E..U+2C7F in the Latin Extended-C block +- Added U+2C88 in the Coptic block +- Added U+2E00..U+2E0D, U+2E3A..U+2E3B in the Supplemental + Punctuation block +- Added U+A736..U+A73F, U+A742..U+A74D, U+A750..U+A787, U+A790..U+A7AD, + U+A7B0..U+A7B1, U+A7F7..U+A7FA in the Latin Extended-D block. + These were also added to relevant features. +- Added U+A92E in the Kayah Li block (to support the Kayah Li language + when using the Roman script) +- Added U+AB64..U+AB65 in the Latin Extended-E block +- Added U+1D40C, U+1D504..U+1D505, U+1D50A, U+1D50E..U+1D50F, U+1D514, + U+1D516..U+1D517, U+1D519 in the Mathematical Alphanumeric Symbols block +- Added PUA characters U+F26C (curl J) and U+F26D (left-hook b) +- Characters in our PUA that were added to Unicode have had their + codepoints updated: + F1AD>A7F9, F266>A78E, F26B>A78D, F32C>0526, F32D>0527, F17B>1DFD, F209>2C70 +- These PUA characters were deprecated (now white on black glyphs): + U+F17B, U+F1AD, U+F209, U+F247, U+F248, U+F266, U+F26B, U+F32C, U+F32D +- Deleted U+0149 as it is officially deprecated in Unicode +- Added support for shorter macrons under narrow letters (i,l,r,t). + (This only works for Graphite or using precomposed characters in OpenType.) +- Made it possible for saltillo characters (U+A78B and U+A78C) to "carry" + diacritics +- Improved design of U+A722..U+A725 and U+A78D +- Refactored all cedilla positioning +- Removal of unneeded duplicate glyphs (because of improvements in smart + font code) +- Bug fix in Graphite code to allow for simultaneous selection of Vietnamese + alternates and Small Caps +- Bug fix in Graphite code to allow for simultaneous selection of Ogonek + alternates and Small Caps +- Subscript and Superscript parentheses, minus, plus and equals were raised. + Metrics were not changed. +- Adjusted tails on U+2C6B, U+2C6C +- Arrowhead design of U+21A8 modified to match the other arrows +- Placement of Ogonek revisited +- Improved hinting +- Version number bumped up to match other SIL Roman fonts +- Slight modification to positioning of U+0361 and U+035C +- Narrow No-Break Space (U+202F) adjusted to be narrower + than the No-Break Space (U+00A0) +- Changed the strongly curved hooks on Cyrillic U+04C3..U+04C4, U+04C7..U+04C8, + U+04FC..U+04FD, U+0512..U+0513 to be more consistent with other hooks and also + reshaped U+0402, U+0452, U+0494, U+0495, U+04A6, U+04A7, U+0520..U+0523 to match +- Modified Cyrillic italic versions of U+04AF, U+04B1 +- Changed postscript names for U+0218 and U+0219 +- Changed postscript names for U+2203, U+232A and U+2329 +- "Hide tone contour staves" feature now works with single tonebar +- Fixed outline for U+1DBF +- Allowed combining marks to render properly with U+02D0 +- Added U+037F in the Greek and Coptic block +- Adjusted U+1FBD so that it is a spacing mark in Graphite +- Improved design of U+2C72 and U+2C73 (hook w) for collision avoidance +- Adjusted width of U+005F +- Adjusted design of U+0264 + +1 Aug 2012 (SIL NRSI team) Gentium Plus version 1.510 +- Changed Graphite feature identifiers from integers to 4-character + alphanumeric tags (no other changes) + +12 Sep 2011 (SIL NRSI team) Gentium Plus version 1.508 +- Removed the VDMX table +- Changed version number (using ttfsetver) + +25 Aug 2011 (SIL NRSI team) Gentium Plus version 1.506 +- Double-encoded the SIL PUA characters which were added to Unicode 5.2 and 6.0 + (using ttfremap) +- Corrected problem with coverage tables (using ttfsortcover) +- Changed version number (using ttfsetver) +- Added device metric tables +- Added an empty dsig table + +16 Nov 2010 (SIL NRSI team) Gentium Plus version 1.504 +- Added codepage bits for 1251 (Cyrillic), 1257 (Windows Baltic), 1258 + (Vietnamese) + +1 Nov 2010 (SIL NRSI team) Gentium Plus version 1.502 +- Regular and Italic weights only +- New OpenType and Graphite support +- Converted kerning to OpenType (but not Graphite) +- New extended Cyrillic script support, inc. Serbian alternates +- Character set and features up to same level as Charis SIL 4.106 +- Support for more historic Greek characters and alternate seriffed beta +- No separate GentiumAlt fonts (replaced by font features) +- Added WOFF versions and examples + +28 Nov 2005 (Victor Gaultney) Gentium version 1.02 +- Changed licensing to the SIL Open Font License +- Included FontLab source files +- Fixed some duplicate PostScript glyph names +- Fixed italic angle + +19 Sep 2003 (Victor Gaultney) Gentium version 1.01 +- Maintenance release focused on changing internal font +- Information to reflect the changeover to an SIL project +- There is only one bug fix - the Greek mu PS name was changed to try and fix +a display/printing problem. There is still no manual hinting. + +16 Sep 2002 (Victor Gaultney) Gentium version 1.00 +- First public release +- No manual hinting is included in this version. Some has been done - with +good results - but is not yet complete enough. + + +Information for Developers/Contributors +--------------------------------------- + +The release of Gentium Plus version 1.502 (and any subsequent versions) under +the OFL license provides a means for people to modify the fonts to meet their +needs and contribute to the project. For information on what you're allowed to +change or modify, consult the OFL and OFL-FAQ. + +Anyone can make their own modified version of Gentium Plus (using a different +name), but SIL International will continue to maintain and develop the +canonical version of the Gentium Plus fonts. As the package maintainer, we +welcome contributions. Here are some things to keep in mind: + +Format: We are open to contributions in various formats, but if you want to +maximise the chances of us including your work, please make it available to +us (via email or a URL) as either a FontLab database (preferred) or a +PostScript Type 1 (or OT-CFF) font. + +Source files: The primary source files for the fonts are the fonts themselves. +They contain all the important data in the fonts and can be studied and +modified using open font tools such as FontForge and TTX. The developer +release contains additional source files that might be useful. See the file +source/SOURCES.txt in that release archive for further information. + +Copyright attribution: If you submit something for inclusion in the main +Gentium Plus fonts, we will ask you to affirm that it is your original work, +and ask you to assign the copyright of your work to SIL International. This +is to ensure that future releases can be made under improved versions of the +OFL without needing to track you down for further permission. This follows +the same principle used by the FSF. Keep in mind that we are a +not-for-profit organization committed to free/libre and open source +software, and that any contributions incorporated in the fonts will always +be available under the OFL or a similar license. + +Quality: Because we want to be able to guarantee a high level of quality for +the primary Gentium Plus fonts, we will review submissions carefully. Please +don't be discouraged if we do not include a submission for this reason, or +ask you to make specific revisions. + +Types of contributions: If you wish to make a contribution - a set of +additional glyphs, scripts, code, etc. - please contact us before you do any +work to see if it is a contribution we currently need. Every addition adds +to the complexity of the project and needs to be carefully planned. This +also avoids two people working on the same type of addition at the same time. + +Linux packages: Please contact the upstream maintainer of the various +packages - nicolas_spalinger@sil.org - if you want to help package or +maintain a package. + +When submissions will be included: We plan to revise the fonts when major +updates are needed (eg new versions of Unicode). If you wish to make +submissions, contact us on the timing. + + +Acknowledgements +---------------- +(Here is where contributors can be acknowledged. If you make modifications be +sure to add your name (N), email (E), web-address (W) and description (D). +This list is sorted by last name in alphabetical order.) + +N: Victor Gaultney +E: victor_gaultney@sil.org +W: http://www.sil.org/~gaultney/ +D: Original Designer + +N: Annie Olsen +E: http://scripts.sil.org/support +W: http://scripts.sil.org/ +D: Contributed some extended Latin glyphs + +N: Iska Routamaa +E: http://scripts.sil.org/support +W: http://scripts.sil.org/ +D: Contributed some extended Latin glyphs and extensive work on the italic face + +N: SIL font engineers +E: http://scripts.sil.org/support +W: http://scripts.sil.org/ +D: Graphite, OpenType, and TypeTuner code, and build support + +The Gentium and Gentium Plus fonts are maintained by SIL International. + +For more information please visit the Gentium page on SIL International's +Computers and Writing systems website: http://scripts.sil.org/gentium + +Support through the website: http://scripts.sil.org/Support diff --git a/text/GENTIUM-FAQ.txt b/text/GENTIUM-FAQ.txt @@ -0,0 +1,205 @@ +GENTIUM-FAQ +Gentium Plus +======================== + +Here are some answers to frequently asked questions about the Gentium Plus +fonts: + + +General +======== + +How do you pronounce Gentium? + + The preferred pronunciation is with a soft G as in 'general', not a + hard one as in 'gold': JEN-tee-oom. + + +Licensing +========= + +I want to use Gentium Plus in my publication - can I? + + Gentium Plus is released under the SIL Open Font License, which permits + use for any publication, whether electronic or printed. For more answers + to use questions see the OFL-FAQ. The license, alongside information + specific to Gentium Plus, is in the release package. + +I would like to bundle Gentium Plus with my application - can I? + + This is our most common question. The SIL Open Font License allows + bundling with applications, even commercial closed source ones, with + some restrictions. See the OFL.txt file and the OFL-FAQ. + +Can I use the font on my web site? + + You can certainly create web pages that request that Gentium Plus be + used to display them (both if that font is already available on the + user's system or if it is delivered via @font-face). According to the + license, you are also allowed to place the font on your site for people + to download it. We would strongly recommend, however, that you direct + users to our site to download the font. This ensures that they are + always using the most recent version with bug fixes, etc. To make this + easier, there is a simple URL for Gentium: http://scripts.sil.org/Gentium + There is further important discussion of webfont issues in the OFL-FAQ. + +Is Gentium Plus going to stay unrestricted and available at no cost? + + There is no intention to ever charge users for using Gentium and its + variants. The current version is licensed under a free/open license and + future versions will be similarly unencumbered. + + +Modification +============ + +I would like to modify Gentium Plus to add a couple of characters I need. +Can I? + + Yes - that is allowed as long as you abide by the conditions of the + SIL Open Font License. + +So will you add glyphs upon request? + + If you have a special symbol that you need (say, for a particular + transcription system), the best means of doing so will be to ensure + that the symbol makes it into the Unicode Standard. It is impossible + for us to add every glyph that every person desires, but we do place + a high priority on adding pretty much anything that falls in certain + Unicode ranges (extended Latin, Greek, Cyrillic). You can send us your + requests, but please understand that we are unlikely to add symbols + where the user base is very small, unless they have been accepted + into Unicode. + +Can I send you work I've done to be incorporated into Gentium Plus? + + Yes. See the FONTLOG for information on becoming a contributor. + + +Technical +========= + +Can you help me get Gentium Plus working on my system? + + We cannot afford to offer individual technical support. The best + resource is this website, where we hope to offer some limited help. + However, we do want to hear of any problems you encounter, so that + we can add them to the list of bugs to fix in later releases. + Our contact address is gentium@sil.org. Please understand + that we cannot guarantee a personal response. + +I can't find all the extended Latin letters in the font. How do I type them? + + Gentium Plus is Unicode-encoded, which means that the computer stores a + special, unique code for each letter in your document. Since most + keyboards do not have hundreds of keys, special software is needed + in order to type the hundreds of special characters supported by the + font. See the README.txt file for more information. + +I can't find the 'o with right hook' in the font. Where is it? + + Combinations of base letters with diacritics are often called + composite, or pre-composed glyphs. Gentium Plus has hundreds of these + (the ones that are included in Unicode). There are, however, many + common combinations that are not represented by a single composite. + It is possible to enter these into a document, but only as + individual components. So 'o with right hook' would be entered as + 'o', then 'right hook'. Although this may not look very good in some + cases, we're not able to anticipate every possible combination. + Gentium Plus includes 'smart font' support for both OpenType and + Graphite. + +Some diacritics are not aligning well with base glyphs, and if I type more +than one diacritic, they run into each other. Why is that? + + The smart diacritic positioning in Gentium Plus relies on either + OpenType or Graphite. The application you are using must support + one of these technologies in order to see appropriate diacritic + positioning. + +How do I type the Greek letters? + + You need a Unicode-compatible keyboarding system, which is not + included in the release. + +I'm having problems making PDFs -- why won't my document distill? + + Gentium Plus is a large font, with lots of glyphs. As a result, + some older printers, PDF distillers and readers can balk at PDFs + that have the complete font embedded. The easiest way to avoid + this is to have the PDF distiller subset the font. This is + generally a good idea anyway (with any font) and can reduce the + size of your files. + + +Plus +===== + +How are the Gentium Plus fonts different from Gentium? + + This font is based on the original Gentium design, but with an expanded + character and glyph repertoire. It currently comes with regular and + italic faces. It comes with near-complete support for Latin, Cyrillic + and Greek. It also contains 'smart font' support for OpenType and Graphite + technologies. This allows for correct diacritic placement over all base + characters, whether they are tall, short, wide, narrow, with or without + descenders. It also provides for a large variety of alternates glyphs. + These are described on the Gentium website. + +Why is the line spacing greater for the Plus fonts? + + In some environments, stacked diacritics in Gentium could display as + 'chopped-off'. Gentium Plus has slightly wider default line spacing + in order to avoid this problem. Most applications do, however, let you + set the line spacing explicitly, so you can have the lines spaced + precisely as you wish. + +Is there an Alt version of the Basic fonts? + + No, although you may notice that capitals and some tall lowercase + letters do use 'low-profile' versions. Gentium Plus also includes + OpenType and Graphite features to turn low-profile diacritics on + and off. + + +Future +====== + +What are your future plans for Gentium Plus? + + Our next major effort is completing bold and bold italic weights + of Gentium Plus alongside a new Gentium Book Plus family. These new + weights are currently available for Gentium Basic/Gentium Book Basic. + +Do you plan to include other typographic enhancements (old style +figures, etc.)? + + Those would be nice, wouldn't they? From a design point of view, + it would be great to have these refinements, and we haven't ruled + them out. But there are other needs that are much higher priority + (such as bold). If you think you could contribute some of your time + and effort to these enhancements, see the FONTLOG.txt file for + information on becoming a contributor. + +Sans-serif? + + There is a definite need for a sans-serif font that shares some of + Gentium's strengths -- high readability, economy of space, etc. It + would also be great if that font also harmonized well with Gentium. + We don't currently have any plans for a companion face, although one + of our other projects - Andika - may be useful. Andika is a sans-serif + font designed specifically for use in literacy programs around the + world, and is available from our web site. + +Will you be extending Gentium to cover other scripts, and Hebrew in +particular? + + It is very unlikely that we would do this, as there are so many + pressing needs in Latin, Greek and Cyrillic scripts. + +Will there be a Type 1 version? What about OpenType? + + Gentium Plus includes OpenType and Graphite support. We do not plan + to produce Type 1 versions at this time, but please write us if this + is important (and tell us why). We already provide the PostScript + bézier curves in the 'designsource' files in the developer release. diff --git a/text/GentiumPlus-I.ttf b/text/GentiumPlus-I.ttf Binary files differ. diff --git a/text/GentiumPlus-R.ttf b/text/GentiumPlus-R.ttf Binary files differ. diff --git a/text/LICENSE_OFL.txt b/text/LICENSE_OFL.txt @@ -0,0 +1,92 @@ +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/text/Makefile b/text/Makefile @@ -0,0 +1,2 @@ +all: text-hb.ubernew.c + gcc text-hb.ubernew.c -g -std=c99 -o test -I /usr/X11R6/include -L /usr/X11R6/lib -I /usr/local/include -L /usr/local/lib -lm -lharfbuzz -lX11 -lXrender diff --git a/text/NotoNastaliqUrdu-Regular.ttf b/text/NotoNastaliqUrdu-Regular.ttf Binary files differ. diff --git a/text/OFL-FAQ.txt b/text/OFL-FAQ.txt @@ -0,0 +1,427 @@ +OFL FAQ - Frequently Asked Questions about the SIL Open Font License (OFL) +Version 1.1-update4 - Sept 2014 +(See http://scripts.sil.org/OFL for updates) + + +CONTENTS OF THIS FAQ +1 USING AND DISTRIBUTING FONTS LICENSED UNDER THE OFL +2 USING OFL FONTS FOR WEB PAGES AND ONLINE WEB FONT SERVICES +3 MODIFYING OFL-LICENSED FONTS +4 LICENSING YOUR ORIGINAL FONTS UNDER THE OFL +5 CHOOSING RESERVED FONT NAMES +6 ABOUT THE FONTLOG +7 MAKING CONTRIBUTIONS TO OFL PROJECTS +8 ABOUT THE LICENSE ITSELF +9 ABOUT SIL INTERNATIONAL +APPENDIX A - FONTLOG EXAMPLE + +1 USING AND DISTRIBUTING FONTS LICENSED UNDER THE OFL + +1.1 Can I use the fonts for a book or other print publication, to create logos or other graphics or even to manufacture objects based on their outlines? +Yes. You are very welcome to do so. Authors of fonts released under the OFL allow you to use their font software as such for any kind of design work. No additional license or permission is required, unlike with some other licenses. Some examples of these uses are: logos, posters, business cards, stationery, video titling, signage, t-shirts, personalised fabric, 3D-printed/laser-cut shapes, sculptures, rubber stamps, cookie cutters and lead type. + +1.1.1 Does that restrict the license or distribution of that artwork? +No. You remain the author and copyright holder of that newly derived graphic or object. You are simply using an open font in the design process. It is only when you redistribute, bundle or modify the font itself that other conditions of the license have to be respected (see below for more details). + +1.1.2 Is any kind of acknowledgement required? +No. Font authors may appreciate being mentioned in your artwork's acknowledgements alongside the name of the font, possibly with a link to their website, but that is not required. + +1.2 Can the fonts be included with Free/Libre and Open Source Software collections such as GNU/Linux and BSD distributions and repositories? +Yes! Fonts licensed under the OFL can be freely included alongside other software under FLOSS (Free/Libre and Open Source Software) licenses. Since fonts are typically aggregated with, not merged into, existing software, there is little need to be concerned about incompatibility with existing software licenses. You may also repackage the fonts and the accompanying components in a .rpm or .deb package (or other similar package formats or installers) and include them in distribution CD/DVDs and online repositories. (Also see section 5.9 about rebuilding from source.) + +1.3 I want to distribute the fonts with my program. Does this mean my program also has to be Free/Libre and Open Source Software? +No. Only the portions based on the Font Software are required to be released under the OFL. The intent of the license is to allow aggregation or bundling with software under restricted licensing as well. + +1.4 Can I sell a software package that includes these fonts? +Yes, you can do this with both the Original Version and a Modified Version of the fonts. Examples of bundling made possible by the OFL would include: word processors, design and publishing applications, training and educational software, games and entertainment software, mobile device applications, etc. + +1.5 Can I include the fonts on a CD of freeware or commercial fonts? +Yes, as long some other font or software is also on the disk, so the OFL font is not sold by itself. + +1.6 Why won't the OFL let me sell the fonts alone? +The intent is to keep people from making money by simply redistributing the fonts. The only people who ought to profit directly from the fonts should be the original authors, and those authors have kindly given up potential direct income to distribute their fonts under the OFL. Please honour and respect their contribution! + +1.7 What about sharing OFL fonts with friends on a CD, DVD or USB stick? +You are very welcome to share open fonts with friends, family and colleagues through removable media. Just remember to include the full font package, including any copyright notices and licensing information as available in OFL.txt. In the case where you sell the font, it has to come bundled with software. + +1.8 Can I host the fonts on a web site for others to use? +Yes, as long as you make the full font package available. In most cases it may be best to point users to the main site that distributes the Original Version so they always get the most recent stable and complete version. See also discussion of web fonts in Section 2. + +1.9 Can I host the fonts on a server for use over our internal network? +Yes. If the fonts are transferred from the server to the client computer by means that allow them to be used even if the computer is no longer attached to the network, the full package (copyright notices, licensing information, etc.) should be included. + +1.10 Does the full OFL license text always need to accompany the font? +The only situation in which an OFL font can be distributed without the text of the OFL (either in a separate file or in font metadata), is when a font is embedded in a document or bundled within a program. In the case of metadata included within a font, it is legally sufficient to include only a link to the text of the OFL on http://scripts.sil.org/OFL, but we strongly recommend against this. Most modern font formats include metadata fields that will accept the full OFL text, and full inclusion increases the likelihood that users will understand and properly apply the license. + +1.11 What do you mean by 'embedding'? How does that differ from other means of distribution? +By 'embedding' we mean inclusion of the font in a document or file in a way that makes extraction (and redistribution) difficult or clearly discouraged. In many cases the names of embedded fonts might also not be obvious to those reading the document, the font data format might be altered, and only a subset of the font - only the glyphs required for the text - might be included. Any other means of delivering a font to another person is considered 'distribution', and needs to be accompanied by any copyright notices and licensing information available in OFL.txt. + +1.12 So can I embed OFL fonts in my document? +Yes, either in full or a subset. The restrictions regarding font modification and redistribution do not apply, as the font is not intended for use outside the document. + +1.13 Does embedding alter the license of the document itself? +No. Referencing or embedding an OFL font in any document does not change the license of the document itself. The requirement for fonts to remain under the OFL does not apply to any document created using the fonts and their derivatives. Similarly, creating any kind of graphic using a font under OFL does not make the resulting artwork subject to the OFL. + +1.14 If OFL fonts are extracted from a document in which they are embedded (such as a PDF file), what can be done with them? Is this a risk to author(s)? +The few utilities that can extract fonts embedded in a PDF will typically output limited amounts of outlines - not a complete font. To create a working font from this method is much more difficult and time consuming than finding the source of the original OFL font. So there is little chance that an OFL font would be extracted and redistributed inappropriately through this method. Even so, copyright laws address any misrepresentation of authorship. All Font Software released under the OFL and marked as such by the author(s) is intended to remain under this license regardless of the distribution method, and cannot be redistributed under any other license. We strongly discourage any font extraction - we recommend directly using the font sources instead - but if you extract font outlines from a document, please be considerate: respect the work of the author(s) and the licensing model. + +1.15 What about distributing fonts with a document? Within a compressed folder structure? Is it distribution, bundling or embedding? +Certain document formats may allow the inclusion of an unmodified font within their file structure which may consist of a compressed folder containing the various resources forming the document (such as pictures and thumbnails). Including fonts within such a structure is understood as being different from embedding but rather similar to bundling (or mere aggregation) which the license explicitly allows. In this case the font is conveyed unchanged whereas embedding a font usually transforms it from the original format. The OFL does not allow anyone to extract the font from such a structure to then redistribute it under another license. The explicit permission to redistribute and embed does not cancel the requirement for the Font Software to remain under the license chosen by its author(s). Even if the font travels inside the document as one of its assets, it should not lose its authorship information and licensing. + +1.16 What about ebooks shipping with open fonts? +The requirements differ depending on whether the fonts are linked, embedded or distributed (bundled or aggregated). Some ebook formats use web technologies to do font linking via @font-face, others are designed for font embedding, some use fonts distributed with the document or reading software, and a few rely solely on the fonts already present on the target system. The license requirements depend on the type of inclusion as discussed in 1.15. + +1.17 Can Font Software released under the OFL be subject to URL-based access restrictions methods or DRM (Digital Rights Management) mechanisms? +Yes, but these issues are out-of-scope for the OFL. The license itself neither encourages their use nor prohibits them since such mechanisms are not implemented in the components of the Font Software but through external software. Such restrictions are put in place for many different purposes corresponding to various usage scenarios. One common example is to limit potentially dangerous cross-site scripting attacks. However, in the spirit of libre/open fonts and unrestricted writing systems, we strongly encourage open sharing and reuse of OFL fonts, and the establishment of an environment where such restrictions are unnecessary. Note that whether you wish to use such mechanisms or you prefer not to, you must still abide by the rules set forth by the OFL when using fonts released by their authors under this license. Derivative fonts must be licensed under the OFL, even if they are part of a service for which you charge fees and/or for which access to source code is restricted. You may not sell the fonts on their own - they must be part of a larger software package, bundle or subscription plan. For example, even if the OFL font is distributed in a software package or via an online service using a DRM mechanism, the user would still have the right to extract that font, use, study, modify and redistribute it under the OFL. + +1.18 I've come across a font released under the OFL. How can I easily get more information about the Original Version? How can I know where it stands compared to the Original Version or other Modified Versions? +Consult the copyright statement(s) in the license for ways to contact the original authors. Consult the FONTLOG (see section 6 for more details and examples) for information on how the font differs from the Original Version, and get in touch with the various contributors via the information in the acknowledgement section. Please consider using the Original Versions of the fonts whenever possible. + +1.19 What do you mean in condition 4 of the OFL's permissions and conditions? Can you provide examples of abusive promotion / endorsement / advertisement vs. normal acknowledgement? +The intent is that the goodwill and reputation of the author(s) should not be used in a way that makes it sound like the original author(s) endorse or approve of a specific Modified Version or software bundle. For example, it would not be right to advertise a word processor by naming the author(s) in a listing of software features, or to promote a Modified Version on a web site by saying "designed by ...". However, it would be appropriate to acknowledge the author(s) if your software package has a list of people who deserve thanks. We realize that this can seem to be a grey area, but the standard used to judge an acknowledgement is that if the acknowledgement benefits the author(s) it is allowed, but if it primarily benefits other parties, or could reflect poorly on the author(s), then it is not. + +1.20 I'm writing a small app for mobile platforms, do I need to include the whole package? +If you bundle a font under the OFL with your mobile app you must comply with the terms of the license. At a minimum you must include the copyright statement, the license notice and the license text. A mention of this information in your About box or Changelog, with a link to where the font package is from, is good practice, and the extra space needed to carry these items is very small. You do not, however, need to include the full contents of the font package - only the fonts you use and the copyright and license that apply to them. For example, if you only use the regular weight in your app, you do not need to include the italic and bold versions. + +1.21 What about including OFL fonts by default in my firmware or dedicated operating system? +Many such systems are restricted and turned into appliances so that users cannot study or modify them. Using open fonts to increase quality and language coverage is a great idea, but you need to be aware that if there is a way for users to extract fonts you cannot legally prevent them from doing that. The fonts themselves, including any changes you make to them, must be distributed under the OFL even if your firmware has a more restrictive license. If you do transform the fonts and change their formats when you include them in your firmware you must respect any names reserved by the font authors via the RFN mechanism and pick your own font name. Alternatively if you directly add a font under the OFL to the font folder of your firmware without modifying or optimizing it you are simply bundling the font like with any other software collection, and do not need to make any further changes. + +1.22 Can I make and publish CMS themes or templates that use OFL fonts? Can I include the fonts themselves in the themes or templates? Can I sell the whole package? +Yes, you are very welcome to integrate open fonts into themes and templates for your preferred CMS and make them more widely available. Remember that you can only sell the fonts and your CMS add-on as part of a software bundle. (See 1.4 for details and examples about selling bundles). + +1.23 Can OFL fonts be included in services that deliver fonts to the desktop from remote repositories? Even if they contain both OFL and non-OFL fonts? +Yes. Some foundries have set up services to deliver fonts to subscribers directly to desktops from their online repositories; similarly, plugins are available to preview and use fonts directly in your design tool or publishing suite. These services may mix open and restricted fonts in the same channel, however they should make a clear distinction between them to users. These services should also not hinder users (such as through DRM or obfuscation mechanisms) from extracting and using the OFL fonts in other environments, or continuing to use OFL fonts after subscription terms have ended, as those uses are specifically allowed by the OFL. + +1.24 Can services that provide or distribute OFL fonts restrict my use of them? +No. The terms of use of such services cannot replace or restrict the terms of the OFL, as that would be the same as distributing the fonts under a different license, which is not allowed. You are still entitled to use, modify and redistribute them as the original authors have intended outside of the sole control of that particular distribution channel. Note, however, that the fonts provided by these services may differ from the Original Versions. + + +2 USING OFL FONTS FOR WEBPAGES AND ONLINE WEB FONT SERVICES + +NOTE: This section often refers to a separate paper on 'Web Fonts & RFNs'. This is available at http://scripts.sil.org/OFL_web_fonts_and_RFNs + +2.1 Can I make webpages using these fonts? +Yes! Go ahead! Using CSS (Cascading Style Sheets) is recommended. Your three best options are: +- referring directly in your stylesheet to open fonts which may be available on the user's system +- providing links to download the full package of the font - either from your own website or from elsewhere - so users can install it themselves +- using @font-face to distribute the font directly to browsers. This is recommended and explicitly allowed by the licensing model because it is distribution. The font file itself is distributed with other components of the webpage. It is not embedded in the webpage but referenced through a web address which will cause the browser to retrieve and use the corresponding font to render the webpage (see 1.11 and 1.15 for details related to embedding fonts into documents). As you take advantage of the @font-face cross-platform standard, be aware that web fonts are often tuned for a web environment and not intended for installation and use outside a browser. The reasons in favour of using web fonts are to allow design of dynamic text elements instead of static graphics, to make it easier for content to be localized and translated, indexed and searched, and all this with cross-platform open standards without depending on restricted extensions or plugins. You should check the CSS cascade (the order in which fonts are being called or delivered to your users) when testing. + +2.2 Can I make and use WOFF (Web Open Font Format) versions of OFL fonts? +Yes, but you need to be careful. A change in font format normally is considered modification, and Reserved Font Names (RFNs) cannot be used. Because of the design of the WOFF format, however, it is possible to create a WOFF version that is not considered modification, and so would not require a name change. You are allowed to create, use and distribute a WOFF version of an OFL font without changing the font name, but only if: + +- the original font data remains unchanged except for WOFF compression, and +- WOFF-specific metadata is either omitted altogether or present and includes, unaltered, the contents of all equivalent metadata in the original font. + +If the original font data or metadata is changed, or the WOFF-specific metadata is incomplete, the font must be considered a Modified Version, the OFL restrictions would apply and the name of the font must be changed: any RFNs cannot be used and copyright notices and licensing information must be included and cannot be deleted or modified. You must come up with a unique name - we recommend one corresponding to your domain or your particular web application. Be aware that only the original author(s) can use RFNs. This is to prevent collisions between a derivative tuned to your audience and the original upstream version and so to reduce confusion. + +Please note that most WOFF conversion tools and online services do not meet the two requirements listed above, and so their output must be considered a Modified Version. So be very careful and check to be sure that the tool or service you're using is compressing unchanged data and completely and accurately reflecting the original font metadata. + +2.3 What about other web font formats such as EOT/EOTLite/CWT/etc.? +In most cases these formats alter the original font data more than WOFF, and do not completely support appropriate metadata, so their use must be considered modification and RFNs may not be used. However, there may be certain formats or usage scenarios that may allow the use of RFNs. See http://scripts.sil.org/OFL_web_fonts_and_RFNs + +2.4 Can I make OFL fonts available through web font online services? +Yes, you are welcome to include OFL fonts in online web font services as long as you properly meet all the conditions of the license. The origin and open status of the font should be clear among the other fonts you are hosting. Authorship, copyright notices and license information must be sufficiently visible to your users or subscribers so they know where the font comes from and the rights granted by the author(s). Make sure the font file contains the needed copyright notice(s) and licensing information in its metadata. Please double-check the accuracy of every field to prevent contradictory information. Other font formats, including EOT/EOTLite/CWT and superior alternatives like WOFF, already provide fields for this information. Remember that if you modify the font within your library or convert it to another format for any reason the OFL restrictions apply and you need to change the names accordingly. Please respect the author's wishes as expressed in the OFL and do not misrepresent original designers and their work. Don't lump quality open fonts together with dubious freeware or public domain fonts. Consider how you can best work with the original designers and foundries, support their efforts and generate goodwill that will benefit your service. (See 1.17 for details related to URL-based access restrictions methods or DRM mechanisms). + +2.5 Some web font formats and services provide ways of "optimizing" the font for a particular website or web application; is that allowed? +Yes, it is permitted, but remember that these optimized versions are Modified Versions and so must follow OFL requirements like appropriate renaming. Also you need to bear in mind the other important parameters beyond compression, speed and responsiveness: you need to consider the audience of your particular website or web application, as choosing some optimization parameters may turn out to be less than ideal for them. Subsetting by removing certain glyphs or features may seriously limit functionality of the font in various languages that your users expect. It may also introduce degradation of quality in the rendering or specific bugs on the various target platforms compared to the original font from upstream. In other words, remember that one person's optimized font may be another person's missing feature. Various advanced typographic features (OpenType, Graphite or AAT) are also available through CSS and may provide the desired effects without the need to modify the font. + +2.6 Is subsetting a web font considered modification? +Yes. Removing any parts of the font when delivering a web font to a browser, including unused glyphs and smart font code, is considered modification. This is permitted by the OFL but would not normally allow the use of RFNs. Some newer subsetting technologies may be able to subset in a way that allows users to effectively have access to the complete font, including smart font behaviour. See 2.8 and http://scripts.sil.org/OFL_web_fonts_and_RFNs + +2.7 Are there any situations in which a modified web font could use RFNs? +Yes. If a web font is optimized only in ways that preserve Functional Equivalence (see 2.8), then it may use RFNs, as it reasonably represents the Original Version and respects the intentions of the author(s) and the main purposes of the RFN mechanism (avoids collisions, protects authors, minimizes support, encourages derivatives). However this is technically very difficult and often impractical, so a much better scenario is for the web font service or provider to sign a separate agreement with the author(s) that allows the use of RFNs for Modified Versions. + +2.8 How do you know if an optimization to a web font preserves Functional Equivalence? +Functional Equivalence is described in full in the 'Web fonts and RFNs' paper at http://scripts.sil.org/OFL_web_fonts_and_RFNs, in general, an optimized font is deemed to be Functionally Equivalent (FE) to the Original Version if it: + +- Supports the same full character inventory. If a character can be properly displayed using the Original Version, then that same character, encoded correctly on a web page, will display properly. +- Provides the same smart font behavior. Any dynamic shaping behavior that works with the Original Version should work when optimized, unless the browser or environment does not support it. There does not need to be guaranteed support in the client, but there should be no forced degradation of smart font or shaping behavior, such as the removal or obfuscation of OpenType, Graphite or AAT tables. +- Presents text with no obvious degradation in visual quality. The lettershapes should be equally (or more) readable, within limits of the rendering platform. +- Preserves original author, project and license metadata. At a minimum, this should include: Copyright and authorship; The license as stated in the Original Version, whether that is the full text of the OFL or a link to the web version; Any RFN declarations; Information already present in the font or documentation that points back to the Original Version, such as a link to the project or the author's website. + +If an optimized font meets these requirements, and so is considered to be FE, then it's very likely that the original author would feel that the optimized font is a good and reasonable equivalent. If it falls short of any of these requirements, the optimized font does not reasonably represent the Original Version, and so should be considered to be a Modified Version. Like other Modified Versions, it would not be allowed to use any RFNs and you simply need to pick your own font name. + +2.9 Isn't use of web fonts another form of embedding? +No. Unlike embedded fonts in a PDF, web fonts are not an integrated part of the document itself. They are not specific to a single document and are often applied to thousands of documents around the world. The font data is not stored alongside the document data and often originates from a different location. The ease by which the web fonts used by a document may be identified and downloaded for desktop use demonstrates that they are philosophically and technically separate from the web pages that specify them. See http://scripts.sil.org/OFL_web_fonts_and_RFNs + +2.10 So would it be better to not use RFNs at all if you want your font to be distributed by a web fonts service? +No. Although the OFL does not require authors to use RFNs, the RFN mechanism is an important part of the OFL model and completely compatible with web font services. If that web font service modifies the fonts, then the best solution is to sign a separate agreement for the use of any RFNs. It is perfectly valid for an author to not declare any RFNs, but before they do so they need to fully understand the benefits they are giving up, and the overall negative effect of allowing many different versions bearing the same name to be widely distributed. As a result, we don't generally recommend it. + +2.11 What should an agreement for the use of RFNs say? Are there any examples? +There is no prescribed format for this agreement, as legal systems vary, and no recommended examples. Authors may wish to add specific clauses to further restrict use, require author review of Modified Versions, establish user support mechanisms or provide terms for ending the agreement. Such agreements are usually not public, and apply only to the main parties. However, it would be very beneficial for web font services to clearly state when they have established such agreements, so that the public understands clearly that their service is operating appropriately. + +See the separate paper on 'Web Fonts & RFNs' for in-depth discussion of issues related to the use of RFNs for web fonts. This is available at http://scripts.sil.org/OFL_web_fonts_and_RFNs + + +3 MODIFYING OFL-LICENSED FONTS + +3.1 Can I change the fonts? Are there any limitations to what things I can and cannot change? +You are allowed to change anything, as long as such changes do not violate the terms of the license. In other words, you are not allowed to remove the copyright statement(s) from the font, but you could put additional information into it that covers your contribution. See the placeholders in the OFL header template for recommendations on where to add your own statements. (Remember that, when authors have reserved names via the RFN mechanism, you need to change the internal names of the font to your own font name when making your modified version even if it is just a small change.) + +3.2 I have a font that needs a few extra glyphs - can I take them from an OFL licensed font and copy them into mine? +Yes, but if you distribute that font to others it must be under the OFL, and include the information mentioned in condition 2 of the license. + +3.3 Can I charge people for my additional work? In other words, if I add a bunch of special glyphs or OpenType/Graphite/AAT code, can I sell the enhanced font? +Not by itself. Derivative fonts must be released under the OFL and cannot be sold by themselves. It is permitted, however, to include them in a larger software package (such as text editors, office suites or operating systems), even if the larger package is sold. In that case, you are strongly encouraged, but not required, to also make that derived font easily and freely available outside of the larger package. + +3.4 Can I pay someone to enhance the fonts for my use and distribution? +Yes. This is a good way to fund the further development of the fonts. Keep in mind, however, that if the font is distributed to others it must be under the OFL. You won't be able to recover your investment by exclusively selling the font, but you will be making a valuable contribution to the community. Please remember how you have benefited from the contributions of others. + +3.5 I need to make substantial revisions to the font to make it work with my program. It will be a lot of work, and a big investment, and I want to be sure that it can only be distributed with my program. Can I restrict its use? +No. If you redistribute a Modified Version of the font it must be under the OFL. You may not restrict it in any way beyond what the OFL permits and requires. This is intended to ensure that all released improvements to the fonts become available to everyone. But you will likely get an edge over competitors by being the first to distribute a bundle with the enhancements. Again, please remember how you have benefited from the contributions of others. + +3.6 Do I have to make any derivative fonts (including extended source files, build scripts, documentation, etc.) publicly available? +No, but please consider sharing your improvements with others. You may find that you receive in return more than what you gave. + +3.7 If a trademark is claimed in the OFL font, does that trademark need to remain in modified fonts? +Yes. Any trademark notices must remain in any derivative fonts to respect trademark laws, but you may add any additional trademarks you claim, officially registered or not. For example if an OFL font called "Foo" contains a notice that "Foo is a trademark of Acme", then if you rename the font to "Bar" when creating a Modified Version, the new trademark notice could say "Foo is a trademark of Acme Inc. - Bar is a trademark of Roadrunner Technologies Ltd.". Trademarks work alongside the OFL and are not subject to the terms of the licensing agreement. The OFL does not grant any rights under trademark law. Bear in mind that trademark law varies from country to country and that there are no international trademark conventions as there are for copyright. You may need to significantly invest in registering and defending a trademark for it to remain valid in the countries you are interested in. This may be costly for an individual independent designer. + +3.8 If I commit changes to a font (or publish a branch in a DVCS) as part of a public open source software project, do I have to change the internal font names? +Only if there are declared RFNs. Making a public commit or publishing a public branch is effectively redistributing your modifications, so any change to the font will require that you do not use the RFNs. Even if there are no RFNs, it may be useful to change the name or add a suffix indicating that a particular version of the font is still in development and not released yet. This will clearly indicate to users and fellow designers that this particular font is not ready for release yet. See section 5 for more details. + + +4 LICENSING YOUR ORIGINAL FONTS UNDER THE OFL + +4.1 Can I use the SIL OFL for my own fonts? +Yes! We heartily encourage everyone to use the OFL to distribute their own original fonts. It is a carefully constructed license that allows great freedom along with enough artistic integrity protection for the work of the authors as well as clear rules for other contributors and those who redistribute the fonts. The licensing model is used successfully by various organisations, both for-profit and not-for-profit, to release fonts of varying levels of scope and complexity. + +4.2 What do I have to do to apply the OFL to my font? +If you want to release your fonts under the OFL, we recommend you do the following: + +4.2.1 Put your copyright and Reserved Font Names information at the beginning of the main OFL.txt file in place of the dedicated placeholders (marked with the <> characters). Include this file in your release package. + +4.2.2 Put your copyright and the OFL text with your chosen Reserved Font Name(s) into your font files (the copyright and license fields). A link to the OFL text on the OFL web site is an acceptable (but not recommended) alternative. Also add this information to any other components (build scripts, glyph databases, documentation, test files, etc). Accurate metadata in your font files is beneficial to you as an increasing number of applications are exposing this information to the user. For example, clickable links can bring users back to your website and let them know about other work you have done or services you provide. Depending on the format of your fonts and sources, you can use template human-readable headers or machine-readable metadata. You should also double-check that there is no conflicting metadata in the font itself contradicting the license, such as the fstype bits in the os2 table or fields in the name table. + +4.2.3 Write an initial FONTLOG.txt for your font and include it in the release package (see Section 6 and Appendix A for details including a template). + +4.2.4 Include the relevant practical documentation on the license by adding the current OFL-FAQ.txt file in your package. + +4.2.5 If you wish you can use the OFL graphics (http://scripts.sil.org/OFL_logo) on your website. + +4.3 Will you make my font OFL for me? +We won't do the work for you. We can, however, try to answer your questions, unfortunately we do not have the resources to review and check your font packages for correct use of the OFL. We recommend you turn to designers, foundries or consulting companies with experience in doing open font design to provide this service to you. + +4.4 Will you distribute my OFL font for me? +No, although if the font is of sufficient quality and general interest we may include a link to it on our partial list of OFL fonts on the OFL web site. You may wish to consider other open font catalogs or hosting services, such as the Unifont Font Guide (http://unifont.org/fontguide), The League of Movable Type (http://theleagueofmovabletype.com) or the Open Font Library (http://openfontlibrary.org/), which despite the name has no direct relationship to the OFL or SIL. We do not endorse any particular catalog or hosting service - it is your responsibility to determine if the service is right for you and if it treats authors with fairness. + +4.5 Why should I use the OFL for my fonts? +- to meet needs for fonts that can be modified to support lesser-known languages +- to provide a legal and clear way for people to respect your work but still use it (and reduce piracy) +- to involve others in your font project +- to enable your fonts to be expanded with new weights and improved writing system/language support +- to allow more technical font developers to add features to your design (such as OpenType, Graphite or AAT support) +- to renew the life of an old font lying on your hard drive with no business model +- to allow your font to be included in Libre Software operating systems like Ubuntu +- to give your font world status and wide, unrestricted distribution +- to educate students about quality typeface and font design +- to expand your test base and get more useful feedback +- to extend your reach to new markets when users see your metadata and go to your website +- to get your font more easily into one of the web font online services +- to attract attention for your commercial fonts +- to make money through web font services +- to make money by bundling fonts with applications +- to make money adjusting and extending existing open fonts +- to get a better chance that foundations/NGOs/charities/companies who commission fonts will pick you +- to be part of a sharing design and development community +- to give back and contribute to a growing body of font sources + + +5 CHOOSING RESERVED FONT NAMES + +5.1 What are Reserved Font Names? +These are font names, or portions of font names, that the author has chosen to reserve for use only with the Original Version of the font, or for Modified Version(s) created by the original author. + +5.2 Why can't I use the Reserved Font Names in my derivative font names? I'd like people to know where the design came from. +The best way to acknowledge the source of the design is to thank the original authors and any other contributors in the files that are distributed with your revised font (although no acknowledgement is required). The FONTLOG is a natural place to do this. Reserved Font Names ensure that the only fonts that have the original names are the unmodified Original Versions. This allows designers to maintain artistic integrity while allowing collaboration to happen. It eliminates potential confusion and name conflicts. When choosing a name, be creative and avoid names that reuse almost all the same letters in the same order or sound like the original. It will help everyone if Original Versions and Modified Versions can easily be distinguished from one another and from other derivatives. Any substitution and matching mechanism is outside the scope of the license. + +5.3 What do you mean by "primary name as presented to the user"? Are you referring to the font menu name? +Yes, this applies to the font menu name and other mechanisms that specify a font in a document. It would be fine, however, to keep a text reference to the original fonts in the description field, in your modified source file or in documentation provided alongside your derivative as long as no one could be confused that your modified source is the original. But you cannot use the Reserved Font Names in any way to identify the font to the user (unless the Copyright Holder(s) allow(s) it through a separate agreement). Users who install derivatives (Modified Versions) on their systems should not see any of the original Reserved Font Names in their font menus, for example. Again, this is to ensure that users are not confused and do not mistake one font for another and so expect features only another derivative or the Original Version can actually offer. + +5.4 Am I not allowed to use any part of the Reserved Font Names? +You may not use individual words from the Reserved Font Names, but you would be allowed to use parts of words, as long as you do not use any word from the Reserved Font Names entirely. We do not recommend using parts of words because of potential confusion, but it is allowed. For example, if "Foobar" was a Reserved Font Name, you would be allowed to use "Foo" or "bar", although we would not recommend it. Such an unfortunate choice would confuse the users of your fonts as well as make it harder for other designers to contribute. + +5.5 So what should I, as an author, identify as Reserved Font Names? +Original authors are encouraged to name their fonts using clear, distinct names, and only declare the unique parts of the name as Reserved Font Names. For example, the author of a font called "Foobar Sans" would declare "Foobar" as a Reserved Font Name, but not "Sans", as that is a common typographical term, and may be a useful word to use in a derivative font name. Reserved Font Names should also be single words for simplicity and legibility. A font called "Flowing River" should have Reserved Font Names "Flowing" and "River", not "Flowing River". You also need to be very careful about reserving font names which are already linked to trademarks (whether registered or not) which you do not own. + +5.6 Do I, as an author, have to identify any Reserved Font Names? +No. RFNs are optional and not required, but we encourage you to use them. This is primarily to avoid confusion between your work and Modified Versions. As an author you can release a font under the OFL and not declare any Reserved Font Names. There may be situations where you find that using no RFNs and letting your font be changed and modified - including any kind of modification - without having to change the original name is desirable. However you need to be fully aware of the consequences. There will be no direct way for end-users and other designers to distinguish your Original Version from many Modified Versions that may be created. You have to trust whoever is making the changes and the optimizations to not introduce problematic changes. The RFNs you choose for your own creation have value to you as an author because they allow you to maintain artistic integrity and keep some control over the distribution channel to your end-users. For discussion of RFNs and web fonts see section 2. + +5.7 Are any names (such as the main font name) reserved by default? +No. That is a change to the license as of version 1.1. If you want any names to be Reserved Font Names, they must be specified after the copyright statement(s). + +5.8 Is there any situation in which I can use Reserved Font Names for a Modified Version? +The Copyright Holder(s) can give certain trusted parties the right to use any of the Reserved Font Names through separate written agreements. For example, even if "Foobar" is a RFN, you could write up an agreement to give company "XYZ" the right to distribute a modified version with a name that includes "Foobar". This allows for freedom without confusion. The existence of such an agreement should be made as clear as possible to downstream users and designers in the distribution package and the relevant documentation. They need to know if they are a party to the agreement or not and what they are practically allowed to do or not even if all the details of the agreement are not public. + +5.9 Do font rebuilds require a name change? Do I have to change the name of the font when my packaging workflow includes a full rebuild from source? +Yes, all rebuilds which change the font data and the smart code are Modified Versions and the requirements of the OFL apply: you need to respect what the Author(s) have chosen in terms of Reserved Font Names. However if a package (or installer) is simply a wrapper or a compressed structure around the final font - leaving them intact on the inside - then no name change is required. Please get in touch with the author(s) and copyright holder(s) to inquire about the presence of font sources beyond the final font file(s) and the recommended build path. That build path may very well be non-trivial and hard to reproduce accurately by the maintainer. If a full font build path is made available by the upstream author(s) please be aware that any regressions and changes you may introduce when doing a rebuild for packaging purposes is your own responsibility as a package maintainer since you are effectively creating a separate branch. You should make it very clear to your users that your rebuilt version is not the canonical one from upstream. + +5.10 Can I add other Reserved Font Names when making a derivative font? +Yes. List your additional Reserved Font Names after your additional copyright statement, as indicated with example placeholders at the top of the OFL.txt file. Be sure you do not remove any existing RFNs but only add your own. RFN statements should be placed next to the copyright statement of the relevant author as indicated in the OFL.txt template to make them visible to designers wishing to make their separate version. + + +6 ABOUT THE FONTLOG + +6.1 What is this FONTLOG thing exactly? +It has three purposes: 1) to provide basic information on the font to users and other designers and developers, 2) to document changes that have been made to the font or accompanying files, either by the original authors or others, and 3) to provide a place to acknowledge authors and other contributors. Please use it! + +6.2 Is the FONTLOG required? +It is not a requirement of the license, but we strongly recommend you have one. + +6.3 Am I required to update the FONTLOG when making Modified Versions? +No, but users, designers and other developers might get very frustrated with you if you don't. People need to know how derivative fonts differ from the original, and how to take advantage of the changes, or build on them. There are utilities that can help create and maintain a FONTLOG, such as the FONTLOG support in FontForge. + +6.4 What should the FONTLOG look like? +It is typically a separate text file (FONTLOG.txt), but can take other formats. It commonly includes these four sections: + +- brief header describing the FONTLOG itself and name of the font family +- Basic Font Information - description of the font family, purpose and breadth +- ChangeLog - chronological listing of changes +- Acknowledgements - list of authors and contributors with contact information + +It could also include other sections, such as: where to find documentation, how to make contributions, information on contributing organizations, source code details, and a short design guide. See Appendix A for an example FONTLOG. + + +7 MAKING CONTRIBUTIONS TO OFL PROJECTS + +7.1 Can I contribute work to OFL projects? +In many cases, yes. It is common for OFL fonts to be developed by a team of people who welcome contributions from the wider community. Contact the original authors for specific information on how to participate in their projects. + +7.2 Why should I contribute my changes back to the original authors? +It would benefit many people if you contributed back in response to what you've received. Your contributions and improvements to the fonts and other components could be a tremendous help and would encourage others to contribute as well and 'give back'. You will then benefit from other people's contributions as well. Sometimes maintaining your own separate version takes more effort than merging back with the original. Be aware that any contributions, however, must be either your own original creation or work that you own, and you may be asked to affirm that clearly when you contribute. + +7.3 I've made some very nice improvements to the font. Will you consider adopting them and putting them into future Original Versions? +Most authors would be very happy to receive such contributions. Keep in mind that it is unlikely that they would want to incorporate major changes that would require additional work on their end. Any contributions would likely need to be made for all the fonts in a family and match the overall design and style. Authors are encouraged to include a guide to the design with the fonts. It would also help to have contributions submitted as patches or clearly marked changes - the use of smart source revision control systems like subversion, mercurial, git or bzr is a good idea. Please follow the recommendations given by the author(s) in terms of preferred source formats and configuration parameters for sending contributions. If this is not indicated in a FONTLOG or other documentation of the font, consider asking them directly. Examples of useful contributions are bug fixes, additional glyphs, stylistic alternates (and the smart font code to access them) or improved hinting. Keep in mind that some kinds of changes (esp. hinting) may be technically difficult to integrate. + +7.4 How can I financially support the development of OFL fonts? +It is likely that most authors of OFL fonts would accept financial contributions - contact them for instructions on how to do this. Such contributions would support future development. You can also pay for others to enhance the fonts and contribute the results back to the original authors for inclusion in the Original Version. + + +8 ABOUT THE LICENSE ITSELF + +8.1 I see that this is version 1.1 of the license. Will there be later changes? +Version 1.1 is the first minor revision of the OFL. We are confident that version 1.1 will meet most needs, but are open to future improvements. Any revisions would be for future font releases, and previously existing licenses would remain in effect. No retroactive changes are possible, although the Copyright Holder(s) can re-release the font under a revised OFL. All versions will be available on our web site: http://scripts.sil.org/OFL. + +8.2 Does this license restrict the rights of the Copyright Holder(s)? +No. The Copyright Holder(s) still retain(s) all the rights to their creation; they are only releasing a portion of it for use in a specific way. For example, the Copyright Holder(s) may choose to release a 'basic' version of their font under the OFL, but sell a restricted 'enhanced' version under a different license. They may also choose to release the same font under both the OFL and some other license. Only the Copyright Holder(s) can do this, and doing so does not change the terms of the OFL as it applies to that font. + +8.3 Is the OFL a contract or a license? +The OFL is a worldwide license based on international copyright agreements and conventions. It is not a contract and so does not require you to sign it to have legal validity. By using, modifying and redistributing components under the OFL you indicate that you accept the license. + +8.4 I really like the terms of the OFL, but want to change it a little. Am I allowed to take ideas and actual wording from the OFL and put them into my own custom license for distributing my fonts? +We strongly recommend against creating your very own unique open licensing model. Using a modified or derivative license will likely cut you off - along with the font(s) under that license - from the community of designers using the OFL, potentially expose you and your users to legal liabilities, and possibly put your work and rights at risk. The OFL went though a community and legal review process that took years of effort, and that review is only applicable to an unmodified OFL. The text of the OFL has been written by SIL (with review and consultation from the community) and is copyright (c) 2005-2013 SIL International. You may re-use the ideas and wording (in part, not in whole) in another non-proprietary license provided that you call your license by another unambiguous name, that you do not use the preamble, that you do not mention SIL and that you clearly present your license as different from the OFL so as not to cause confusion by being too similar to the original. If you feel the OFL does not meet your needs for an open license, please contact us. + +8.5 Can I translate the license and the FAQ into other languages? +SIL certainly recognises the need for people who are not familiar with English to be able to understand the OFL and its use. Making the license very clear and readable has been a key goal for the OFL, but we know that people understand their own language best. + +If you are an experienced translator, you are very welcome to translate the OFL and OFL-FAQ so that designers and users in your language community can understand the license better. But only the original English version of the license has legal value and has been approved by the community. Translations do not count as legal substitutes and should only serve as a way to explain the original license. SIL - as the author and steward of the license for the community at large - does not approve any translation of the OFL as legally valid because even small translation ambiguities could be abused and create problems. + +SIL gives permission to publish unofficial translations into other languages provided that they comply with the following guidelines: + +- Put the following disclaimer in both English and the target language stating clearly that the translation is unofficial: + +"This is an unofficial translation of the SIL Open Font License into <language_name>. It was not published by SIL International, and does not legally state the distribution terms for fonts that use the OFL. A release under the OFL is only valid when using the original English text. However, we recognize that this unofficial translation will help users and designers not familiar with English to better understand and use the OFL. We encourage designers who consider releasing their creation under the OFL to read the OFL-FAQ in their own language if it is available. Please go to http://scripts.sil.org/OFL for the official version of the license and the accompanying OFL-FAQ." + +- Keep your unofficial translation current and update it at our request if needed, for example if there is any ambiguity which could lead to confusion. + +If you start such a unofficial translation effort of the OFL and OFL-FAQ please let us know. + +8.6 Does the OFL have an explicit expiration term? +No, the implicit intent of the OFL is that the permissions granted are perpetual and irrevocable. + + +9 ABOUT SIL INTERNATIONAL + +9.1 Who is SIL International and what do they do? +SIL serves language communities worldwide, building their capacity for sustainable language development, by means of research, translation, training and materials development. SIL makes its services available to all without regard to religious belief, political ideology, gender, race, or ethnic background. SIL's members and volunteers share a Christian commitment. + +9.2 What does this have to do with font licensing? +The ability to read, write, type and publish in one's own language is one of the most critical needs for millions of people around the world. This requires fonts that are widely available and support lesser-known languages. SIL develops - and encourages others to develop - a complete stack of writing systems implementation components available under open licenses. This open stack includes input methods, smart fonts, smart rendering libraries and smart applications. There has been a need for a common open license that is specifically applicable to fonts and related software (a crucial component of this stack), so SIL developed the SIL Open Font License with the help of the Free/Libre and Open Source Software community. + +9.3 How can I contact SIL? +Our main web site is: http://www.sil.org/ +Our site about complex scripts is: http://scripts.sil.org/ +Information about this license (and contact information) is at: http://scripts.sil.org/OFL + + +APPENDIX A - FONTLOG EXAMPLE + +Here is an example of the recommended format for a FONTLOG, although other formats are allowed. + +----- +FONTLOG for the GlobalFontFamily fonts + +This file provides detailed information on the GlobalFontFamily Font Software. This information should be distributed along with the GlobalFontFamily fonts and any derivative works. + +Basic Font Information + +GlobalFontFamily is a Unicode typeface family that supports all languages that use the Latin script and its variants, and could be expanded to support other scripts. + +NewWorldFontFamily is based on the GlobalFontFamily and also supports Greek, Hebrew, Cyrillic and Armenian. + +More specifically, this release supports the following Unicode ranges... +This release contains... +Documentation can be found at... +To contribute to the project... + +ChangeLog + +10 December 2010 (Fred Foobar) GlobalFontFamily-devel version 1.4 +- fix new build and testing system (bug #123456) + +1 August 2008 (Tom Parker) GlobalFontFamily version 1.2.1 +- Tweaked the smart font code (Branch merged with trunk version) +- Provided improved build and debugging environment for smart behaviours + +7 February 2007 (Pat Johnson) NewWorldFontFamily Version 1.3 +- Added Greek and Cyrillic glyphs + +7 March 2006 (Fred Foobar) NewWorldFontFamily Version 1.2 +- Tweaked contextual behaviours + +1 Feb 2005 (Jane Doe) NewWorldFontFamily Version 1.1 +- Improved build script performance and verbosity +- Extended the smart code documentation +- Corrected minor typos in the documentation +- Fixed position of combining inverted breve below (U+032F) +- Added OpenType/Graphite smart code for Armenian +- Added Armenian glyphs (U+0531 -> U+0587) +- Released as "NewWorldFontFamily" + +1 Jan 2005 (Joe Smith) GlobalFontFamily Version 1.0 +- Initial release + +Acknowledgements + +If you make modifications be sure to add your name (N), email (E), web-address (if you have one) (W) and description (D). This list is in alphabetical order. + +N: Jane Doe +E: jane@university.edu +W: http://art.university.edu/projects/fonts +D: Contributor - Armenian glyphs and code + +N: Fred Foobar +E: fred@foobar.org +W: http://foobar.org +D: Contributor - misc Graphite fixes + +N: Pat Johnson +E: pat@fontstudio.org +W: http://pat.fontstudio.org +D: Designer - Greek & Cyrillic glyphs based on Roman design + +N: Tom Parker +E: tom@company.com +W: http://www.company.com/tom/projects/fonts +D: Engineer - original smart font code + +N: Joe Smith +E: joe@fontstudio.org +W: http://joe.fontstudio.org +D: Designer - original Roman glyphs + +Fontstudio.org is an not-for-profit design group whose purpose is... +Foobar.org is a distributed community of developers... +Company.com is a small business who likes to support community designers... +University.edu is a renowned educational institution with a strong design department... +----- + diff --git a/text/OFL.txt b/text/OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2003-2014 SIL International (http://www.sil.org/), +with Reserved Font Names "Gentium" and "SIL". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/text/README b/text/README @@ -0,0 +1,5 @@ +khash.h: khint32_t is defined in terms of either unsigned int or unsigned long - +I hope this really works properly because it *must* be 32 bits for combining +16 bit font id and 16 bit font size into one number + +Possibly add code as fallback for uint8_t, etc. if not implemented in stdint.h? diff --git a/text/README.txt b/text/README.txt @@ -0,0 +1,88 @@ +README +Gentium Plus +======================== + +Thank you for your interest in the Gentium Plus fonts. +We hope you find them useful! + +Gentium Plus supports a wide range of Latin, Greek and Cyrillic +characters. Documentation for the fonts is available on Gentium website +(http://scripts.sil.org/Gentium), including details on what ranges are +supported. + +Gentium Plus is released under the SIL Open Font License. + +See the OFL and OFL-FAQ for details of the SIL Open Font License. +See the FONTLOG for information on this and previous releases. +See the GENTIUM-FAQ for answers to common questions about the Gentium fonts +See the website (http://scripts.sil.org/Gentium) for further documentation. +See the SIL Unicode Roman FAQ (http://scripts.sil.org/ComplexRomanFontFAQ) +for frequently asked questions and their answers regarding SIL's Roman fonts. + + +TIPS +==== + +As this font is distributed at no cost, we are unable to provide a +commercial level of personal technical support. The font has, however, +been through some testing on various platforms to be sure it works in most +situations. In particular, it has been tested and shown to work on Windows +XP, Windows Vista and Windows 7. Graphite capabilities have been tested +on Graphite-supported platforms. + +If you do find a problem, please do report it to fonts@sil.org. +We can't guarantee any direct response, but will try to fix reported bugs in +future versions. Make sure you read through the +SIL Unicode Roman FAQ (http://scripts.sil.org/ComplexRomanFontFAQ). + +Many problems can be solved, or at least explained, through an understanding +of the encoding and use of the fonts. Here are some basic hints: + +Encoding: +The fonts are encoded according to Unicode, so your application must support +Unicode text in order to access letters other than the standard alphabet. +Most Windows applications provide basic Unicode support. You will, however, +need some way of entering Unicode text into your document. + +Keyboarding: +This font does not include any keyboarding helps or utilities. It uses the +built-in keyboards of the operating system. You will need to install the +appropriate keyboard and input method for the characters of the language you +wish to use. If you want to enter characters that are not supported by any +system keyboard, the Keyman program (www.tavultesoft.com) can be helpful +on Windows systems. Also available for Windows is MSKLC +(http://www.microsoft.com/globaldev/tools/msklc.mspx). +For Linux systems such as Ubuntu, KMFL (http://kmfl.sourceforge.net/) +is available. Ukelele (http://scripts.sil.org/ukelele) is available for +Mac OS X versions 10.2 and later. + +For other platforms, KMFL (http://kmfl.sourceforge.net/), +XKB (http://www.x.org/wiki/XKB) or Ukelele (http://scripts.sil.org/ukelele) +can be helpful. + +If you want to enter characters that are not supported by any system +keyboard, and to access the full Unicode range, we suggest you use +gucharmap, kcharselect on Ubuntu or similar software. + +Another method of entering some symbols is provided by a few applications such +as Adobe InDesign or OpenOffice.org. They can display a glyph palette or input +dialog that shows all the glyphs (symbols) in a font and allow you to enter +them by clicking on the glyph you want. + +Rendering: +This font is designed to work with Graphite or Opentype advanced font +technologies. To take advantage of the advanced typographic +capabilities of this font, you must be using applications that provide an +adequate level of support for Graphite or OpenType. See "Applications +that provide an adequate level of support for SIL Unicode Roman fonts" +(http://scripts.sil.org/Complex_AdLvSup). + + +CONTACT +======== +For more information please visit the Gentium page on SIL International's +Computers and Writing systems website: +http://scripts.sil.org/Gentium + +Support through the website: http://scripts.sil.org/Support + diff --git a/text/bob.c b/text/bob.c @@ -0,0 +1,28 @@ +#include <graphite2/Segment.h> +#include <stdio.h> + +int main(int argc, char *argv[]) +{ + int rtl = 0; /*are we rendering right to left? probably not */ + int pointsize = 12; /*point size in points */ + int dpi = 96; /*work with this many dots per inch */ + char *pError; /*location of faulty utf-8 */ + gr_font *font = NULL; + size_t numCodePoints = 0; + gr_segment *seg = NULL; + const gr_slot *s; + gr_face *face = gr_make_file_face(argv[1], 0); + if (!face) return 1; + font = gr_make_font(pointsize * dpi / 72.0f, face); + if (!font) return 2; + numCodePoints = gr_count_unicode_characters(gr_utf8, argv[2], NULL, (const void **)(&pError)); + if (pError) return 3; + seg = gr_make_seg(font, face, 0, 0, gr_utf8, argv[2], numCodePoints, rtl); + if (!seg) return 3; + for (s = gr_seg_first_slot(seg); s; s = gr_slot_next_in_segment(s)) + printf("%d %d(%f,%f) ", gr_slot_gid(s), gr_slot_origin_X(s), gr_slot_origin_Y(s)); + gr_seg_destroy(seg); + gr_font_destroy(font); + gr_face_destroy(face); + return 0; +} diff --git a/text/documentation/AwamiNastaliq-Features.odt b/text/documentation/AwamiNastaliq-Features.odt Binary files differ. diff --git a/text/documentation/AwamiNastaliq-Features.pdf b/text/documentation/AwamiNastaliq-Features.pdf Binary files differ. diff --git a/text/documentation/AwamiNastaliq-TypeSample.odt b/text/documentation/AwamiNastaliq-TypeSample.odt Binary files differ. diff --git a/text/documentation/AwamiNastaliq-TypeSample.pdf b/text/documentation/AwamiNastaliq-TypeSample.pdf Binary files differ. diff --git a/text/documentation/DOCUMENTATION.txt b/text/documentation/DOCUMENTATION.txt @@ -0,0 +1,11 @@ +DOCUMENTATION +Gentium Plus +======================== + + +This file describes the documentation files included with the Gentium Plus font family. This information should be distributed along with the Gentium Plus fonts and any derivative works. + +Primary documentation for the Gentium Plus font family is on the Gentium website (http://scripts.sil.org/Gentium). + +The GentiumPlus-features.pdf file illustrates the use of OpenType and Graphite features. +The GentiumPlus-features.odt file illustrates the use of OpenType and Graphite features (source file for the pdf). diff --git a/text/documentation/GentiumPlus-features.odt b/text/documentation/GentiumPlus-features.odt Binary files differ. diff --git a/text/documentation/GentiumPlus-features.pdf b/text/documentation/GentiumPlus-features.pdf Binary files differ. diff --git a/text/font.ttf b/text/font.ttf Binary files differ. diff --git a/text/font1.ttf b/text/font1.ttf Binary files differ. diff --git a/text/font2.ttf b/text/font2.ttf Binary files differ. diff --git a/text/hashtest.c b/text/hashtest.c @@ -0,0 +1,27 @@ +#include <stdio.h> +#include "khash.h" + +KHASH_MAP_INIT_INT(m32, char) +KHASH_MAP_INIT_INT(32hash, khash_t(m32)*) + +int main(int argc, char *argv[]) +{ + int ret, is_missing; + khint_t k; + khash_t(m32) *h = kh_init(m32); + khash_t(32hash) *h1 = kh_init(32hash); + k = kh_put(m32, h, 5, &ret); + if (!ret) kh_del(m32, h, k); + kh_value(h, k) = 10; + + k = kh_put(32hash, h1, 20, &ret); + if (!ret) kh_del(32hash, h1, k); + kh_value(h1, k) = h; + + k = kh_get(32hash, h1, 20); + khash_t(m32) *bob = kh_value(h1, k); + k = kh_get(m32, bob, 5); + printf("%d\n", kh_value(bob, k)); + + return 0; +} diff --git a/text/khash.h b/text/khash.h @@ -0,0 +1,627 @@ +/* The MIT License + + Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +/* + An example: + +#include "khash.h" +KHASH_MAP_INIT_INT(32, char) +int main() { + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; +} +*/ + +/* + 2013-05-02 (0.2.8): + + * Use quadratic probing. When the capacity is power of 2, stepping function + i*(i+1)/2 guarantees to traverse each bucket. It is better than double + hashing on cache performance and is more robust than linear probing. + + In theory, double hashing should be more robust than quadratic probing. + However, my implementation is probably not for large hash tables, because + the second hash function is closely tied to the first hash function, + which reduce the effectiveness of double hashing. + + Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php + + 2011-12-29 (0.2.7): + + * Minor code clean up; no actual effect. + + 2011-09-16 (0.2.6): + + * The capacity is a power of 2. This seems to dramatically improve the + speed for simple keys. Thank Zilong Tan for the suggestion. Reference: + + - http://code.google.com/p/ulib/ + - http://nothings.org/computer/judy/ + + * Allow to optionally use linear probing which usually has better + performance for random input. Double hashing is still the default as it + is more robust to certain non-random input. + + * Added Wang's integer hash function (not used by default). This hash + function is more robust to certain non-random input. + + 2011-02-14 (0.2.5): + + * Allow to declare global functions. + + 2009-09-26 (0.2.4): + + * Improve portability + + 2008-09-19 (0.2.3): + + * Corrected the example + * Improved interfaces + + 2008-09-11 (0.2.2): + + * Improved speed a little in kh_put() + + 2008-09-10 (0.2.1): + + * Added kh_clear() + * Fixed a compiling error + + 2008-09-02 (0.2.0): + + * Changed to token concatenation which increases flexibility. + + 2008-08-31 (0.1.2): + + * Fixed a bug in kh_get(), which has not been tested previously. + + 2008-08-31 (0.1.1): + + * Added destructor +*/ + + +#ifndef __AC_KHASH_H +#define __AC_KHASH_H + +/*! + @header + + Generic hash table library. + */ + +#define AC_VERSION_KHASH_H "0.2.8" + +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +/* compiler specific configuration */ + +#if UINT_MAX == 0xffffffffu +typedef unsigned int khint32_t; +#elif ULONG_MAX == 0xffffffffu +typedef unsigned long khint32_t; +#endif + +#if ULONG_MAX == ULLONG_MAX +typedef unsigned long khint64_t; +#else +typedef unsigned long long khint64_t; +#endif + +#ifndef kh_inline +#ifdef _MSC_VER +#define kh_inline __inline +#else +#define kh_inline inline +#endif +#endif /* kh_inline */ + +#ifndef klib_unused +#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3) +#define klib_unused __attribute__ ((__unused__)) +#else +#define klib_unused +#endif +#endif /* klib_unused */ + +typedef khint32_t khint_t; +typedef khint_t khiter_t; + +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) + +#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) + +#ifndef kroundup32 +#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) +#endif + +#ifndef kcalloc +#define kcalloc(N,Z) calloc(N,Z) +#endif +#ifndef kmalloc +#define kmalloc(Z) malloc(Z) +#endif +#ifndef krealloc +#define krealloc(P,Z) realloc(P,Z) +#endif +#ifndef kfree +#define kfree(P) free(P) +#endif + +static const double __ac_HASH_UPPER = 0.77; + +#define __KHASH_TYPE(name, khkey_t, khval_t) \ + typedef struct kh_##name##_s { \ + khint_t n_buckets, size, n_occupied, upper_bound; \ + khint32_t *flags; \ + khkey_t *keys; \ + khval_t *vals; \ + } kh_##name##_t; + +#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ + extern kh_##name##_t *kh_init_##name(void); \ + extern void kh_destroy_##name(kh_##name##_t *h); \ + extern void kh_clear_##name(kh_##name##_t *h); \ + extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ + extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ + extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ + extern void kh_del_##name(kh_##name##_t *h, khint_t x); + +#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + SCOPE kh_##name##_t *kh_init_##name(void) { \ + return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ + } \ + SCOPE void kh_destroy_##name(kh_##name##_t *h) \ + { \ + if (h) { \ + kfree((void *)h->keys); kfree(h->flags); \ + kfree((void *)h->vals); \ + kfree(h); \ + } \ + } \ + SCOPE void kh_clear_##name(kh_##name##_t *h) \ + { \ + if (h && h->flags) { \ + memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ + h->size = h->n_occupied = 0; \ + } \ + } \ + SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ + { \ + if (h->n_buckets) { \ + khint_t k, i, last, mask, step = 0; \ + mask = h->n_buckets - 1; \ + k = __hash_func(key); i = k & mask; \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + i = (i + (++step)) & mask; \ + if (i == last) return h->n_buckets; \ + } \ + return __ac_iseither(h->flags, i)? h->n_buckets : i; \ + } else return 0; \ + } \ + SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ + { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ + khint32_t *new_flags = 0; \ + khint_t j = 1; \ + { \ + kroundup32(new_n_buckets); \ + if (new_n_buckets < 4) new_n_buckets = 4; \ + if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ + else { /* hash table size to be changed (shrink or expand); rehash */ \ + new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (!new_flags) return -1; \ + memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (h->n_buckets < new_n_buckets) { /* expand */ \ + khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (!new_keys) { kfree(new_flags); return -1; } \ + h->keys = new_keys; \ + if (kh_is_map) { \ + khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ + if (!new_vals) { kfree(new_flags); return -1; } \ + h->vals = new_vals; \ + } \ + } /* otherwise shrink */ \ + } \ + } \ + if (j) { /* rehashing is needed */ \ + for (j = 0; j != h->n_buckets; ++j) { \ + if (__ac_iseither(h->flags, j) == 0) { \ + khkey_t key = h->keys[j]; \ + khval_t val; \ + khint_t new_mask; \ + new_mask = new_n_buckets - 1; \ + if (kh_is_map) val = h->vals[j]; \ + __ac_set_isdel_true(h->flags, j); \ + while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ + khint_t k, i, step = 0; \ + k = __hash_func(key); \ + i = k & new_mask; \ + while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ + __ac_set_isempty_false(new_flags, i); \ + if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ + { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ + if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ + __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ + } else { /* write the element and jump out of the loop */ \ + h->keys[i] = key; \ + if (kh_is_map) h->vals[i] = val; \ + break; \ + } \ + } \ + } \ + } \ + if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ + h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ + } \ + kfree(h->flags); /* free the working space */ \ + h->flags = new_flags; \ + h->n_buckets = new_n_buckets; \ + h->n_occupied = h->size; \ + h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ + } \ + return 0; \ + } \ + SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ + { \ + khint_t x; \ + if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ + if (h->n_buckets > (h->size<<1)) { \ + if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ + *ret = -1; return h->n_buckets; \ + } \ + } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ + *ret = -1; return h->n_buckets; \ + } \ + } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ + { \ + khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ + x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ + if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ + else { \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + if (__ac_isdel(h->flags, i)) site = i; \ + i = (i + (++step)) & mask; \ + if (i == last) { x = site; break; } \ + } \ + if (x == h->n_buckets) { \ + if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ + else x = i; \ + } \ + } \ + } \ + if (__ac_isempty(h->flags, x)) { /* not present at all */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; ++h->n_occupied; \ + *ret = 1; \ + } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; \ + *ret = 2; \ + } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ + return x; \ + } \ + SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ + { \ + if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ + __ac_set_isdel_true(h->flags, x); \ + --h->size; \ + } \ + } + +#define KHASH_DECLARE(name, khkey_t, khval_t) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_PROTOTYPES(name, khkey_t, khval_t) + +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +/* --- BEGIN OF HASH FUNCTIONS --- */ + +/*! @function + @abstract Integer hash function + @param key The integer [khint32_t] + @return The hash value [khint_t] + */ +#define kh_int_hash_func(key) (khint32_t)(key) +/*! @function + @abstract Integer comparison function + */ +#define kh_int_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract 64-bit integer hash function + @param key The integer [khint64_t] + @return The hash value [khint_t] + */ +#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) +/*! @function + @abstract 64-bit integer comparison function + */ +#define kh_int64_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract const char* hash function + @param s Pointer to a null terminated string + @return The hash value + */ +static kh_inline khint_t __ac_X31_hash_string(const char *s) +{ + khint_t h = (khint_t)*s; + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; + return h; +} +/*! @function + @abstract Another interface to const char* hash function + @param key Pointer to a null terminated string [const char*] + @return The hash value [khint_t] + */ +#define kh_str_hash_func(key) __ac_X31_hash_string(key) +/*! @function + @abstract Const char* comparison function + */ +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) + +static kh_inline khint_t __ac_Wang_hash(khint_t key) +{ + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} +#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key) + +/* --- END OF HASH FUNCTIONS --- */ + +/* Other convenient macros... */ + +/*! + @abstract Type of the hash table. + @param name Name of the hash table [symbol] + */ +#define khash_t(name) kh_##name##_t + +/*! @function + @abstract Initiate a hash table. + @param name Name of the hash table [symbol] + @return Pointer to the hash table [khash_t(name)*] + */ +#define kh_init(name) kh_init_##name() + +/*! @function + @abstract Destroy a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_destroy(name, h) kh_destroy_##name(h) + +/*! @function + @abstract Reset a hash table without deallocating memory. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_clear(name, h) kh_clear_##name(h) + +/*! @function + @abstract Resize a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param s New size [khint_t] + */ +#define kh_resize(name, h, s) kh_resize_##name(h, s) + +/*! @function + @abstract Insert a key to the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @param r Extra return code: -1 if the operation failed; + 0 if the key is present in the hash table; + 1 if the bucket is empty (never used); 2 if the element in + the bucket has been deleted [int*] + @return Iterator to the inserted element [khint_t] + */ +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) + +/*! @function + @abstract Retrieve a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] + */ +#define kh_get(name, h, k) kh_get_##name(h, k) + +/*! @function + @abstract Remove a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Iterator to the element to be deleted [khint_t] + */ +#define kh_del(name, h, k) kh_del_##name(h, k) + +/*! @function + @abstract Test whether a bucket contains data. + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return 1 if containing data; 0 otherwise [int] + */ +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) + +/*! @function + @abstract Get key given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Key [type of keys] + */ +#define kh_key(h, x) ((h)->keys[x]) + +/*! @function + @abstract Get value given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Value [type of values] + @discussion For hash sets, calling this results in segfault. + */ +#define kh_val(h, x) ((h)->vals[x]) + +/*! @function + @abstract Alias of kh_val() + */ +#define kh_value(h, x) ((h)->vals[x]) + +/*! @function + @abstract Get the start iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The start iterator [khint_t] + */ +#define kh_begin(h) (khint_t)(0) + +/*! @function + @abstract Get the end iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The end iterator [khint_t] + */ +#define kh_end(h) ((h)->n_buckets) + +/*! @function + @abstract Get the number of elements in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of elements in the hash table [khint_t] + */ +#define kh_size(h) ((h)->size) + +/*! @function + @abstract Get the number of buckets in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of buckets in the hash table [khint_t] + */ +#define kh_n_buckets(h) ((h)->n_buckets) + +/*! @function + @abstract Iterate over the entries in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param kvar Variable to which key will be assigned + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (kvar) = kh_key(h,__i); \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/*! @function + @abstract Iterate over the values in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach_value(h, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/* More conenient interfaces */ + +/*! @function + @abstract Instantiate a hash set containing integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT(name) \ + KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT(name, khval_t) \ + KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT64(name) \ + KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT64(name, khval_t) \ + KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) + +typedef const char *kh_cstr_t; +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_STR(name) \ + KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_STR(name, khval_t) \ + KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) + +#endif /* __AC_KHASH_H */ diff --git a/text/main.c b/text/main.c @@ -0,0 +1,200 @@ +/* By Justin Meiners (2013) */ +/* font and image utilties from nothings.org */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xos.h> + +/* is c the start of a utf8 sequence? */ +#define isutf(c) (((c)&0xC0)!=0x80) + +static const uint32_t offsetsFromUTF8[6] = { + 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL +}; + +/* next character without NUL character terminator */ +uint32_t u8_nextmemchar(const char *s, size_t *i) +{ + uint32_t ch = 0; + size_t sz = 0; + do { + ch <<= 6; + ch += (unsigned char)s[(*i)++]; + sz++; + } while (!isutf(s[*i])); + ch -= offsetsFromUTF8[sz-1]; + + return ch; +} + +void u8_dec(const char *s, size_t *i) +{ + (void)(isutf(s[--(*i)]) || isutf(s[--(*i)]) || isutf(s[--(*i)]) || --(*i)); +} + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */ + +Pixmap ltk_draw_text(const unsigned char *text, int line_height, XColor color, XColor background) +{ + /* Get width of text */ + int w; +} + +int main(int argc, const char * argv[]) +{ + + Display *display; + int screen; + Window window; + GC gc; + + unsigned long black, white; + Colormap colormap; + display = XOpenDisplay((char *)0); + screen = DefaultScreen(display); + colormap = DefaultColormap(display, screen); + black = BlackPixel(display, screen); + white = WhitePixel(display, screen); + window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 1366, 512, 0, black, white); + XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL); + XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask); + gc = XCreateGC(display, window, 0, 0); + XSetBackground(display, gc, black); + XSetForeground(display, gc, black); + XClearWindow(display, window); + XMapRaised(display, window); + + /* load font file */ + FILE *f; + long len; + char *file_contents; + f = fopen("font1.ttf", "rb"); + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + file_contents = malloc(len + 1); + fread(file_contents, 1, len, f); + file_contents[len] = '\0'; + fclose(f); + unsigned char *fontBuffer = file_contents; + + /* prepare font */ + stbtt_fontinfo info; + if (!stbtt_InitFont(&info, fontBuffer, 0)) + { + printf("failed\n"); + } + + int b_w = 1366; /* bitmap width */ + int b_h = 100; /* bitmap height */ + int l_h = 100; /* line height */ + + /* create a bitmap for the phrase */ + unsigned char *bitmap = malloc(b_w * b_h); + + /* calculate font scaling */ + float scale = stbtt_ScaleForPixelHeight(&info, l_h); + + //uint8_t *word = "ہمارے بارے میں"; + //char *word = "Hello"; + uint8_t *word = "sfsdf"; + + int x = 0; + + int ascent, descent, lineGap; + stbtt_GetFontVMetrics(&info, &ascent, &descent, &lineGap); + + ascent *= scale; + descent *= scale; + + Pixmap pix = XCreatePixmap(display, window, b_w, l_h, 24); + XColor green; + XParseColor(display, colormap, "#00FFFF", &green); + XAllocColor(display, colormap, &green); + XColor green1; + XParseColor(display, colormap, "#FF0000", &green1); + XAllocColor(display, colormap, &green1); + + size_t i = 0; + int length = strlen(word); + uint32_t target; + uint32_t target1; + unsigned char *bob; + int x1, y1, xoff, yoff; + while (i < length) + { + target = u8_nextmemchar(word, &i); + if (!target) break; + /* get bounding box for character (may be offset to account for chars that dip above or below the line */ + int c_x1, c_y1, c_x2, c_y2; + stbtt_GetCodepointBitmapBox(&info, target, scale, scale, &c_x1, &c_y1, &c_x2, &c_y2); + + /* compute y (different characters have different heights */ + int y = ascent + c_y1; + + /* render character (stride and offset is important here) */ + int byteOffset = x + (y * b_w); + bob = stbtt_GetCodepointBitmap(&info, scale, scale, target, &x1, &y1, &xoff, &yoff); +// stbtt_MakeCodepointBitmap(&info, bitmap + byteOffset, c_x2 - c_x1, c_y2 - c_y1, b_w, scale, scale, target); + + for (int i = 0; i < y1; i++) + { + for (int j = 0; j < x1; j++) + { + XSetForeground(display, gc, (bob[i * x1 + j] / 255.0) * black + ((255 - bob[i * x1 + j]) / 255.0) * white); + XDrawPoint(display, pix, gc, x + j, i); + } + } + + /* how wide is this character */ + int ax; + stbtt_GetCodepointHMetrics(&info, target, &ax, 0); + x += ax * scale; + + /* add kerning */ + target1 = u8_nextmemchar(word, &i); + if (!target1) break; + u8_dec(word, &i); + int kern; + kern = stbtt_GetCodepointKernAdvance(&info, target, target1); + x += kern * scale; + } + /* save out a 1 channel image */ + /*stbi_write_png("out.png", b_w, b_h, 1, bitmap, b_w);*/ + XSetForeground(display, gc, white); + /* + Pixmap pix = XCreatePixmapFromBitmapData(display, window, bitmap, b_w, b_h, white, black, 24); + */ + XCopyArea(display, pix, window, gc, 0, 0, b_w, b_h, 0, 0); + + XEvent event; + KeySym key; + char text[255]; + + while(1) + { + XNextEvent(display, &event); + if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) + { + XCopyArea(display, pix, window, gc, 0, 0, b_w, b_h, 0, 0); + if (text[0] == 'q') + { + XFreeGC(display, gc); + XFreeColormap(display, colormap); + XDestroyWindow(display, window); + XCloseDisplay(display); + exit(0); + } + } + } + + free(fontBuffer); + free(bitmap); + + return 0; +} diff --git a/text/main1.c b/text/main1.c @@ -0,0 +1,94 @@ +#include <stdio.h> +#include <stdlib.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xos.h> + +int main(int argc, char *argv[]) +{ + Display *display; + int screen; + Window window; + GC gc; + + unsigned long black, white; + XColor green; + Colormap colormap; + display = XOpenDisplay((char *)0); + screen = DefaultScreen(display); + colormap = DefaultColormap(display, screen); + black = BlackPixel(display, screen); + white = WhitePixel(display, screen); + XParseColor(display, colormap, "#00FF00", &green); + XAllocColor(display, colormap, &green); + window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 200, 300, 0, white, white); + XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL); + XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask); + gc = XCreateGC(display, window, 0, 0); + XSetBackground(display, gc, black); + XSetForeground(display, gc, black); + XClearWindow(display, window); + XMapRaised(display, window); + + XEvent event; + KeySym key; + char text[255]; + + + +/* this pointer will hold the returned font structure. */ +XFontStruct* font_info; + +/* try to load the given font. */ +char* font_name = "*"; +font_info = XLoadQueryFont(display, font_name); +if (!font_info) { + fprintf(stderr, "XLoadQueryFont: failed loading font '%s'\n", font_name); +} + +XSetFont(display, gc, font_info->fid); +/* assume that win_width contains the width of our window, win_height */ +/* contains the height of our window, and 'win' is the handle of our window. */ + +/* some temporary variables used for the drawing. */ +int x, y; + +/* draw a "hello world" string on the top-left side of our window. */ +x = 0; +y = 0; +XDrawString(display, window, gc, x, y, "hello world", strlen("hello world")); + +/* draw a "middle of the road" string in the middle of our window. */ +char* text_string = "middle of the road"; +/* find the width, in pixels, of the text that will be drawn using */ +/* the given font. */ +int string_width = XTextWidth(font_info, text_string, strlen(text_string)); +/* find the height of the characters drawn using this font. */ +int font_height = font_info->ascent + font_info->descent; +/* +x = (win_width - string_width) / 2; +y = (win_width - font_height) / 2; +*/ +x = 0; +y = 0; +XDrawString(display, window, gc, x, y, "hello world", strlen("hello world")); + + while(1) + { + XNextEvent(display, &event); + if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) + { + if (text[0] == 'q') + { +XDrawString(display, window, gc, x, y, "hello world", strlen("hello world")); +/* + XFreeGC(display, gc); + XFreeColormap(display, colormap); + XDestroyWindow(display, window); + XCloseDisplay(display); + exit(0); + */ + } + } + } +} diff --git a/text/new.c b/text/new.c @@ -0,0 +1,126 @@ +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" +#include <harfbuzz/hb.h> +#include <harfbuzz/hb-ot.h> + +char *ltk_load_file(const char *path, size_t *len) +{ + FILE *f; + char *contents; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + *len = ftell(f); + fseek(f, 0, SEEK_SET); + contents = malloc(*len + 1); + fread(contents, 1, *len, f); + contents[*len] = '\0'; + fclose(f); + return contents; +} + +int main(int argc, char *argv[]) +{ + stbtt_fontinfo font; + hb_blob_t *blob; + hb_face_t *face; + size_t filelen = 0; + char *filedata = ltk_load_file("NotoNastaliqUrdu-Regular.ttf", &filelen); + + if (filedata == 0) exit(1); + if (!stbtt_InitFont(&font, filedata, stbtt_GetFontOffsetForIndex(filedata,0))) exit(1); + + int height = 32; + int font_scale = stbtt_ScaleForPixelHeight(&font, height); + + blob = hb_blob_create((const char *)filedata, (unsigned int)filelen, HB_MEMORY_MODE_READONLY, NULL, NULL); + if (!blob) exit(1); + face = hb_face_create(blob, 0); + if (!face) exit(1); + hb_blob_destroy(blob); // face keeps its ref. + + hb_font_t *hb_font = hb_font_create(face); + if (!hb_font) exit(1); + hb_face_destroy(face); // font keeps ref + + // stb_truetype only does TrueType/OpenType which HB supports natively + hb_ot_font_set_funcs(hb_font); + + int x = 0; + int y = 0; + const char *msg = "ہمارے بارے میں"; + + hb_buffer_t *buf; + hb_glyph_info_t *ginf; + hb_glyph_position_t *gpos; + unsigned int len = 0; + + buf = hb_buffer_create(); + if (!buf) exit(1); + + // set up the buffer + hb_buffer_set_flags(buf, HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT); // beginning and end of text both + hb_buffer_add_utf8(buf, msg, -1, 0, -1); + + hb_buffer_guess_segment_properties(buf); + + // shape it + hb_shape(hb_font, buf, NULL, 0); + + ginf = hb_buffer_get_glyph_infos(buf, &len); + gpos = hb_buffer_get_glyph_positions(buf, &len); + if ((!ginf || !gpos) && len) exit(1); + + int width = 0; + for (int i = 0; i < len; i++) { + width += gpos[i].x_advance * font_scale; + } + + unsigned char *bitmap = calloc((width + 100) * (height + 100), sizeof(unsigned char)); + + int ascent, descent, line_gap; + stbtt_GetFontVMetrics(&font, &ascent, &descent, &line_gap); + ascent *= font_scale; + descent *= font_scale; + + int ax, x1, y1, x2, y2, byte_offset, kern_advance; + unsigned char *b; + for (int i = 0; i < len; i++) + { + hb_glyph_info_t *gi = &ginf[i]; + hb_glyph_position_t *gp = &gpos[i]; + stbtt_GetGlyphBitmapBox(&font, gi->codepoint, font_scale, font_scale, &x1, &y1, &x2, &y2); + y = ascent + y1; + byte_offset = x + (y * width); + b = stbtt_GetGlyphBitmap(&font, font_scale, font_scale, gi->codepoint, &x1, &y1, &x2, &y2); + int h = y2 - y1; + int x = x2 - x1; + for (int i = 0; i < height; i++) + { + for (int j = 0; j < width; j++) + { + bitmap[(yb + i) * (width + 100) + (xb + j)] = bitmap[(yb + i) * (width + 100) + (xb + j)] + b[i * w + j]; + if (bitmap[(yb + i) * (width + 100) + (xb + j)] > 255) bitmap[(yb + i) * (width + 100) + (xb + j)] = 255; + } + } + free(b); + + stbtt_GetGlyphHMetrics(&font, gi->codepoint, &ax, 0); + x += ax * font_scale; + + if (i < len - 1) { + kern_advance = stbtt_GetGlyphKernAdvance(&font, gi->codepoint, &ginf[i + 1]->codepoint); + x += kern_advance * font_scale; + } + + return bitmap; + } + + printf("sfsdf\n"); + stbi_write_png("test.png", width + 100, height + 100, 3, bitmap, sizeof(char) * width); + + hb_buffer_destroy(buf); + hb_font_destroy(hb_font); + return 0; +} diff --git a/text/stb_image_write.h b/text/stb_image_write.h @@ -0,0 +1,1048 @@ +/* stb_image_write - v1.02 - public domain - http://nothings.org/stb/stb_image_write.h + writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio. It could be + adapted to write to memory or a general streaming interface; let me know. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation. This library is designed + for source code compactness and simplicity, not optimal image file size + or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can define STBIW_MEMMOVE() to replace memmove() + +USAGE: + + There are four functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + There are also four equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + +CREDITS: + + PNG/BMP/TGA + Sean Barrett + HDR + Baldur Karlsson + TGA monochrome: + Jean-Sebastien Guay + misc enhancements: + Tim Kelsey + TGA RLE + Alan Hickman + initial file IO callback implementation + Emmanuel Julien + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + +LICENSE + +This software is dual-licensed to the public domain and under the following +license: you are granted a perpetual, irrevocable license to copy, modify, +publish, and distribute this file as you see fit. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#define STBIWDEF extern +extern int stbi_write_tga_with_rle; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + +#ifdef __cplusplus +} +#endif + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include <stdio.h> +#endif // STBI_WRITE_NO_STDIO + +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include <assert.h> +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +typedef struct +{ + stbi_write_func *func; + void *context; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_tga_with_rle = 1; +#else +int stbi_write_tga_with_rle = 1; +#endif + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + unsigned char arr[3]; + arr[0] = a, arr[1] = b, arr[2] = c; + s->func(s->context, arr, 3); +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + s->func(s->context, &d[comp - 1], 1); + + switch (comp) { + case 1: + s->func(s->context,d,1); + break; + case 2: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + s->func(s->context, d, 1); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + s->func(s->context, &d[comp - 1], 1); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (vdir < 0) + j_end = -1, j = y-1; + else + j_end = y, j = 0; + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + for (j = y - 1; j >= 0; --j) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + s->func(s->context, &header, 1); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + s->func(s->context, &header, 1); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + } + return 1; +} + +int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson +#ifndef STBI_WRITE_NO_STDIO + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*i*x); + STBIW_FREE(scratch); + return 1; + } +} + +int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(char**)); + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) best=d,bestloc=hlist[j]; + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1; + s1 %= 65521, s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int i,j,k,p,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = j ? mapping : firstmap; + int best = 0, bestval = 0x7fffffff; + for (p=0; p < 2; ++p) { + for (k= p?best:0; k < 5; ++k) { + int type = mymap[k],est=0; + unsigned char *z = pixels + stride_bytes*j; + for (i=0; i < n; ++i) + switch (type) { + case 0: line_buffer[i] = z[i]; break; + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; + case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-stride_bytes],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + for (i=n; i < x*n; ++i) { + switch (type) { + case 0: line_buffer[i] = z[i]; break; + case 1: line_buffer[i] = z[i] - z[i-n]; break; + case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; + case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break; + case 4: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break; + case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } + } + if (p) break; + for (i=0; i < x*n; ++i) + est += abs((signed char) line_buffer[i]); + if (est < bestval) { bestval = est; best = k; } + } + } + // when we get here, best contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) best; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + f = fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ diff --git a/text/stb_truetype.h b/text/stb_truetype.h @@ -0,0 +1,4018 @@ +// stb_truetype.h - v1.13 - public domain +// authored from 2009-2016 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket (with fix) +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// David Gow +// David Given +// Ivan-Assen Ivanov +// Anthony Pesch +// Johan Duparc +// Hou Qiming +// Fabian "ryg" Giesen +// Martins Mozeiko +// Cap Petschulat +// Omar Cornut +// github:aloucks +// Peter LaValle +// Sergey Popov +// Giumo X. Clanjor +// Higor Euripedes +// Thomas Fields +// Derek Vinyard +// +// VERSION HISTORY +// +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts, num-fonts-in-TTC function +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversample() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since they different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.6c, 2050 LOC) +// +// Documentation & header file 520 LOC \___ 660 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 620 LOC ---- 620 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 +// +// +// PERFORMANCE MEASUREMENTS FOR 1.06: +// +// 32-bit 64-bit +// Previous release: 8.83 s 7.68 s +// Pool allocations: 7.72 s 6.34 s +// Inline sort : 6.54 s 5.65 s +// New rasterizer : 5.63 s 5.00 s + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLuint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include <stdio.h> +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include <math.h> + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include <math.h> + #define STBTT_sqrt(x) sqrt(x) + #endif + + #ifndef STBTT_fabs + #include <math.h> + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include <stdlib.h> + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include <assert.h> + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include <string.h> + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include <memory.h> + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is weight x height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) { + *x0 = r ? c.min_x : 0; + *y0 = r ? c.min_y : 0; + *x1 = r ? c.max_x : 0; + *y1 = r ? c.max_y : 0; + } + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clear pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + float y1,y2; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + y1 = (x - x0) / dx + y_top; + y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + STBTT_assert(z->ey >= scan_y_top); + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshhold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && mid<n: 0>n => n; 0<n => 0 */ + /* 0<mid && mid>n: 0>n => 0; 0<n => n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + return k; +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// diff --git a/text/test data/RandomWords.odt b/text/test data/RandomWords.odt Binary files differ. diff --git a/text/test data/RandomWords.pdf b/text/test data/RandomWords.pdf Binary files differ. diff --git a/text/test data/RandomWords.xml b/text/test data/RandomWords.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet type="text/xsl" href="ftml_wf.xsl"?> +<ftml version="1.0"> + <head> + <columns class="2%" comment="2%" label="10%" string="5%"/> + <description>Test of Awami - Letter Coverage</description> + <fontscale>100</fontscale> + <fontsrc>local('Awami Nastaliq Beta3'), url(Awami_beta3.ttf)</fontsrc> + <title>Test of Awami - Random Words</title> + <styles><style feats=' ' name="default"/></styles> + </head> + <testgroup label="Group 1"> + <test label="word 1" rtl="True"><string>&#x0622;&#x062A;&#x0650;&#x0634;&#x0650;&#x06CC;&#x0646;</string><comment></comment></test> + <test label="word 2" rtl="True"><string>&#x0622;&#x0631;&#x0627;&#x0645;&#x06AF;&#x0627;&#x06C1;&#x0648;&#x06BA;</string></test> + <test label="word 3" rtl="True"><string>&#x0633;&#x0628;&#x0651;&#x06A9;&#x06CC;</string></test> + <test label="word 4" rtl="True"><string>&#x0633;&#x06D2;&#x0635;&#x064F;&#x062D;&#x0628;&#x062A;</string></test> + <test label="word 5" rtl="True"><string>&#x0634;&#x062E;&#x0635;&#x0650;&#x06CC;&#x0645;&#x0627;&#x06C1;</string></test> + <test label="word 6" rtl="True"><string>&#x0637;&#x06CC;&#x062A;&#x06BE;</string></test> + <test label="word 7" rtl="True"><string>&#x0639;&#x0648;&#x0641;&#x0644;</string></test> + <test label="word 8" rtl="True"><string>&#x0639;&#x064E;&#x0645;&#x0627;&#x0644;&#x06CC;&#x0642;</string></test> + <test label="word 9" rtl="True"><string>&#x0639;&#x0650;&#x0645;&#x0651;&#x06CC;&#x06C1;&#x064F;&#x0648;&#x062F;</string></test> + <test label="word 10" rtl="True"><string>&#x0648;&#x0627;&#x0633;&#x0637;&#x06C1;</string></test> + <test label="word 11" rtl="True"><string>&#x067E;&#x064E;&#x06BE;&#x06CC;&#x0644;&#x0627;&#x0626;&#x06CC;</string></test> + <test label="word 12" rtl="True"><string>&#x067E;&#x064E;&#x06CC;&#x062F;&#x0627;&#x06A9;&#x0631;</string></test> + <test label="word 13" rtl="True"><string>&#x06CC;&#x06C1;&#x064F;&#x0648;&#x0633;&#x0641;&#x0637;</string></test> + <test label="word 14" rtl="True"><string>&#x0627;&#x0686;&#x06BE;&#x0627;&#x0651;&#x0644;&#x06AF;&#x062A;&#x0627;</string></test> + <test label="word 15" rtl="True"><string>&#x0628;&#x063A;&#x064E;&#x06CC;&#x0631;&#x06A9;&#x0644;&#x0627;&#x0645;</string></test> + <test label="word 16" rtl="True"><string>&#x0633;&#x0645;&#x062F;&#x06BE;&#x06CC;&#x0627;&#x0646;&#x06C1;</string></test> + </testgroup> +</ftml> diff --git a/text/test data/ftml_wf.xsl b/text/test data/ftml_wf.xsl @@ -0,0 +1,253 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:output method="html" encoding="utf-8"/> + +<!-- +This XSL stylesheet generates tables from a group of test strings, showing a waterfall at multiple sizes. +The top-level test group generates a section with a label and table. +Each test is shown in a row in the table, at four successive sizes. +The final cell in the row is the first test's comment. + +The data should look something like: + +<ftml> + <head> + <columns class="2%" comment="2%" label="10%" string="5%"/> + <description>Page Title</description> + <fontscale>100</fontscale> + <fontsrc>local('Font Name'), url(filefile.ttf)</fontsrc> + <title>Page Title</title> + <styles><style feats=' ' name="default"/></styles> + </head> + <testgroup label="Group 1"> + <test label="word 1" rtl="True"><string>test string 1</string><comment>This is a comment.</comment></test> + <test label="word 2" rtl="True"><string>test string 2</string></test> + ... +--> + +<!-- set variables from head element --> +<xsl:variable name="width-class" select="/ftml/head/columns/@class"/> +<xsl:variable name="width-comment" select="/ftml/head/columns/@comment"/> +<xsl:variable name="width-label" select="/ftml/head/columns/@label"/> +<xsl:variable name="width-string" select="/ftml/head/columns/@string"/> +<xsl:variable name="font-scale" select="concat(/ftml/head/fontscale, substring('100', 1 div not(/ftml/head/fontscale)))"/> + +<!-- + Process the root node to construct the html page +--> +<xsl:template match="/"> +<html> + <head> + <title> + <xsl:value-of select="ftml/head/title"/> + </title> + <meta charset="utf-8"/> + <meta name="description"> + <xsl:attribute name="content"> + <xsl:value-of select="ftml/head/description"/> + </xsl:attribute> + </meta> + <style> + body, td { font-family: sans-serif; } + @font-face {font-family: TestFont; src: <xsl:value-of select="ftml/head/fontsrc"/>; } + th { text-align: left; } + table { width: 100%; table-layout: fixed; } + table,th,td { padding: 20px; border: 1px solid #D8D8D8; border-collapse: collapse; } +<xsl:if test="$width-label != ''"> + .label { width: <xsl:value-of select="$width-label"/> } +</xsl:if> +<xsl:if test="$width-string != ''"> + .string {font-family: TestFont;} +</xsl:if> +<xsl:if test="$width-comment != ''"> + .comment {width: <xsl:value-of select="$width-comment"/>} +</xsl:if> +<xsl:if test="$width-class != ''"> + .comment {width: <xsl:value-of select="$width-class"/>} +</xsl:if> + .dim {color: silver;} + .bright {color: red;} + <!-- NB: Uncomment the following to build separate css styles for each item in /ftml/head/styles --> + <!-- <xsl:apply-templates select="/ftml/head/styles/*" /> --> + </style> + </head> + + <body> + <h1><xsl:value-of select="/ftml/head/title"/></h1> + <xsl:apply-templates select="/ftml/testgroup"/> + </body> +</html> +</xsl:template> + +<!-- + Build CSS style for FTML style element +--> +<xsl:template match="style"> + .<xsl:value-of select="@name"/> { + font-family: TestFont; font-size: <xsl:value-of select="$font-scale"/>%; +<xsl:if test="@feats"> + -moz-font-feature-settings: <xsl:value-of select="@feats"/>; + -ms-font-feature-settings: <xsl:value-of select="@feats"/>; + -webkit-font-feature-settings: <xsl:value-of select="@feats"/>; + font-feature-settings: <xsl:value-of select="@feats"/> ; +</xsl:if> + } +</xsl:template> + +<!-- + Process a testgroup, emitting a table containing all test records from the group +--> +<xsl:template match="testgroup"> + <h2><xsl:value-of select="@label"/></h2> + <table> + <tbody> + <xsl:apply-templates/> + </tbody> + </table> +</xsl:template> + +<!-- + Process a single test record, emitting a table row. +--> +<xsl:template match="test"> +<tr> + <xsl:if test="@background"> + <xsl:attribute name="style">background-color: <xsl:value-of select="@background"/>;</xsl:attribute> + </xsl:if> + <xsl:if test="$width-label != ''"> + <!-- emit label column --> + <td class="label"> + <xsl:value-of select="@label"/> + </td> + </xsl:if> + <xsl:if test="$width-string != ''"> + <!-- emit test data column --> + <xsl:call-template name="cell"> + <xsl:with-param name="scale">1</xsl:with-param> + </xsl:call-template> + <xsl:call-template name="cell"> + <xsl:with-param name="scale">2</xsl:with-param> + </xsl:call-template> + <xsl:call-template name="cell"> + <xsl:with-param name="scale">5</xsl:with-param> + </xsl:call-template> + <xsl:call-template name="cell"> + <xsl:with-param name="scale">10</xsl:with-param> + </xsl:call-template> + </xsl:if> + <xsl:if test="$width-comment != ''"> + <td class="comment"> + <!-- emit comment --> + <xsl:value-of select="comment"/> + </td> + </xsl:if> + <xsl:if test="$width-class != ''"> + <td class="class"> + <!-- emit class --> + <xsl:value-of select="@class"/> + </td> + </xsl:if> +</tr> +</xsl:template> + +<!-- + Emit html for one cell +--> +<xsl:template name="cell"> + <xsl:param name="scale">1</xsl:param> <!-- 1 = default --> + + <td class="string"> <!-- assume default string class --> + <xsl:variable name="styleName" select="@class"/> + <xsl:if test="$styleName != ''"> + <!-- emit lang attribute --> + <xsl:apply-templates select="/ftml/head/styles/style[@name=$styleName]" mode="getLang"/> + </xsl:if> + <xsl:if test="@background"> + <xsl:attribute name="style">background-color: <xsl:value-of select="@background"/>;</xsl:attribute> + </xsl:if> + <xsl:if test="@rtl='True' "> + <xsl:attribute name="dir">RTL</xsl:attribute> + </xsl:if> + <!-- emit style attribute with features and font-size --> + <xsl:attribute name="style"> + <xsl:if test="$styleName != ''"> + <xsl:apply-templates select="/ftml/head/styles/style[@name=$styleName]" mode="getFeats"/> + </xsl:if> + font-size: <xsl:value-of select="$scale * $font-scale"/>%; + width: + <xsl:choose> + <xsl:when test="contains($width-label,'%')"> + <xsl:value-of select="$scale * substring-before($width-string,'%')"/>%; + </xsl:when> + <!-- would need other xsl:when elements to handle other suffixes: em, pt, etc. --> + <xsl:otherwise> + <xsl:value-of select="$scale * $width-string"/>; + </xsl:otherwise> + </xsl:choose> + </xsl:attribute> + + <!-- and finally the test data --> + <xsl:choose> + <!-- if the test has an <em> marker, the use a special template --> + <xsl:when test="string[em]"> + <xsl:apply-templates select="string" mode="hasEM"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="string"/> + </xsl:otherwise> + </xsl:choose> + </td> +</xsl:template> + +<!-- + Emit html lang attribute. +--> +<xsl:template match="style" mode="getLang"> + <xsl:if test="@lang"> + <xsl:attribute name="lang"> + <xsl:value-of select="@lang"/> + </xsl:attribute> + </xsl:if> +</xsl:template> + +<!-- + Emit html feature-settings (to add to style attribute). +--> +<xsl:template match="style" mode="getFeats"> + <xsl:if test="@feats"> +-moz-font-feature-settings: <xsl:value-of select="@feats"/>; +-ms-font-feature-settings: <xsl:value-of select="@feats"/>; +-webkit-font-feature-settings: <xsl:value-of select="@feats"/>; +font-feature-settings: <xsl:value-of select="@feats"/>; + </xsl:if> +</xsl:template> + +<!-- + Suppress all text nodes except those we really want. +--> +<xsl:template match="text()"/> + +<!-- + for test strings that have no <em> children, emit text nodes without any adornment +--> +<xsl:template match="string/text()"> + <xsl:value-of select="."/> +</xsl:template> + +<!-- + for test strings that have <em> children, emit text nodes dimmed +--> +<xsl:template match="string/text()" mode="hasEM"> + <span class="dim"><xsl:value-of select="."/></span> +</xsl:template> + +<!-- + for <em> children of test strings, emit the text nodes with no adornment +--> +<xsl:template match="em/text()" mode="hasEM"> + <!-- <span class="bright"><xsl:value-of select="."/></span> --> + <xsl:value-of select="."/> +</xsl:template> + +</xsl:stylesheet> + diff --git a/text/test data/language data/Kalami-gwc_UDHR.odt b/text/test data/language data/Kalami-gwc_UDHR.odt Binary files differ. diff --git a/text/test data/language data/Kalami-gwc_UDHR.pdf b/text/test data/language data/Kalami-gwc_UDHR.pdf Binary files differ. diff --git a/text/test data/language data/Khowar-khw_UDHR.odt b/text/test data/language data/Khowar-khw_UDHR.odt Binary files differ. diff --git a/text/test data/language data/Khowar-khw_UDHR.pdf b/text/test data/language data/Khowar-khw_UDHR.pdf Binary files differ. diff --git a/text/test data/language data/Palula-phl_UDHR.odt b/text/test data/language data/Palula-phl_UDHR.odt Binary files differ. diff --git a/text/test data/language data/Palula-phl_UDHR.pdf b/text/test data/language data/Palula-phl_UDHR.pdf Binary files differ. diff --git a/text/test data/language data/Saraiki-skr_UDHR.odt b/text/test data/language data/Saraiki-skr_UDHR.odt Binary files differ. diff --git a/text/test data/language data/Saraiki-skr_UDHR.pdf b/text/test data/language data/Saraiki-skr_UDHR.pdf Binary files differ. diff --git a/text/test data/language data/SaraikiWords_Awami.odt b/text/test data/language data/SaraikiWords_Awami.odt Binary files differ. diff --git a/text/test data/language data/SaraikiWords_Awami.pdf b/text/test data/language data/SaraikiWords_Awami.pdf Binary files differ. diff --git a/text/test data/language data/Shina-scl_UDHR.odt b/text/test data/language data/Shina-scl_UDHR.odt Binary files differ. diff --git a/text/test data/language data/Shina-scl_UDHR.pdf b/text/test data/language data/Shina-scl_UDHR.pdf Binary files differ. diff --git a/text/test data/language data/Urdu-urd_UDHR.odt b/text/test data/language data/Urdu-urd_UDHR.odt Binary files differ. diff --git a/text/test data/language data/Urdu-urd_UDHR.pdf b/text/test data/language data/Urdu-urd_UDHR.pdf Binary files differ. diff --git a/text/test data/language data/UrduWords_Awami.odt b/text/test data/language data/UrduWords_Awami.odt Binary files differ. diff --git a/text/test data/language data/UrduWords_Awami.pdf b/text/test data/language data/UrduWords_Awami.pdf Binary files differ. diff --git a/text/test data/letter combinations/ftml.xsl b/text/test data/letter combinations/ftml.xsl @@ -0,0 +1,251 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:output method="html" encoding="utf-8"/> + +<!-- +This XSL stylesheet generates tables from a group of test strings. +The top-level test group generates a section with a label and table. +Each second-level testgroup is a single line of the of the table; the row label is taken from that +testgroup label. +The testgroup's test items are successive cells in the row. +The final cell is the first test's comment, which really functions as a comment for the group. + +The data should look something like: + +<ftml> + <head> + <columns comment="15%" label="20%" string="15%"/> + <description>Page Title</description> + <fontscale>200</fontscale> + <fontsrc>local('Font Name'), url(fontfile.ttf)</fontsrc> + <title>Page Title</title> + <styles><style feats=' ' name="default"/></styles> + </head> + <testgroup label="Section 1"> + <testgroup label="row 1" comment="comment 1"> + <test rtl="True"> + <comment>This is a comment - for the group, really.</comment> + <string><em>test1</em></string> + </test> + <test rtl="True"><string>pre-context<em>test1</em></string></test> + <test rtl="True" background="#cfcfcf"><string></string></test> + <test rtl="True" background="#cfcfcf"><string></string></test> + </testgroup> + <testgroup label="row 2"> + <test rtl="True"><string><em>test2</em></string></test> + <test rtl="True"><string>pre-context<em>test2</em></string></test> + <test rtl="True"><string><em>test2</em>y</string>post-context</test> + <test rtl="True"><string>pre-context<em>test2</em>post-context</string></test> + </testgroup> + </testgroup> + ... +--> + +<!-- set variables from head element --> +<xsl:variable name="width-comment" select="/ftml/head/columns/@comment"/> +<xsl:variable name="width-label" select="/ftml/head/columns/@label"/> +<xsl:variable name="width-string" select="/ftml/head/columns/@string"/> +<xsl:variable name="font-scale" select="concat(/ftml/head/fontscale, substring('100', 1 div not(/ftml/head/fontscale)))"/> + +<!-- + Process the root node to construct the html page. +--> +<xsl:template match="/"> +<html> + <head> + <title> + <xsl:value-of select="ftml/head/title"/> + </title> + <meta charset="utf-8"/> + <meta name="description"> + <xsl:attribute name="content"> + <xsl:value-of select="ftml/head/description"/> + </xsl:attribute> + </meta> + <style> + body, td { font-family: sans-serif; } + @font-face {font-family: TestFont; src: <xsl:value-of select="ftml/head/fontsrc"/>; } + th { text-align: left; } + table { width: 100%; table-layout: fixed; } + table,th,td { padding: 2px; border: 1px solid #D8D8D8; border-collapse: collapse; } +<xsl:if test="$width-label != ''"> + .label { width: <xsl:value-of select="$width-label"/> } +</xsl:if> +<xsl:if test="$width-string != ''"> + .string {width: <xsl:value-of select="$width-string"/>; font-family: TestFont; font-size: <xsl:value-of select="$font-scale"/>%;} +</xsl:if> +<xsl:if test="$width-comment != ''"> + .comment {width: <xsl:value-of select="$width-comment"/>} +</xsl:if> + .dim {color: silver;} + .bright {color: red;} + <!-- NB: Uncomment the following to build separate css styles for each item in /ftml/head/styles --> + <!-- <xsl:apply-templates select="/ftml/head/styles/*" /> --> + </style> + </head> + + <body> + <!-- TOC anchor --> + <a><xsl:attribute name="name">toc</xsl:attribute></a> + + <h1><xsl:value-of select="/ftml/head/title"/></h1> + + <!-- Generate table of contents --> + <ul> + <xsl:apply-templates select="/ftml/testgroup" mode="toc"/> + </ul> + + <xsl:apply-templates select="/ftml/testgroup" /> + </body> +</html> +</xsl:template> + +<!-- + Build CSS style for FTML style element. +--> +<xsl:template match="style"> + .<xsl:value-of select="@name"/> { + font-family: TestFont; font-size: <xsl:value-of select="$font-scale"/>%; +<xsl:if test="@feats"> + -moz-font-feature-settings: <xsl:value-of select="@feats"/>; + -ms-font-feature-settings: <xsl:value-of select="@feats"/>; + -webkit-font-feature-settings: <xsl:value-of select="@feats"/>; + font-feature-settings: <xsl:value-of select="@feats"/> ; +</xsl:if> + } +</xsl:template> + + +<!-- + Generate a table-of-contents link for the testgroup. +--> +<xsl:template match="ftml/testgroup" mode="toc"> + <li><a> + <xsl:attribute name="href">#section<xsl:value-of select="position()"/></xsl:attribute> + <xsl:value-of select="@label" /> + </a></li> +</xsl:template> + + +<!-- + Process a top level testgroup, emitting a table (containing a row for each testgroup subelement). +--> +<xsl:template match="/ftml/testgroup"> + <!-- TOC anchor --> + <a><xsl:attribute name="name">section<xsl:value-of select="position()"/></xsl:attribute></a> + + <h2><xsl:value-of select="@label"/></h2> + <p><a><xsl:attribute name="href">#toc</xsl:attribute>[Table of Contents]</a></p> + <table> + <tbody> + <xsl:apply-templates/> + </tbody> + </table> +</xsl:template> + +<!-- + Process a second level testgroup record, emitting a table row (containing a cell for each test subelement). + Pick up comment and class from first test subelement. +--> +<xsl:template match="/ftml/testgroup/testgroup"> +<tr> + <xsl:if test="@background"> + <xsl:attribute name="style">background-color: <xsl:value-of select="@background"/>;</xsl:attribute> + </xsl:if> + <xsl:if test="$width-label != ''"> + <!-- emit label column --> + <td class="label"> + <xsl:value-of select="@label"/> + </td> + </xsl:if> + + <xsl:apply-templates/> <!-- generate the test string cells --> + + <xsl:if test="$width-comment != ''"> + <td class="comment"> + <!-- emit comment concatenated with class (if not default) --> + <xsl:value-of select="test/comment"/> + <xsl:if test="test/@class"> (<xsl:value-of select="test/@class"/>)</xsl:if> + </td> + </xsl:if> +</tr> +</xsl:template> + +<!-- + Emit html lang and font-feature-settings for a test +--> +<xsl:template match="style" mode="getLang"> + <xsl:if test="@lang"> + <xsl:attribute name="lang"> + <xsl:value-of select="@lang"/> + </xsl:attribute> + </xsl:if> + <xsl:if test="@feats"> + <xsl:attribute name="style"> +-moz-font-feature-settings: <xsl:value-of select="@feats"/>; +-ms-font-feature-settings: <xsl:value-of select="@feats"/>; +-webkit-font-feature-settings: <xsl:value-of select="@feats"/>; +font-feature-settings: <xsl:value-of select="@feats"/>;</xsl:attribute> + </xsl:if> +</xsl:template> + +<!-- + Process a single test record, emitting a table cell. +--> +<xsl:template match="test"> + <xsl:if test="$width-string != ''"> + <!-- emit test data column --> + <td class="string"> <!-- assume default string class --> + <xsl:if test="@class"> + <!-- emit features and lang attributes --> + <xsl:variable name="styleName" select="@class"/> + <xsl:apply-templates select="/ftml/head/styles/style[@name=$styleName]" mode="getLang"/> + </xsl:if> + <xsl:if test="@rtl='True' "> + <xsl:attribute name="dir">RTL</xsl:attribute> + </xsl:if> + <xsl:if test="@background"> + <xsl:attribute name="style">background-color: <xsl:value-of select="@background"/>;</xsl:attribute> + </xsl:if> + <!-- and finally the test data --> + <xsl:choose> + <!-- if the test has an <em> marker, the use a special template --> + <xsl:when test="string[em]"> + <xsl:apply-templates select="string" mode="hasEM"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="string"/> + </xsl:otherwise> + </xsl:choose> + </td> + </xsl:if> +</xsl:template> + +<!-- + Suppress all text nodes except those we really want. +--> +<xsl:template match="text()"/> + +<!-- + for test strings that have no <em> children, emit text nodes without any adornment +--> +<xsl:template match="string/text()"> + <xsl:value-of select="."/> +</xsl:template> + +<!-- + for test strings that have <em> children, emit text nodes dimmed +--> +<xsl:template match="string/text()" mode="hasEM"> + <span class="dim"><xsl:value-of select="."/></span> +</xsl:template> + +<!-- + for <em> children of test strings, emit the text nodes with no adornment +--> +<xsl:template match="em/text()" mode="hasEM"> + <!-- <span class="bright"><xsl:value-of select="."/></span> --> + <xsl:value-of select="."/> +</xsl:template> + +</xsl:stylesheet> diff --git a/text/test data/letter combinations/test_allbasechars.pdf b/text/test data/letter combinations/test_allbasechars.pdf Binary files differ. diff --git a/text/test data/letter combinations/test_allbasechars.xml b/text/test data/letter combinations/test_allbasechars.xml @@ -0,0 +1,2324 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet type="text/xsl" href="ftml.xsl"?> +<ftml version="1.0"> + <head> + <columns comment="15%" label="20%" string="15%"/> + <description>Test of All Awami Base Characters</description> + <fontscale>200</fontscale> + <fontsrc>local('Awami Nastaliq Beta3'), url(Awami_beta3.ttf)</fontsrc> + <title>Test of All Awami Base Characters</title> + <styles><style feats=' ' name="default"/></styles> + </head> + <testgroup label="Alef form sequences"> + <testgroup label="tteheh + alef"> + <test rtl="True"><string><em>&#x067A;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067A;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hahTah2smd + alef"> + <test rtl="True"><string><em>&#x076F;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076F;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + alef"> + <test rtl="True"><string><em>&#x0633;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="dadDotBelow + alef"> + <test rtl="True"><string><em>&#x06FB;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + alef"> + <test rtl="True"><string><em>&#x0637;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ghain + alef"> + <test rtl="True"><string><em>&#x063A;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + alef"> + <test rtl="True"><string><em>&#x0641;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + alef"> + <test rtl="True"><string><em>&#x0644;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + alef"> + <test rtl="True"><string><em>&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ngoeh + alef"> + <test rtl="True"><string><em>&#x06B1;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B1;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + alef"> + <test rtl="True"><string><em>&#x06BE;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="arabicHeh + alef"> + <test rtl="True"><string><em>&#x0647;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0647;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Beh form sequences"> + <testgroup label="beh + teh"> + <test rtl="True"><string><em>&#x0628;&#x062A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x062A;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x062A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x062A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hah + peh"> + <test rtl="True"><string><em>&#x062D;&#x067E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062D;&#x067E;</em></string></test> + <test rtl="True"><string><em>&#x062D;&#x067E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062D;&#x067E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sheen + peh"> + <test rtl="True"><string><em>&#x0634;&#x067E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0634;&#x067E;</em></string></test> + <test rtl="True"><string><em>&#x0634;&#x067E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0634;&#x067E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dad + theh"> + <test rtl="True"><string><em>&#x0636;&#x062B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x062B;</em></string></test> + <test rtl="True"><string><em>&#x0636;&#x062B;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x062B;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + beh"> + <test rtl="True"><string><em>&#x0637;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ghain + beh"> + <test rtl="True"><string><em>&#x063A;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x063A;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + beh"> + <test rtl="True"><string><em>&#x0641;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lamSmallV + beh"> + <test rtl="True"><string><em>&#x06B5;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06B5;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + beh"> + <test rtl="True"><string><em>&#x0645;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gueh + beh"> + <test rtl="True"><string><em>&#x06B3;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B3;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06B3;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B3;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + beh"> + <test rtl="True"><string><em>&#x06BE;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="arabicHeh + beh"> + <test rtl="True"><string><em>&#x0647;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0647;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0647;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0647;&#x0628;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Jeem form sequences"> + <testgroup label="theh + jeem"> + <test rtl="True"><string><em>&#x062B;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062B;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x062B;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062B;&#x062C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="khah + tcheh"> + <test rtl="True"><string><em>&#x062E;&#x0686;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062E;&#x0686;</em></string></test> + <test rtl="True"><string><em>&#x062E;&#x0686;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062E;&#x0686;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seenDotDot + tcheh"> + <test rtl="True"><string><em>&#x069A;&#x0686;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x069A;&#x0686;</em></string></test> + <test rtl="True"><string><em>&#x069A;&#x0686;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x069A;&#x0686;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dadDotBelow + dyeh"> + <test rtl="True"><string><em>&#x06FB;&#x0684;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x0684;</em></string></test> + <test rtl="True"><string><em>&#x06FB;&#x0684;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x0684;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="zah + tcheheh"> + <test rtl="True"><string><em>&#x0638;&#x0687;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0687;</em></string></test> + <test rtl="True"><string><em>&#x0638;&#x0687;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0687;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + hahHamza"> + <test rtl="True"><string><em>&#x0639;&#x0681;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0681;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0681;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0681;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh3dots + hah3dots"> + <test rtl="True"><string><em>&#x06A5;&#x0685;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x0685;</em></string></test> + <test rtl="True"><string><em>&#x06A5;&#x0685;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x0685;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + hahTah"> + <test rtl="True"><string><em>&#x0644;&#x076E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x076E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x076E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x076E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + hahTah2smd"> + <test rtl="True"><string><em>&#x0645;&#x076F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x076F;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x076F;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x076F;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ngoeh + jeem"> + <test rtl="True"><string><em>&#x06B1;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B1;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x06B1;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B1;&#x062C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + hah"> + <test rtl="True"><string><em>&#x06BE;&#x062D;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x062D;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x062D;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x062D;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehHamza + khah"> + <test rtl="True"><string><em>&#x06C2;&#x062E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x062E;</em></string></test> + <test rtl="True"><string><em>&#x06C2;&#x062E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x062E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Dal form sequences"> + <testgroup label="beeh + dal"> + <test rtl="True"><string><em>&#x067B;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067B;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + ddal"> + <test rtl="True"><string><em>&#x062C;&#x0688;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0688;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sheen + dal2dotsVTah"> + <test rtl="True"><string><em>&#x0634;&#x0759;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0634;&#x0759;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + dal4dots"> + <test rtl="True"><string><em>&#x0635;&#x0690;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0690;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="zah + thal"> + <test rtl="True"><string><em>&#x0638;&#x0630;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0630;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + dalDotTah"> + <test rtl="True"><string><em>&#x0639;&#x068B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x068B;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh3dots + dalRing"> + <test rtl="True"><string><em>&#x06A5;&#x0689;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x0689;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lamBar + dal"> + <test rtl="True"><string><em>&#x076A;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076A;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + thal"> + <test rtl="True"><string><em>&#x0645;&#x0630;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0630;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kafRing + dalDotTah"> + <test rtl="True"><string><em>&#x06AB;&#x068B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AB;&#x068B;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + dal2dotsVTah"> + <test rtl="True"><string><em>&#x06BE;&#x0759;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0759;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehHamza + dal4dots"> + <test rtl="True"><string><em>&#x06C2;&#x0690;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x0690;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Reh form sequences"> + <testgroup label="tehRing + reh"> + <test rtl="True"><string><em>&#x067C;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067C;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hah + rreh"> + <test rtl="True"><string><em>&#x062D;&#x0691;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062D;&#x0691;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seenDotDot + rehDotBelow"> + <test rtl="True"><string><em>&#x069A;&#x0694;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x069A;&#x0694;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="dad + reh2dots"> + <test rtl="True"><string><em>&#x0636;&#x0697;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x0697;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + rehHamza"> + <test rtl="True"><string><em>&#x0637;&#x076C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x076C;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ghain + rehTah2smd"> + <test rtl="True"><string><em>&#x063A;&#x0771;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0771;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + reh"> + <test rtl="True"><string><em>&#x0641;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lamSmallV + rreh"> + <test rtl="True"><string><em>&#x06B5;&#x0691;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x0691;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + rehDotBelow"> + <test rtl="True"><string><em>&#x0645;&#x0694;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0694;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + reh2dots"> + <test rtl="True"><string><em>&#x06A9;&#x0697;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0697;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + rehHamza"> + <test rtl="True"><string><em>&#x06BE;&#x076C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x076C;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + rehTah2smd"> + <test rtl="True"><string><em>&#x06C1;&#x0771;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0771;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Seen form sequences"> + <testgroup label="tteh + seen"> + <test rtl="True"><string><em>&#x0679;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0679;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0679;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0679;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dyeh + seen2dotsV"> + <test rtl="True"><string><em>&#x0684;&#x076D;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0684;&#x076D;</em></string></test> + <test rtl="True"><string><em>&#x0684;&#x076D;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0684;&#x076D;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen4dots + seen2dotsV"> + <test rtl="True"><string><em>&#x075C;&#x076D;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x075C;&#x076D;</em></string></test> + <test rtl="True"><string><em>&#x075C;&#x076D;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x075C;&#x076D;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + seen"> + <test rtl="True"><string><em>&#x0635;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + seen2dotsV"> + <test rtl="True"><string><em>&#x0637;&#x076D;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x076D;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x076D;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x076D;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ghain + sheen"> + <test rtl="True"><string><em>&#x063A;&#x0634;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0634;</em></string></test> + <test rtl="True"><string><em>&#x063A;&#x0634;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0634;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + seenTah2smd"> + <test rtl="True"><string><em>&#x0641;&#x0770;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0770;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0770;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0770;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lamBar + seenDotDot"> + <test rtl="True"><string><em>&#x076A;&#x069A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076A;&#x069A;</em></string></test> + <test rtl="True"><string><em>&#x076A;&#x069A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x076A;&#x069A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + seen3dots3dots"> + <test rtl="True"><string><em>&#x0645;&#x069C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x069C;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x069C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x069C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kafRing + seen4dots"> + <test rtl="True"><string><em>&#x06AB;&#x075C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AB;&#x075C;</em></string></test> + <test rtl="True"><string><em>&#x06AB;&#x075C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AB;&#x075C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + seen"> + <test rtl="True"><string><em>&#x06BE;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + seen2dotsV"> + <test rtl="True"><string><em>&#x06C1;&#x076D;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x076D;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x076D;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x076D;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Sad form sequences"> + <testgroup label="peh + sad"> + <test rtl="True"><string><em>&#x067E;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067E;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x067E;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x067E;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tcheheh + dad"> + <test rtl="True"><string><em>&#x0687;&#x0636;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0687;&#x0636;</em></string></test> + <test rtl="True"><string><em>&#x0687;&#x0636;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0687;&#x0636;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seenTah2smd + dadDotBelow"> + <test rtl="True"><string><em>&#x0770;&#x06FB;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0770;&#x06FB;</em></string></test> + <test rtl="True"><string><em>&#x0770;&#x06FB;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0770;&#x06FB;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dad + dadDotBelow"> + <test rtl="True"><string><em>&#x0636;&#x06FB;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x06FB;</em></string></test> + <test rtl="True"><string><em>&#x0636;&#x06FB;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x06FB;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="zah + dad"> + <test rtl="True"><string><em>&#x0638;&#x0636;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0636;</em></string></test> + <test rtl="True"><string><em>&#x0638;&#x0636;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0636;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + dadDotBelow"> + <test rtl="True"><string><em>&#x0639;&#x06FB;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06FB;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x06FB;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06FB;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh3dots + sad"> + <test rtl="True"><string><em>&#x06A5;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x06A5;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lamSmallV + dad"> + <test rtl="True"><string><em>&#x06B5;&#x0636;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x0636;</em></string></test> + <test rtl="True"><string><em>&#x06B5;&#x0636;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x0636;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + dadDotBelow"> + <test rtl="True"><string><em>&#x0645;&#x06FB;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06FB;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x06FB;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06FB;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + sad"> + <test rtl="True"><string><em>&#x06A9;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + dad"> + <test rtl="True"><string><em>&#x06BE;&#x0636;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0636;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0636;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0636;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="arabicHeh + dadDotBelow"> + <test rtl="True"><string><em>&#x0647;&#x06FB;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0647;&#x06FB;</em></string></test> + <test rtl="True"><string><em>&#x0647;&#x06FB;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0647;&#x06FB;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Tah form sequences"> + <testgroup label="tteheh + tah"> + <test rtl="True"><string><em>&#x067A;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067A;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x067A;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x067A;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hahHamza + zah"> + <test rtl="True"><string><em>&#x0681;&#x0638;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0681;&#x0638;</em></string></test> + <test rtl="True"><string><em>&#x0681;&#x0638;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0681;&#x0638;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen3dots3dots + tah"> + <test rtl="True"><string><em>&#x069C;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x069C;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x069C;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x069C;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + tah"> + <test rtl="True"><string><em>&#x0635;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zah"> + <test rtl="True"><string><em>&#x0637;&#x0638;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0638;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0638;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0638;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ghain + tah"> + <test rtl="True"><string><em>&#x063A;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x063A;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zah"> + <test rtl="True"><string><em>&#x0641;&#x0638;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0638;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0638;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0638;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + tah"> + <test rtl="True"><string><em>&#x0644;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zah"> + <test rtl="True"><string><em>&#x0645;&#x0638;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0638;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0638;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0638;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + tah"> + <test rtl="True"><string><em>&#x06AF;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zah"> + <test rtl="True"><string><em>&#x06BE;&#x0638;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0638;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0638;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0638;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehHamza + tah"> + <test rtl="True"><string><em>&#x06C2;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x06C2;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x0637;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Ain form sequences"> + <testgroup label="beeh + ain"> + <test rtl="True"><string><em>&#x067B;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067B;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x067B;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x067B;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hah3dots + ghain"> + <test rtl="True"><string><em>&#x0685;&#x063A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0685;&#x063A;</em></string></test> + <test rtl="True"><string><em>&#x0685;&#x063A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0685;&#x063A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + ain"> + <test rtl="True"><string><em>&#x0633;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dad + ain"> + <test rtl="True"><string><em>&#x0636;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x0636;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + ain"> + <test rtl="True"><string><em>&#x0637;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + ghain"> + <test rtl="True"><string><em>&#x0639;&#x063A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x063A;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x063A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x063A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh3dots + ghain"> + <test rtl="True"><string><em>&#x06A5;&#x063A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x063A;</em></string></test> + <test rtl="True"><string><em>&#x06A5;&#x063A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x063A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lamBar + ain"> + <test rtl="True"><string><em>&#x076A;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076A;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x076A;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x076A;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + ghain"> + <test rtl="True"><string><em>&#x0645;&#x063A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x063A;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x063A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x063A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gueh + ain"> + <test rtl="True"><string><em>&#x06B3;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B3;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x06B3;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B3;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + ghain"> + <test rtl="True"><string><em>&#x06BE;&#x063A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x063A;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x063A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x063A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + ain"> + <test rtl="True"><string><em>&#x06C1;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0639;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Feh form sequences"> + <testgroup label="tehRing + feh"> + <test rtl="True"><string><em>&#x067C;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067C;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x067C;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x067C;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hahTah + feh3dots"> + <test rtl="True"><string><em>&#x076E;&#x06A5;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076E;&#x06A5;</em></string></test> + <test rtl="True"><string><em>&#x076E;&#x06A5;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x076E;&#x06A5;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sheen + feh"> + <test rtl="True"><string><em>&#x0634;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0634;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x0634;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0634;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dadDotBelow + feh"> + <test rtl="True"><string><em>&#x06FB;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x06FB;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="zah + feh"> + <test rtl="True"><string><em>&#x0638;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x0638;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + feh3dots"> + <test rtl="True"><string><em>&#x0639;&#x06A5;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06A5;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x06A5;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06A5;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + feh3dots"> + <test rtl="True"><string><em>&#x0641;&#x06A5;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06A5;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x06A5;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06A5;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lamSmallV + feh"> + <test rtl="True"><string><em>&#x06B5;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x06B5;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + feh3dots"> + <test rtl="True"><string><em>&#x0645;&#x06A5;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06A5;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x06A5;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06A5;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ngoeh + feh"> + <test rtl="True"><string><em>&#x06B1;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B1;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x06B1;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B1;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + feh3dots"> + <test rtl="True"><string><em>&#x06BE;&#x06A5;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06A5;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x06A5;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06A5;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="arabicHeh + feh"> + <test rtl="True"><string><em>&#x0647;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0647;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x0647;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0647;&#x0641;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Qaf form sequences"> + <testgroup label="teh3down + qaf"> + <test rtl="True"><string><em>&#x067D;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067D;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="khah + qaf"> + <test rtl="True"><string><em>&#x062E;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062E;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen4dots + qaf"> + <test rtl="True"><string><em>&#x075C;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x075C;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="dadDotBelow + qaf"> + <test rtl="True"><string><em>&#x06FB;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="zah + qaf"> + <test rtl="True"><string><em>&#x0638;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + qaf"> + <test rtl="True"><string><em>&#x0639;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh3dots + qaf"> + <test rtl="True"><string><em>&#x06A5;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + qaf"> + <test rtl="True"><string><em>&#x0644;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + qaf"> + <test rtl="True"><string><em>&#x0645;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + qaf"> + <test rtl="True"><string><em>&#x06AF;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + qaf"> + <test rtl="True"><string><em>&#x06BE;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="arabicHeh + qaf"> + <test rtl="True"><string><em>&#x0647;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0647;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Lam form sequences"> + <testgroup label="teh3down + lam"> + <test rtl="True"><string><em>&#x067D;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067D;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x067D;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x067D;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hahTah2smd + lamSmallV"> + <test rtl="True"><string><em>&#x076F;&#x06B5;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076F;&#x06B5;</em></string></test> + <test rtl="True"><string><em>&#x076F;&#x06B5;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x076F;&#x06B5;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seenDotDot + lamBar"> + <test rtl="True"><string><em>&#x069A;&#x076A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x069A;&#x076A;</em></string></test> + <test rtl="True"><string><em>&#x069A;&#x076A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x069A;&#x076A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + lam"> + <test rtl="True"><string><em>&#x0635;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + lamSmallV"> + <test rtl="True"><string><em>&#x0637;&#x06B5;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06B5;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x06B5;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06B5;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ghain + lamBar"> + <test rtl="True"><string><em>&#x063A;&#x076A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x076A;</em></string></test> + <test rtl="True"><string><em>&#x063A;&#x076A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x076A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + lam"> + <test rtl="True"><string><em>&#x0641;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + lamBar"> + <test rtl="True"><string><em>&#x0644;&#x076A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x076A;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x076A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x076A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + lam"> + <test rtl="True"><string><em>&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kafRing + lamSmallV"> + <test rtl="True"><string><em>&#x06AB;&#x06B5;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AB;&#x06B5;</em></string></test> + <test rtl="True"><string><em>&#x06AB;&#x06B5;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AB;&#x06B5;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + lam"> + <test rtl="True"><string><em>&#x06BE;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehHamza + lamSmallV"> + <test rtl="True"><string><em>&#x06C2;&#x06B5;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x06B5;</em></string></test> + <test rtl="True"><string><em>&#x06C2;&#x06B5;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x06B5;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Meem form sequences"> + <testgroup label="beh + meem"> + <test rtl="True"><string><em>&#x0628;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + meem"> + <test rtl="True"><string><em>&#x062C;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen4dots + meem"> + <test rtl="True"><string><em>&#x075C;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x075C;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x075C;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x075C;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dad + meem"> + <test rtl="True"><string><em>&#x0636;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x0636;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="zah + meem"> + <test rtl="True"><string><em>&#x0638;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x0638;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + meem"> + <test rtl="True"><string><em>&#x0639;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh3dots + meem"> + <test rtl="True"><string><em>&#x06A5;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x06A5;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lamSmallV + meem"> + <test rtl="True"><string><em>&#x06B5;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x06B5;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + meem"> + <test rtl="True"><string><em>&#x0645;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + meem"> + <test rtl="True"><string><em>&#x06A9;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + meem"> + <test rtl="True"><string><em>&#x06BE;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + meem"> + <test rtl="True"><string><em>&#x06C1;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0645;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Alternate meem sequences"> + <testgroup label="theh + meem-alt + gaf"> + <test rtl="True"><string><em>&#x062B;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062B;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x062B;&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062B;&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tteh + meem-alt + lamBar"> + <test rtl="True"><string><em>&#x0679;&#x0645;&#x076A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0679;&#x0645;&#x076A;</em></string></test> + <test rtl="True"><string><em>&#x0679;&#x0645;&#x076A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0679;&#x0645;&#x076A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="theh + meem-alt + alef"> + <test rtl="True"><string><em>&#x062B;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062B;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tteh + meem-alt + thal"> + <test rtl="True"><string><em>&#x0679;&#x0645;&#x0630;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0679;&#x0645;&#x0630;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hahHamza + meem-alt + ngoeh"> + <test rtl="True"><string><em>&#x0681;&#x0645;&#x06B1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0681;&#x0645;&#x06B1;</em></string></test> + <test rtl="True"><string><em>&#x0681;&#x0645;&#x06B1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0681;&#x0645;&#x06B1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hah3dots + meem-alt + lam"> + <test rtl="True"><string><em>&#x0685;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0685;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0685;&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0685;&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hah + meem-alt + alef"> + <test rtl="True"><string><em>&#x062D;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062D;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="khah + meem-alt + dalDotTah"> + <test rtl="True"><string><em>&#x062E;&#x0645;&#x068B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062E;&#x0645;&#x068B;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sheen + meem-alt + kaf"> + <test rtl="True"><string><em>&#x0634;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0634;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0634;&#x0645;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0634;&#x0645;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seenDotDot + meem-alt + lamSmallV"> + <test rtl="True"><string><em>&#x069A;&#x0645;&#x06B5;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x069A;&#x0645;&#x06B5;</em></string></test> + <test rtl="True"><string><em>&#x069A;&#x0645;&#x06B5;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x069A;&#x0645;&#x06B5;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seenTah2smd + meem-alt + alef"> + <test rtl="True"><string><em>&#x0770;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0770;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen3dots3dots + meem-alt + dalRing"> + <test rtl="True"><string><em>&#x069C;&#x0645;&#x0689;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x069C;&#x0645;&#x0689;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="dadDotBelow + meem-alt + gueh"> + <test rtl="True"><string><em>&#x06FB;&#x0645;&#x06B3;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x0645;&#x06B3;</em></string></test> + <test rtl="True"><string><em>&#x06FB;&#x0645;&#x06B3;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x0645;&#x06B3;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + meem-alt + lamBar"> + <test rtl="True"><string><em>&#x0635;&#x0645;&#x076A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0645;&#x076A;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0645;&#x076A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0645;&#x076A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dadDotBelow + meem-alt + alef"> + <test rtl="True"><string><em>&#x06FB;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + meem-alt + dal"> + <test rtl="True"><string><em>&#x0635;&#x0645;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0645;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="zah + meem-alt + kafRing"> + <test rtl="True"><string><em>&#x0638;&#x0645;&#x06AB;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0645;&#x06AB;</em></string></test> + <test rtl="True"><string><em>&#x0638;&#x0645;&#x06AB;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0645;&#x06AB;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + meem-alt + lam"> + <test rtl="True"><string><em>&#x0637;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + meem-alt + alef"> + <test rtl="True"><string><em>&#x0637;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="zah + meem-alt + ddal"> + <test rtl="True"><string><em>&#x0638;&#x0645;&#x0688;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0645;&#x0688;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + meem-alt + gaf"> + <test rtl="True"><string><em>&#x0639;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ghain + meem-alt + lamSmallV"> + <test rtl="True"><string><em>&#x063A;&#x0645;&#x06B5;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0645;&#x06B5;</em></string></test> + <test rtl="True"><string><em>&#x063A;&#x0645;&#x06B5;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0645;&#x06B5;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ghain + meem-alt + alef"> + <test rtl="True"><string><em>&#x063A;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + meem-alt + dal2dotsVTah"> + <test rtl="True"><string><em>&#x0639;&#x0645;&#x0759;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;&#x0759;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh3dots + meem-alt + ngoeh"> + <test rtl="True"><string><em>&#x06A5;&#x0645;&#x06B1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x0645;&#x06B1;</em></string></test> + <test rtl="True"><string><em>&#x06A5;&#x0645;&#x06B1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x0645;&#x06B1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + meem-alt + lamBar"> + <test rtl="True"><string><em>&#x0641;&#x0645;&#x076A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0645;&#x076A;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0645;&#x076A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0645;&#x076A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + meem-alt + alef"> + <test rtl="True"><string><em>&#x0641;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh3dots + meem-alt + dal4dots"> + <test rtl="True"><string><em>&#x06A5;&#x0645;&#x0690;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x0645;&#x0690;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + meem-alt + gaf"> + <test rtl="True"><string><em>&#x0645;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + meem-alt + lamBar"> + <test rtl="True"><string><em>&#x0645;&#x0645;&#x076A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;&#x076A;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0645;&#x076A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;&#x076A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + meem-alt + alef"> + <test rtl="True"><string><em>&#x0645;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + meem-alt + ddal"> + <test rtl="True"><string><em>&#x0645;&#x0645;&#x0688;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;&#x0688;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + meem-alt + gaf"> + <test rtl="True"><string><em>&#x06BE;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + meem-alt + lamBar"> + <test rtl="True"><string><em>&#x06BE;&#x0645;&#x076A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;&#x076A;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0645;&#x076A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;&#x076A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + meem-alt + alef"> + <test rtl="True"><string><em>&#x06BE;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + meem-alt + dalRing"> + <test rtl="True"><string><em>&#x06BE;&#x0645;&#x0689;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;&#x0689;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="arabicHeh + meem-alt + ngoeh"> + <test rtl="True"><string><em>&#x0647;&#x0645;&#x06B1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0647;&#x0645;&#x06B1;</em></string></test> + <test rtl="True"><string><em>&#x0647;&#x0645;&#x06B1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0647;&#x0645;&#x06B1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehHamza + meem-alt + lam"> + <test rtl="True"><string><em>&#x06C2;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x06C2;&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehHamza + meem-alt + alef"> + <test rtl="True"><string><em>&#x06C2;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + meem-alt + dal"> + <test rtl="True"><string><em>&#x06C1;&#x0645;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0645;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Noon form sequences"> + <testgroup label="tteh + noon"> + <test rtl="True"><string><em>&#x0679;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0679;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hahHamza + noonRetro"> + <test rtl="True"><string><em>&#x0681;&#x0768;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0681;&#x0768;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + rnoon"> + <test rtl="True"><string><em>&#x0633;&#x06BB;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x06BB;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + noonGhunna"> + <test rtl="True"><string><em>&#x0635;&#x06BA;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x06BA;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="zah + noonDotBelow"> + <test rtl="True"><string><em>&#x0638;&#x06B9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x06B9;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + noon"> + <test rtl="True"><string><em>&#x0639;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh3dots + noonRetro"> + <test rtl="True"><string><em>&#x06A5;&#x0768;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x0768;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lamBar + rnoon"> + <test rtl="True"><string><em>&#x076A;&#x06BB;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076A;&#x06BB;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + noonGhunna"> + <test rtl="True"><string><em>&#x0645;&#x06BA;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06BA;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + noonDotBelow"> + <test rtl="True"><string><em>&#x06A9;&#x06B9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x06B9;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + noon"> + <test rtl="True"><string><em>&#x06BE;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehHamza + noonRetro"> + <test rtl="True"><string><em>&#x06C2;&#x0768;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x0768;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Waw form sequences"> + <testgroup label="beh + waw"> + <test rtl="True"><string><em>&#x0628;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tcheh + wawHamza"> + <test rtl="True"><string><em>&#x0686;&#x0624;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0686;&#x0624;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen2dotsV + wawRing"> + <test rtl="True"><string><em>&#x076D;&#x06C4;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076D;&#x06C4;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + waw"> + <test rtl="True"><string><em>&#x0635;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + wawHamza"> + <test rtl="True"><string><em>&#x0637;&#x0624;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0624;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ghain + wawRing"> + <test rtl="True"><string><em>&#x063A;&#x06C4;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x06C4;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + waw"> + <test rtl="True"><string><em>&#x0641;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lamBar + wawHamza"> + <test rtl="True"><string><em>&#x076A;&#x0624;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076A;&#x0624;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + wawRing"> + <test rtl="True"><string><em>&#x0645;&#x06C4;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06C4;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gueh + waw"> + <test rtl="True"><string><em>&#x06B3;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B3;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + wawHamza"> + <test rtl="True"><string><em>&#x06BE;&#x0624;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0624;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehHamza + wawRing"> + <test rtl="True"><string><em>&#x06C2;&#x06C4;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x06C4;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Kaf form sequences"> + <testgroup label="teh + kaf"> + <test rtl="True"><string><em>&#x062A;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hah + gueh"> + <test rtl="True"><string><em>&#x062D;&#x06B3;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062D;&#x06B3;</em></string></test> + <test rtl="True"><string><em>&#x062D;&#x06B3;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062D;&#x06B3;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen2dotsV + kafRing"> + <test rtl="True"><string><em>&#x076D;&#x06AB;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076D;&#x06AB;</em></string></test> + <test rtl="True"><string><em>&#x076D;&#x06AB;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x076D;&#x06AB;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dadDotBelow + gaf"> + <test rtl="True"><string><em>&#x06FB;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x06FB;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + ngoeh"> + <test rtl="True"><string><em>&#x0637;&#x06B1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06B1;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x06B1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06B1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ghain + kaf"> + <test rtl="True"><string><em>&#x063A;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x063A;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + gueh"> + <test rtl="True"><string><em>&#x0641;&#x06B3;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06B3;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x06B3;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06B3;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + kafRing"> + <test rtl="True"><string><em>&#x0644;&#x06AB;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x06AB;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x06AB;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x06AB;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + kaf"> + <test rtl="True"><string><em>&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + gueh"> + <test rtl="True"><string><em>&#x06AF;&#x06B3;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x06B3;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x06B3;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x06B3;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + kaf"> + <test rtl="True"><string><em>&#x06BE;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="arabicHeh + gueh"> + <test rtl="True"><string><em>&#x0647;&#x06B3;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0647;&#x06B3;</em></string></test> + <test rtl="True"><string><em>&#x0647;&#x06B3;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0647;&#x06B3;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Heh-Doachashmee form sequences"> + <testgroup label="theh + hehDo"> + <test rtl="True"><string><em>&#x062B;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062B;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x062B;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062B;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="khah + hehDo"> + <test rtl="True"><string><em>&#x062E;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062E;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x062E;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062E;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seenTah2smd + hehDo"> + <test rtl="True"><string><em>&#x0770;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0770;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x0770;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0770;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + hehDo"> + <test rtl="True"><string><em>&#x0635;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="zah + hehDo"> + <test rtl="True"><string><em>&#x0638;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x0638;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + hehDo"> + <test rtl="True"><string><em>&#x0639;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh3dots + hehDo"> + <test rtl="True"><string><em>&#x06A5;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x06A5;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lamBar + hehDo"> + <test rtl="True"><string><em>&#x076A;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076A;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x076A;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x076A;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + hehDo"> + <test rtl="True"><string><em>&#x0645;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ngoeh + hehDo"> + <test rtl="True"><string><em>&#x06B1;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B1;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x06B1;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B1;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + hehDo"> + <test rtl="True"><string><em>&#x06BE;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehHamza + hehDo"> + <test rtl="True"><string><em>&#x06C2;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x06C2;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Heh-Goal form sequences"> + <testgroup label="tteh + hehGoal"> + <test rtl="True"><string><em>&#x0679;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0679;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x0679;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0679;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tcheh + hehHamza"> + <test rtl="True"><string><em>&#x0686;&#x06C2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0686;&#x06C2;</em></string></test> + <test rtl="True"><string><em>&#x0686;&#x06C2;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0686;&#x06C2;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen3dots3dots + arabicHeh"> + <test rtl="True"><string><em>&#x069C;&#x0647;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x069C;&#x0647;</em></string></test> + <test rtl="True"><string><em>&#x069C;&#x0647;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x069C;&#x0647;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dad + hehGoal"> + <test rtl="True"><string><em>&#x0636;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x0636;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + hehHamza"> + <test rtl="True"><string><em>&#x0637;&#x06C2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06C2;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x06C2;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06C2;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ghain + arabicHeh"> + <test rtl="True"><string><em>&#x063A;&#x0647;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0647;</em></string></test> + <test rtl="True"><string><em>&#x063A;&#x0647;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0647;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + hehGoal"> + <test rtl="True"><string><em>&#x0641;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lamSmallV + hehHamza"> + <test rtl="True"><string><em>&#x06B5;&#x06C2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x06C2;</em></string></test> + <test rtl="True"><string><em>&#x06B5;&#x06C2;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x06C2;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + arabicHeh"> + <test rtl="True"><string><em>&#x0645;&#x0647;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0647;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0647;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0647;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kafRing + hehGoal"> + <test rtl="True"><string><em>&#x06AB;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AB;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x06AB;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AB;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + hehHamza"> + <test rtl="True"><string><em>&#x06BE;&#x06C2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06C2;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x06C2;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06C2;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + arabicHeh"> + <test rtl="True"><string><em>&#x06C1;&#x0647;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0647;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0647;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0647;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Teh-marbuta form sequences"> + <testgroup label="peh + tehMarbuta"> + <test rtl="True"><string><em>&#x067E;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067E;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hah3dots + tehMarbuta"> + <test rtl="True"><string><em>&#x0685;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0685;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sheen + tehMarbuta"> + <test rtl="True"><string><em>&#x0634;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0634;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="dad + tehMarbuta"> + <test rtl="True"><string><em>&#x0636;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + tehMarbuta"> + <test rtl="True"><string><em>&#x0637;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ghain + tehMarbuta"> + <test rtl="True"><string><em>&#x063A;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + tehMarbuta"> + <test rtl="True"><string><em>&#x0641;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lamSmallV + tehMarbuta"> + <test rtl="True"><string><em>&#x06B5;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + tehMarbuta"> + <test rtl="True"><string><em>&#x0645;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + tehMarbuta"> + <test rtl="True"><string><em>&#x06AF;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + tehMarbuta"> + <test rtl="True"><string><em>&#x06BE;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + tehMarbuta"> + <test rtl="True"><string><em>&#x06C1;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Chotiyeh form sequences"> + <testgroup label="theh + chotiyeh"> + <test rtl="True"><string><em>&#x062B;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062B;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tcheheh + yehHamza"> + <test rtl="True"><string><em>&#x0687;&#x0626;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0687;&#x0626;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen3dots3dots + yehSmallV"> + <test rtl="True"><string><em>&#x069C;&#x06CE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x069C;&#x06CE;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="dadDotBelow + arabicE"> + <test rtl="True"><string><em>&#x06FB;&#x06D0;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x06D0;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + chotiyeh"> + <test rtl="True"><string><em>&#x0637;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ghain + yehHamza"> + <test rtl="True"><string><em>&#x063A;&#x0626;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0626;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + yehSmallV"> + <test rtl="True"><string><em>&#x0641;&#x06CE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06CE;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + arabicE"> + <test rtl="True"><string><em>&#x0644;&#x06D0;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x06D0;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + chotiyeh"> + <test rtl="True"><string><em>&#x0645;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kafRing + yehHamza"> + <test rtl="True"><string><em>&#x06AB;&#x0626;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AB;&#x0626;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + yehSmallV"> + <test rtl="True"><string><em>&#x06BE;&#x06CE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06CE;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="arabicHeh + arabicE"> + <test rtl="True"><string><em>&#x0647;&#x06D0;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0647;&#x06D0;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Bariyeh form sequences"> + <testgroup label="teh + bariyeh"> + <test rtl="True"><string><em>&#x062A;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="dyeh + bariyeh"> + <test rtl="True"><string><em>&#x0684;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0684;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seenTah2smd + bariyeh"> + <test rtl="True"><string><em>&#x0770;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0770;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="dad + bariyeh"> + <test rtl="True"><string><em>&#x0636;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="zah + bariyeh"> + <test rtl="True"><string><em>&#x0638;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + bariyeh"> + <test rtl="True"><string><em>&#x0639;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh3dots + bariyeh"> + <test rtl="True"><string><em>&#x06A5;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lamSmallV + bariyeh"> + <test rtl="True"><string><em>&#x06B5;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + bariyeh"> + <test rtl="True"><string><em>&#x0645;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ngoeh + bariyeh"> + <test rtl="True"><string><em>&#x06B1;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B1;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + bariyeh"> + <test rtl="True"><string><em>&#x06BE;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + bariyeh"> + <test rtl="True"><string><em>&#x06C1;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Multiple Beh sequences"> + <testgroup label="peh + tteheh + beeh"> + <test rtl="True"><string><em>&#x067E;&#x067A;&#x067B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067E;&#x067A;&#x067B;</em></string></test> + <test rtl="True"><string><em>&#x067E;&#x067A;&#x067B;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x067E;&#x067A;&#x067B;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tehRing + teh3down + beh + teh"> + <test rtl="True"><string><em>&#x067C;&#x067D;&#x0628;&#x062A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067C;&#x067D;&#x0628;&#x062A;</em></string></test> + <test rtl="True"><string><em>&#x067C;&#x067D;&#x0628;&#x062A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x067C;&#x067D;&#x0628;&#x062A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dyeh + tteheh + beeh"> + <test rtl="True"><string><em>&#x0684;&#x067A;&#x067B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0684;&#x067A;&#x067B;</em></string></test> + <test rtl="True"><string><em>&#x0684;&#x067A;&#x067B;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0684;&#x067A;&#x067B;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tcheheh + tehRing + teh3down + beh"> + <test rtl="True"><string><em>&#x0687;&#x067C;&#x067D;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0687;&#x067C;&#x067D;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0687;&#x067C;&#x067D;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0687;&#x067C;&#x067D;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + tteheh + beeh"> + <test rtl="True"><string><em>&#x0633;&#x067A;&#x067B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x067A;&#x067B;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x067A;&#x067B;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x067A;&#x067B;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sheen + tehRing + teh3down + beh"> + <test rtl="True"><string><em>&#x0634;&#x067C;&#x067D;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0634;&#x067C;&#x067D;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0634;&#x067C;&#x067D;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0634;&#x067C;&#x067D;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dadDotBelow + tteh + peh"> + <test rtl="True"><string><em>&#x06FB;&#x0679;&#x067E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x0679;&#x067E;</em></string></test> + <test rtl="True"><string><em>&#x06FB;&#x0679;&#x067E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x0679;&#x067E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + tteheh + beeh + tehRing"> + <test rtl="True"><string><em>&#x0635;&#x067A;&#x067B;&#x067C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x067A;&#x067B;&#x067C;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x067A;&#x067B;&#x067C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x067A;&#x067B;&#x067C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="zah + teh + theh"> + <test rtl="True"><string><em>&#x0638;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string><em>&#x0638;&#x062A;&#x062B;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x062A;&#x062B;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + tteh + peh + tteheh"> + <test rtl="True"><string><em>&#x0637;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + teh + theh"> + <test rtl="True"><string><em>&#x0639;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x062A;&#x062B;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x062A;&#x062B;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ghain + tteh + peh + tteheh"> + <test rtl="True"><string><em>&#x063A;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string><em>&#x063A;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh3dots + teh + theh"> + <test rtl="True"><string><em>&#x06A5;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string><em>&#x06A5;&#x062A;&#x062B;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x062A;&#x062B;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + tteh + peh + tteheh"> + <test rtl="True"><string><em>&#x0641;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + teh + theh"> + <test rtl="True"><string><em>&#x0644;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x062A;&#x062B;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x062A;&#x062B;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lamBar + tteh + peh + tteheh"> + <test rtl="True"><string><em>&#x076A;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076A;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string><em>&#x076A;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x076A;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + teh + theh"> + <test rtl="True"><string><em>&#x0645;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x062A;&#x062B;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x062A;&#x062B;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + tteh + peh + tteheh"> + <test rtl="True"><string><em>&#x0645;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + teh + theh"> + <test rtl="True"><string><em>&#x06A9;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x062A;&#x062B;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x062A;&#x062B;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + tteh + peh + tteheh"> + <test rtl="True"><string><em>&#x06AF;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + teh + theh"> + <test rtl="True"><string><em>&#x06BE;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x062A;&#x062B;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x062A;&#x062B;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + tteh + peh + tteheh"> + <test rtl="True"><string><em>&#x06BE;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehHamza + teh + theh"> + <test rtl="True"><string><em>&#x06C2;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x062A;&#x062B;</em></string></test> + <test rtl="True"><string><em>&#x06C2;&#x062A;&#x062B;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x062A;&#x062B;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + tteh + peh + tteheh"> + <test rtl="True"><string><em>&#x06C1;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0679;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0679;&#x067E;&#x067A;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Seen/Sad + Beh sequences"> + <testgroup label="seenDotDot + teh + sad"> + <test rtl="True"><string><em>&#x069A;&#x062A;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x069A;&#x062A;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x069A;&#x062A;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x069A;&#x062A;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen4dots + theh + zah"> + <test rtl="True"><string><em>&#x075C;&#x062B;&#x0638;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x075C;&#x062B;&#x0638;</em></string></test> + <test rtl="True"><string><em>&#x075C;&#x062B;&#x0638;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x075C;&#x062B;&#x0638;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen2dotsV + tteh + ghain"> + <test rtl="True"><string><em>&#x076D;&#x0679;&#x063A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076D;&#x0679;&#x063A;</em></string></test> + <test rtl="True"><string><em>&#x076D;&#x0679;&#x063A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x076D;&#x0679;&#x063A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seenTah2smd + peh + feh3dots"> + <test rtl="True"><string><em>&#x0770;&#x067E;&#x06A5;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0770;&#x067E;&#x06A5;</em></string></test> + <test rtl="True"><string><em>&#x0770;&#x067E;&#x06A5;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0770;&#x067E;&#x06A5;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen3dots3dots + tteheh + qaf"> + <test rtl="True"><string><em>&#x069C;&#x067A;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x069C;&#x067A;&#x0642;</em></string></test> + <test rtl="True"><string><em>&#x069C;&#x067A;&#x0642;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x069C;&#x067A;&#x0642;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + beeh + tehRing"> + <test rtl="True"><string><em>&#x0633;&#x067B;&#x067C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x067B;&#x067C;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x067B;&#x067C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x067B;&#x067C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dad + teh3down + dadDotBelow"> + <test rtl="True"><string><em>&#x0636;&#x067D;&#x06FB;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x067D;&#x06FB;</em></string></test> + <test rtl="True"><string><em>&#x0636;&#x067D;&#x06FB;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x067D;&#x06FB;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + beh + zah"> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0638;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0638;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0638;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0638;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dad + teh + ghain"> + <test rtl="True"><string><em>&#x0636;&#x062A;&#x063A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x062A;&#x063A;</em></string></test> + <test rtl="True"><string><em>&#x0636;&#x062A;&#x063A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x062A;&#x063A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dadDotBelow + theh + feh3dots"> + <test rtl="True"><string><em>&#x06FB;&#x062B;&#x06A5;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x062B;&#x06A5;</em></string></test> + <test rtl="True"><string><em>&#x06FB;&#x062B;&#x06A5;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x062B;&#x06A5;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + tteh + qaf"> + <test rtl="True"><string><em>&#x0635;&#x0679;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0679;&#x0642;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0679;&#x0642;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0679;&#x0642;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dad + peh + tteheh"> + <test rtl="True"><string><em>&#x0636;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x067E;&#x067A;</em></string></test> + <test rtl="True"><string><em>&#x0636;&#x067E;&#x067A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x067E;&#x067A;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Beh + Reh sequences"> + <testgroup label="tehRing + teh3down + zain"> + <test rtl="True"><string><em>&#x067C;&#x067D;&#x0632;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067C;&#x067D;&#x0632;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hahTah2smd + theh + jeh"> + <test rtl="True"><string><em>&#x076F;&#x062B;&#x0698;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076F;&#x062B;&#x0698;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen4dots + beh + rehDotDot"> + <test rtl="True"><string><em>&#x075C;&#x0628;&#x0696;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x075C;&#x0628;&#x0696;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + tehRing + reh4dots"> + <test rtl="True"><string><em>&#x0635;&#x067C;&#x0699;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x067C;&#x0699;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + tehRing + rehRing"> + <test rtl="True"><string><em>&#x0637;&#x067C;&#x0693;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x067C;&#x0693;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ghain + tehRing + reh2dotsV"> + <test rtl="True"><string><em>&#x063A;&#x067C;&#x076B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x063A;&#x067C;&#x076B;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + tehRing + zain"> + <test rtl="True"><string><em>&#x0641;&#x067C;&#x0632;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x067C;&#x0632;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lamBar + tehRing + jeh"> + <test rtl="True"><string><em>&#x076A;&#x067C;&#x0698;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076A;&#x067C;&#x0698;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + tehRing + rehDotDot"> + <test rtl="True"><string><em>&#x0645;&#x067C;&#x0696;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x067C;&#x0696;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ngoeh + tehRing + reh4dots"> + <test rtl="True"><string><em>&#x06B1;&#x067C;&#x0699;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B1;&#x067C;&#x0699;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + tehRing + rehRing"> + <test rtl="True"><string><em>&#x06BE;&#x067C;&#x0693;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x067C;&#x0693;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehHamza + tehRing + reh2dotsV"> + <test rtl="True"><string><em>&#x06C2;&#x067C;&#x076B;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C2;&#x067C;&#x076B;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Beh + Noon sequences"> + <testgroup label="tteheh + beeh + noonGhunna"> + <test rtl="True"><string><em>&#x067A;&#x067B;&#x06BA;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067A;&#x067B;&#x06BA;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hahTah + teh + noonDotBelow"> + <test rtl="True"><string><em>&#x076E;&#x062A;&#x06B9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076E;&#x062A;&#x06B9;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seenDotDot + teh3down + noon"> + <test rtl="True"><string><em>&#x069A;&#x067D;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x069A;&#x067D;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="dadDotBelow + beeh + noonRetro"> + <test rtl="True"><string><em>&#x06FB;&#x067B;&#x0768;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06FB;&#x067B;&#x0768;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="zah + beeh + rnoon"> + <test rtl="True"><string><em>&#x0638;&#x067B;&#x06BB;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x067B;&#x06BB;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + beeh + noonGhunna"> + <test rtl="True"><string><em>&#x0639;&#x067B;&#x06BA;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x067B;&#x06BA;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh3dots + beeh + noonDotBelow"> + <test rtl="True"><string><em>&#x06A5;&#x067B;&#x06B9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x067B;&#x06B9;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + beeh + noon"> + <test rtl="True"><string><em>&#x0644;&#x067B;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x067B;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + beeh + noonRetro"> + <test rtl="True"><string><em>&#x0645;&#x067B;&#x0768;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x067B;&#x0768;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gueh + beeh + rnoon"> + <test rtl="True"><string><em>&#x06B3;&#x067B;&#x06BB;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B3;&#x067B;&#x06BB;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + beeh + noonGhunna"> + <test rtl="True"><string><em>&#x06BE;&#x067B;&#x06BA;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x067B;&#x06BA;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="arabicHeh + beeh + noonDotBelow"> + <test rtl="True"><string><em>&#x0647;&#x067B;&#x06B9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0647;&#x067B;&#x06B9;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Beh + Heh-Goal sequences"> + <testgroup label="beh + teh + arabicHeh"> + <test rtl="True"><string><em>&#x0628;&#x062A;&#x0647;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x062A;&#x0647;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + tteh + hehGoal"> + <test rtl="True"><string><em>&#x062C;&#x0679;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0679;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen2dotsV + teh + hehHamza"> + <test rtl="True"><string><em>&#x076D;&#x062A;&#x06C2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076D;&#x062A;&#x06C2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="dad + teh3down + arabicHeh"> + <test rtl="True"><string><em>&#x0636;&#x067D;&#x0647;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x067D;&#x0647;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="zah + teh3down + hehGoal"> + <test rtl="True"><string><em>&#x0638;&#x067D;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x067D;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + teh3down + hehHamza"> + <test rtl="True"><string><em>&#x0639;&#x067D;&#x06C2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x067D;&#x06C2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh3dots + teh3down + arabicHeh"> + <test rtl="True"><string><em>&#x06A5;&#x067D;&#x0647;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x067D;&#x0647;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lamSmallV + teh3down + hehGoal"> + <test rtl="True"><string><em>&#x06B5;&#x067D;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x067D;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + teh3down + hehHamza"> + <test rtl="True"><string><em>&#x0645;&#x067D;&#x06C2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x067D;&#x06C2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kafRing + teh3down + arabicHeh"> + <test rtl="True"><string><em>&#x06AB;&#x067D;&#x0647;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AB;&#x067D;&#x0647;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + teh3down + hehGoal"> + <test rtl="True"><string><em>&#x06BE;&#x067D;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x067D;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + teh3down + arabicHeh"> + <test rtl="True"><string><em>&#x06C1;&#x067D;&#x0647;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x067D;&#x0647;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Seen + seen + seen sequences"> + <testgroup label="peh + sheen + seenDotDot + seen4dots"> + <test rtl="True"><string><em>&#x067E;&#x0634;&#x069A;&#x075C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x067E;&#x0634;&#x069A;&#x075C;</em></string></test> + <test rtl="True"><string><em>&#x067E;&#x0634;&#x069A;&#x075C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x067E;&#x0634;&#x069A;&#x075C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hahTah + seenTah2smd + seen3dots3dots + seen"> + <test rtl="True"><string><em>&#x076E;&#x0770;&#x069C;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x076E;&#x0770;&#x069C;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x076E;&#x0770;&#x069C;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x076E;&#x0770;&#x069C;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen4dots + seen2dotsV + seenTah2smd + seen3dots3dots"> + <test rtl="True"><string><em>&#x075C;&#x076D;&#x0770;&#x069C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x075C;&#x076D;&#x0770;&#x069C;</em></string></test> + <test rtl="True"><string><em>&#x075C;&#x076D;&#x0770;&#x069C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x075C;&#x076D;&#x0770;&#x069C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="dad + sheen + seenDotDot + seen4dots"> + <test rtl="True"><string><em>&#x0636;&#x0634;&#x069A;&#x075C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x0634;&#x069A;&#x075C;</em></string></test> + <test rtl="True"><string><em>&#x0636;&#x0634;&#x069A;&#x075C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0636;&#x0634;&#x069A;&#x075C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="zah + seenTah2smd + seen3dots3dots + seen"> + <test rtl="True"><string><em>&#x0638;&#x0770;&#x069C;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0770;&#x069C;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0638;&#x0770;&#x069C;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0638;&#x0770;&#x069C;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + seenDotDot + seen4dots + seen2dotsV"> + <test rtl="True"><string><em>&#x0639;&#x069A;&#x075C;&#x076D;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x069A;&#x075C;&#x076D;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x069A;&#x075C;&#x076D;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x069A;&#x075C;&#x076D;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh3dots + seen3dots3dots + seen + sheen"> + <test rtl="True"><string><em>&#x06A5;&#x069C;&#x0633;&#x0634;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x069C;&#x0633;&#x0634;</em></string></test> + <test rtl="True"><string><em>&#x06A5;&#x069C;&#x0633;&#x0634;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A5;&#x069C;&#x0633;&#x0634;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lamSmallV + seen4dots + seen2dotsV + seenTah2smd"> + <test rtl="True"><string><em>&#x06B5;&#x075C;&#x076D;&#x0770;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x075C;&#x076D;&#x0770;</em></string></test> + <test rtl="True"><string><em>&#x06B5;&#x075C;&#x076D;&#x0770;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B5;&#x075C;&#x076D;&#x0770;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + seen + sheen + seenDotDot"> + <test rtl="True"><string><em>&#x0645;&#x0633;&#x0634;&#x069A;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0633;&#x0634;&#x069A;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0633;&#x0634;&#x069A;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0633;&#x0634;&#x069A;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gueh + seen2dotsV + seenTah2smd + seen3dots3dots"> + <test rtl="True"><string><em>&#x06B3;&#x076D;&#x0770;&#x069C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B3;&#x076D;&#x0770;&#x069C;</em></string></test> + <test rtl="True"><string><em>&#x06B3;&#x076D;&#x0770;&#x069C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06B3;&#x076D;&#x0770;&#x069C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + sheen + seenDotDot + seen4dots"> + <test rtl="True"><string><em>&#x06BE;&#x0634;&#x069A;&#x075C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0634;&#x069A;&#x075C;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0634;&#x069A;&#x075C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0634;&#x069A;&#x075C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + seenTah2smd + seen3dots3dots + seen"> + <test rtl="True"><string><em>&#x06C1;&#x0770;&#x069C;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0770;&#x069C;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0770;&#x069C;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0770;&#x069C;&#x0633;</em>&#x0641;</string></test> + </testgroup> + </testgroup> +</ftml> diff --git a/text/test data/letter combinations/test_basic_somediac.pdf b/text/test data/letter combinations/test_basic_somediac.pdf Binary files differ. diff --git a/text/test data/letter combinations/test_basic_somediac.xml b/text/test data/letter combinations/test_basic_somediac.xml @@ -0,0 +1,6792 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet type="text/xsl" href="ftml.xsl"?> +<ftml version="1.0"> + <head> + <columns comment="15%" label="20%" string="15%"/> + <description>Test of Awami - Basic Forms with Diacritics</description> + <fontscale>200</fontscale> + <fontsrc>local('Awami Nastaliq Beta3'), url(Awami_beta3.ttf)</fontsrc> + <title>Test of Awami - Basic Forms with Diacritics</title> + <styles><style feats=' ' name="default"/></styles> + </head> + <testgroup label="Alef form sequences with Zabar"> + <testgroup label="teh + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Alef form sequences with Zair"> + <testgroup label="teh + zair + alef + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + alef + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + alef + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + alef + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + alef + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + alef + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + alef + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + alef + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + alef + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + alef + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + alef + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + alef + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + alef + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + alef + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Beh form sequences with Zabar"> + <testgroup label="teh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Beh form sequences with Zair"> + <testgroup label="teh + zair + beh + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + beh + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + beh + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + beh + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + beh + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + beh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + beh + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + beh + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + beh + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + beh + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + beh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + beh + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Teh form sequences with Zabar"> + <testgroup label="teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Teh form sequences with Zair"> + <testgroup label="teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + teh + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + teh + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + teh + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + teh + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + teh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + teh + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + teh + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + teh + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + teh + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + teh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + teh + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Jeem form sequences with Zabar"> + <testgroup label="teh + zabar + jeem + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + jeem + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + jeem + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + jeem + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + jeem + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + jeem + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + jeem + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + jeem + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + jeem + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + jeem + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + jeem + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + jeem + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + jeem + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + jeem + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x062C;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x062C;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Jeem form sequences with Zair"> + <testgroup label="teh + zair + jeem + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + jeem + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + jeem + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + jeem + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + jeem + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + jeem + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + jeem + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + jeem + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + jeem + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + jeem + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + jeem + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + jeem + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + jeem + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + jeem + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x062C;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x062C;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Dal form sequences with Zabar"> + <testgroup label="teh + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Dal form sequences with Zair"> + <testgroup label="teh + zair + dal + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + dal + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + dal + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + dal + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + dal + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + dal + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + dal + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + dal + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + dal + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + dal + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + dal + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + dal + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + dal + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + dal + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Reh form sequences with Zabar"> + <testgroup label="teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Reh form sequences with Zair"> + <testgroup label="teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + reh + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + reh + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + reh + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + reh + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + reh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + reh + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + reh + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + reh + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + reh + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + reh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + reh + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Seen form sequences with Zabar"> + <testgroup label="teh + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Seen form sequences with Zair"> + <testgroup label="teh + zair + seen + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + seen + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + seen + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + seen + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + seen + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + seen + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + seen + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + seen + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + seen + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + seen + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + seen + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + seen + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + seen + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Sad form sequences with Zabar"> + <testgroup label="teh + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Sad form sequences with Zair"> + <testgroup label="teh + zair + sad + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + sad + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + sad + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + sad + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + sad + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + sad + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + sad + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + sad + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + sad + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + sad + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + sad + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + sad + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + sad + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + sad + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Tah form sequences with Zabar"> + <testgroup label="teh + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Tah form sequences with Zair"> + <testgroup label="teh + zair + tah + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + tah + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + tah + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + tah + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + tah + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + tah + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + tah + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + tah + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + tah + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + tah + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + tah + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + tah + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + tah + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + tah + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Ain form sequences with Zabar"> + <testgroup label="teh + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Ain form sequences with Zair"> + <testgroup label="teh + zair + ain + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + ain + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + ain + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + ain + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + ain + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + ain + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + ain + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + ain + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + ain + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + ain + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + ain + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + ain + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + ain + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + ain + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Feh form sequences with Zabar"> + <testgroup label="teh + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Feh form sequences with Zair"> + <testgroup label="teh + zair + feh + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + feh + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + feh + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + feh + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + feh + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + feh + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + feh + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + feh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + feh + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + feh + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + feh + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + feh + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + feh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + feh + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Qaf form sequences with Zabar"> + <testgroup label="teh + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Qaf form sequences with Zair"> + <testgroup label="teh + zair + qaf + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + qaf + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + qaf + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + qaf + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + qaf + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + qaf + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + qaf + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + qaf + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + qaf + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + qaf + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + qaf + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + qaf + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + qaf + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + qaf + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Lam form sequences with Zabar"> + <testgroup label="teh + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Lam form sequences with Zair"> + <testgroup label="teh + zair + lam + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + lam + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + lam + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + lam + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + lam + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + lam + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + lam + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + lam + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + lam + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + lam + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + lam + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + lam + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + lam + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + lam + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Meem form sequences with Zabar"> + <testgroup label="teh + zabar + meem + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + meem + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + meem + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + meem + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + meem + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + meem + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + meem + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + meem + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + meem + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + meem + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + meem + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + meem + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + meem + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + meem + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0645;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0645;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Meem form sequences with Zair"> + <testgroup label="teh + zair + meem + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + meem + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + meem + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + meem + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + meem + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + meem + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + meem + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + meem + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + meem + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + meem + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + meem + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + meem + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + meem + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + meem + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0645;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0645;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Alternate meem sequences with Zabar"> + <testgroup label="teh + zabar + meem-alt + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="teh + zabar + meem-alt + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="teh + zabar + meem-alt + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="teh + zabar + meem-alt + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="teh + zabar + meem-alt + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + meem-alt + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + meem-alt + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + meem-alt + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + meem-alt + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + meem-alt + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + meem-alt + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + meem-alt + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + meem-alt + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + meem-alt + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + meem-alt + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + meem-alt + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + meem-alt + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + meem-alt + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + meem-alt + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + meem-alt + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + meem-alt + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + meem-alt + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + meem-alt + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + meem-alt + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + meem-alt + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + meem-alt + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + meem-alt + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + meem-alt + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + meem-alt + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + meem-alt + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + meem-alt + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + meem-alt + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + meem-alt + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + meem-alt + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + meem-alt + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + meem-alt + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + meem-alt + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + meem-alt + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + meem-alt + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + meem-alt + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + meem-alt + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + meem-alt + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + meem-alt + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + meem-alt + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + meem-alt + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + meem-alt + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + meem-alt + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + meem-alt + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + meem-alt + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + meem-alt + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + meem-alt + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + meem-alt + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + meem-alt + zabar + lam + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0645;&#x064E;&#x0644;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + meem-alt + zabar + alef + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0645;&#x064E;&#x0627;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + meem-alt + zabar + dal + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0645;&#x064E;&#x062F;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Alternate meem sequences with Zair"> + <testgroup label="teh + zair + meem-alt + zair + kaf + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="teh + zair + meem-alt + zair + gaf + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="teh + zair + meem-alt + zair + lam + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="teh + zair + meem-alt + zair + alef + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="teh + zair + meem-alt + zair + dal + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + meem-alt + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + meem-alt + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + meem-alt + zair + lam + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + meem-alt + zair + alef + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + meem-alt + zair + dal + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + meem-alt + zair + kaf + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + meem-alt + zair + gaf + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + meem-alt + zair + lam + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + meem-alt + zair + alef + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + meem-alt + zair + dal + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + meem-alt + zair + kaf + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + meem-alt + zair + gaf + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + meem-alt + zair + lam + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + meem-alt + zair + alef + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + meem-alt + zair + dal + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + meem-alt + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + meem-alt + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + meem-alt + zair + lam + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + meem-alt + zair + alef + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + meem-alt + zair + dal + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + meem-alt + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + meem-alt + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + meem-alt + zair + lam + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + meem-alt + zair + alef + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + meem-alt + zair + dal + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + meem-alt + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + meem-alt + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + meem-alt + zair + lam + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + meem-alt + zair + alef + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + meem-alt + zair + dal + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + meem-alt + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + meem-alt + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + meem-alt + zair + lam + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + meem-alt + zair + alef + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + meem-alt + zair + dal + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + meem-alt + zair + kaf + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + meem-alt + zair + gaf + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + meem-alt + zair + lam + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + meem-alt + zair + alef + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + meem-alt + zair + dal + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + meem-alt + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + meem-alt + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + meem-alt + zair + lam + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + meem-alt + zair + alef + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + meem-alt + zair + dal + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + meem-alt + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + meem-alt + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + meem-alt + zair + lam + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0645;&#x0650;&#x0644;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + meem-alt + zair + alef + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0645;&#x0650;&#x0627;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + meem-alt + zair + dal + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0645;&#x0650;&#x062F;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Noon form sequences with Zabar"> + <testgroup label="teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Noon form sequences with Zair"> + <testgroup label="teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + noon + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + noon + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + noon + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + noon + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + noon + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + noon + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + noon + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + noon + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + noon + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + noon + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + noon + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Waw form sequences with Zabar"> + <testgroup label="teh + zabar + waw + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + waw + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + waw + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + waw + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + waw + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + waw + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + waw + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + waw + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + waw + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + waw + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + waw + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + waw + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + waw + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + waw + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0648;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Waw form sequences with Zair"> + <testgroup label="teh + zair + waw + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + waw + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + waw + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + waw + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + waw + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + waw + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + waw + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + waw + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + waw + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + waw + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + waw + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + waw + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + waw + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + waw + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0648;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Kaf form sequences with Zabar"> + <testgroup label="teh + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + kaf + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x06A9;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x06A9;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Kaf form sequences with Zair"> + <testgroup label="teh + zair + kaf + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + kaf + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + kaf + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + kaf + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + kaf + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + kaf + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + kaf + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x06A9;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x06A9;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Gaf form sequences with Zabar"> + <testgroup label="teh + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + gaf + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x06AF;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x06AF;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Gaf form sequences with Zair"> + <testgroup label="teh + zair + gaf + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + gaf + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + gaf + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + gaf + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + gaf + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + gaf + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + gaf + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x06AF;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x06AF;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Heh-Doachashmee form sequences with Zabar"> + <testgroup label="teh + zabar + hehDo + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + hehDo + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + hehDo + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + hehDo + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + hehDo + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + hehDo + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + hehDo + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + hehDo + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + hehDo + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + hehDo + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + hehDo + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + hehDo + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + hehDo + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + hehDo + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x06BE;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x06BE;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Heh-Doachashmee form sequences with Zair"> + <testgroup label="teh + zair + hehDo + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + hehDo + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + hehDo + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + hehDo + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + hehDo + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + hehDo + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + hehDo + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + hehDo + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + hehDo + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + hehDo + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + hehDo + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + hehDo + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + hehDo + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + hehDo + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x06BE;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x06BE;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Heh-Goal form sequences with Zabar"> + <testgroup label="teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x06C1;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Heh-Goal form sequences with Zair"> + <testgroup label="teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x06C1;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Teh-marbuta form sequences with Zabar"> + <testgroup label="teh + zabar + tehMarbuta + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + tehMarbuta + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + tehMarbuta + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + tehMarbuta + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + tehMarbuta + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + tehMarbuta + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + tehMarbuta + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + tehMarbuta + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + tehMarbuta + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + tehMarbuta + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + tehMarbuta + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + tehMarbuta + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + tehMarbuta + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + tehMarbuta + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0629;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Teh-marbuta form sequences with Zair"> + <testgroup label="teh + zair + tehMarbuta + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + tehMarbuta + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + tehMarbuta + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + tehMarbuta + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + tehMarbuta + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + tehMarbuta + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + tehMarbuta + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + tehMarbuta + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + tehMarbuta + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + tehMarbuta + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + tehMarbuta + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + tehMarbuta + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + tehMarbuta + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + tehMarbuta + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0629;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Chotiyeh form sequences with Zabar"> + <testgroup label="teh + zabar + chotiyeh + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + chotiyeh + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + chotiyeh + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + chotiyeh + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + chotiyeh + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + chotiyeh + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + chotiyeh + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + chotiyeh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + chotiyeh + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + chotiyeh + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + chotiyeh + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + chotiyeh + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + chotiyeh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + chotiyeh + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x06CC;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Chotiyeh form sequences with Zair"> + <testgroup label="teh + zair + chotiyeh + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + chotiyeh + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + chotiyeh + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + chotiyeh + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + chotiyeh + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + chotiyeh + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + chotiyeh + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + chotiyeh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + chotiyeh + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + chotiyeh + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + chotiyeh + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + chotiyeh + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + chotiyeh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + chotiyeh + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x06CC;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Bariyeh form sequences with Zabar"> + <testgroup label="teh + zabar + bariyeh + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + bariyeh + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + bariyeh + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + bariyeh + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + bariyeh + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + bariyeh + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + bariyeh + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + bariyeh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + bariyeh + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + bariyeh + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + bariyeh + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + bariyeh + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + bariyeh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + bariyeh + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x06D2;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Bariyeh form sequences with Zair"> + <testgroup label="teh + zair + bariyeh + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + bariyeh + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + bariyeh + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + bariyeh + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + bariyeh + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + bariyeh + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + bariyeh + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + bariyeh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + bariyeh + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + bariyeh + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + bariyeh + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + bariyeh + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + bariyeh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + bariyeh + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x06D2;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Multiple Beh sequences with Zabar"> + <testgroup label="teh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="teh + zabar + beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="teh + zabar + teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + beh + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + teh + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Multiple Beh sequences with Zair"> + <testgroup label="teh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="teh + zair + beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="teh + zair + teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + beh + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + teh + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Seen/Sad + Beh sequences with Zabar"> + <testgroup label="sad + zabar + beh + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + teh + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + beh + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + teh + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + beh + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + teh + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + beh + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + teh + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + beh + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0642;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0642;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + teh + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0642;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0642;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + beh + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + teh + zabar + sad + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0635;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0635;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + beh + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + teh + zabar + tah + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0637;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0637;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + beh + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + teh + zabar + ain + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0639;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0639;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + beh + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + teh + zabar + feh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0641;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0641;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + beh + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0642;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0642;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + teh + zabar + qaf + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0642;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0642;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0642;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + beh + zabar + beh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0628;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + teh + zabar + teh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x062A;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Seen/Sad + Beh sequences with Zair"> + <testgroup label="sad + zair + beh + zair + sad + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + teh + zair + sad + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + beh + zair + tah + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + teh + zair + tah + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + beh + zair + ain + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + teh + zair + ain + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + beh + zair + feh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + teh + zair + feh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + beh + zair + qaf + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0642;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0642;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + teh + zair + qaf + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0642;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0642;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + beh + zair + sad + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + teh + zair + sad + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0635;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0635;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + beh + zair + tah + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + teh + zair + tah + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0637;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0637;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + beh + zair + ain + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + teh + zair + ain + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0639;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0639;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + beh + zair + feh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + teh + zair + feh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0641;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0641;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + beh + zair + qaf + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0642;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0642;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + teh + zair + qaf + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0642;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0642;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0642;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + beh + zair + beh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0628;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + teh + zair + teh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x062A;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Beh + Reh sequences with Zabar"> + <testgroup label="teh + zabar + beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="teh + zabar + teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + beh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0628;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + teh + zabar + reh + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x062A;&#x064E;&#x0631;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Beh + Reh sequences with Zair"> + <testgroup label="teh + zair + beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="teh + zair + teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + beh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0628;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + teh + zair + reh + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x062A;&#x0650;&#x0631;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Beh + Noon sequences with Zabar"> + <testgroup label="teh + zabar + beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="teh + zabar + teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + beh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0628;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + teh + zabar + noon + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x062A;&#x064E;&#x0646;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Beh + Noon sequences with Zair"> + <testgroup label="teh + zair + beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="teh + zair + teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + beh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0628;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + teh + zair + noon + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x062A;&#x0650;&#x0646;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Beh + Heh-Goal sequences with Zabar"> + <testgroup label="teh + zabar + beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="teh + zabar + teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zabar + teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zabar + teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zabar + teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zabar + teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zabar + teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zabar + teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zabar + teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zabar + teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zabar + teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zabar + teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zabar + teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zabar + teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + beh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0628;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zabar + teh + zabar + hehGoal + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x062A;&#x064E;&#x06C1;&#x064E;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Beh + Heh-Goal sequences with Zair"> + <testgroup label="teh + zair + beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="teh + zair + teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + zair + teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + zair + teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + zair + teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + zair + teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + zair + teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + zair + teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + zair + teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + zair + teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + zair + teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + zair + teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + zair + teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + zair + teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + beh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0628;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + zair + teh + zair + hehGoal + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x062A;&#x0650;&#x06C1;&#x0650;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Seen + seen + seen sequences with Zabar"> + <testgroup label="teh + zabar + seen + zabar + seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zabar + seen + zabar + seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zabar + seen + zabar + seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zabar + seen + zabar + seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zabar + seen + zabar + seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zabar + seen + zabar + seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zabar + seen + zabar + seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zabar + seen + zabar + seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zabar + seen + zabar + seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zabar + seen + zabar + seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zabar + seen + zabar + seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zabar + seen + zabar + seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zabar + seen + zabar + seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zabar + seen + zabar + seen + zabar + seen + zabar"> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;&#x0633;&#x064E;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Seen + seen + seen sequences with Zair"> + <testgroup label="teh + zair + seen + zair + seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062A;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062A;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + zair + seen + zair + seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + zair + seen + zair + seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + zair + seen + zair + seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + zair + seen + zair + seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + zair + seen + zair + seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + zair + seen + zair + seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + zair + seen + zair + seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + zair + seen + zair + seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + zair + seen + zair + seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + zair + seen + zair + seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + zair + seen + zair + seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + zair + seen + zair + seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + zair + seen + zair + seen + zair + seen + zair"> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;&#x0633;&#x0650;</em>&#x0641;</string></test> + </testgroup> + </testgroup> +</ftml> diff --git a/text/test data/letter combinations/test_basicforms.pdf b/text/test data/letter combinations/test_basicforms.pdf Binary files differ. diff --git a/text/test data/letter combinations/test_basicforms.xml b/text/test data/letter combinations/test_basicforms.xml @@ -0,0 +1,2626 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet type="text/xsl" href="ftml.xsl"?> +<ftml version="1.0"> + <head> + <columns comment="15%" label="20%" string="15%"/> + <description>Test of Awami Basic Base Character Set</description> + <fontscale>200</fontscale> + <fontsrc>local('Awami Nastaliq Beta3'), url(Awami_beta3.ttf)</fontsrc> + <title>Test of Awami Basic Base Character Set</title> + <styles><style feats=' ' name="default"/></styles> + </head> + <testgroup label="Alef form sequences"> + <testgroup label="beh + alef"> + <test rtl="True"><string><em>&#x0628;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + alef"> + <test rtl="True"><string><em>&#x062C;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + alef"> + <test rtl="True"><string><em>&#x0633;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + alef"> + <test rtl="True"><string><em>&#x0635;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + alef"> + <test rtl="True"><string><em>&#x0637;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + alef"> + <test rtl="True"><string><em>&#x0639;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + alef"> + <test rtl="True"><string><em>&#x0641;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + alef"> + <test rtl="True"><string><em>&#x0644;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + alef"> + <test rtl="True"><string><em>&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + alef"> + <test rtl="True"><string><em>&#x06A9;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + alef"> + <test rtl="True"><string><em>&#x06AF;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + alef"> + <test rtl="True"><string><em>&#x06BE;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + alef"> + <test rtl="True"><string><em>&#x06C1;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Beh form sequences"> + <testgroup label="beh + beh"> + <test rtl="True"><string><em>&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + beh"> + <test rtl="True"><string><em>&#x062C;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + beh"> + <test rtl="True"><string><em>&#x0633;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + beh"> + <test rtl="True"><string><em>&#x0635;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + beh"> + <test rtl="True"><string><em>&#x0637;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + beh"> + <test rtl="True"><string><em>&#x0639;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + beh"> + <test rtl="True"><string><em>&#x0641;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + beh"> + <test rtl="True"><string><em>&#x0644;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + beh"> + <test rtl="True"><string><em>&#x0645;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + beh"> + <test rtl="True"><string><em>&#x06A9;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + beh"> + <test rtl="True"><string><em>&#x06AF;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + beh"> + <test rtl="True"><string><em>&#x06BE;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + beh"> + <test rtl="True"><string><em>&#x06C1;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0628;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Jeem form sequences"> + <testgroup label="beh + jeem"> + <test rtl="True"><string><em>&#x0628;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x062C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + jeem"> + <test rtl="True"><string><em>&#x062C;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x062C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + jeem"> + <test rtl="True"><string><em>&#x0633;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x062C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + jeem"> + <test rtl="True"><string><em>&#x0635;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x062C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + jeem"> + <test rtl="True"><string><em>&#x0637;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x062C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + jeem"> + <test rtl="True"><string><em>&#x0639;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x062C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + jeem"> + <test rtl="True"><string><em>&#x0641;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x062C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + jeem"> + <test rtl="True"><string><em>&#x0644;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x062C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + jeem"> + <test rtl="True"><string><em>&#x0645;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x062C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + jeem"> + <test rtl="True"><string><em>&#x06A9;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x062C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + jeem"> + <test rtl="True"><string><em>&#x06AF;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x062C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + jeem"> + <test rtl="True"><string><em>&#x06BE;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x062C;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + jeem"> + <test rtl="True"><string><em>&#x06C1;&#x062C;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x062C;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x062C;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x062C;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Dal form sequences"> + <testgroup label="beh + dal"> + <test rtl="True"><string><em>&#x0628;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + dal"> + <test rtl="True"><string><em>&#x062C;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + dal"> + <test rtl="True"><string><em>&#x0633;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + dal"> + <test rtl="True"><string><em>&#x0635;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + dal"> + <test rtl="True"><string><em>&#x0637;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + dal"> + <test rtl="True"><string><em>&#x0639;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + dal"> + <test rtl="True"><string><em>&#x0641;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + dal"> + <test rtl="True"><string><em>&#x0644;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + dal"> + <test rtl="True"><string><em>&#x0645;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + dal"> + <test rtl="True"><string><em>&#x06A9;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + dal"> + <test rtl="True"><string><em>&#x06AF;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + dal"> + <test rtl="True"><string><em>&#x06BE;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + dal"> + <test rtl="True"><string><em>&#x06C1;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Reh form sequences"> + <testgroup label="beh + reh"> + <test rtl="True"><string><em>&#x0628;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + reh"> + <test rtl="True"><string><em>&#x062C;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + reh"> + <test rtl="True"><string><em>&#x0633;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + reh"> + <test rtl="True"><string><em>&#x0635;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + reh"> + <test rtl="True"><string><em>&#x0637;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + reh"> + <test rtl="True"><string><em>&#x0639;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + reh"> + <test rtl="True"><string><em>&#x0641;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + reh"> + <test rtl="True"><string><em>&#x0644;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + reh"> + <test rtl="True"><string><em>&#x0645;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + reh"> + <test rtl="True"><string><em>&#x06A9;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + reh"> + <test rtl="True"><string><em>&#x06AF;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + reh"> + <test rtl="True"><string><em>&#x06BE;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + reh"> + <test rtl="True"><string><em>&#x06C1;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Seen form sequences"> + <testgroup label="beh + seen"> + <test rtl="True"><string><em>&#x0628;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + seen"> + <test rtl="True"><string><em>&#x062C;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + seen"> + <test rtl="True"><string><em>&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + seen"> + <test rtl="True"><string><em>&#x0635;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + seen"> + <test rtl="True"><string><em>&#x0637;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + seen"> + <test rtl="True"><string><em>&#x0639;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + seen"> + <test rtl="True"><string><em>&#x0641;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + seen"> + <test rtl="True"><string><em>&#x0644;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + seen"> + <test rtl="True"><string><em>&#x0645;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + seen"> + <test rtl="True"><string><em>&#x06A9;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + seen"> + <test rtl="True"><string><em>&#x06AF;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + seen"> + <test rtl="True"><string><em>&#x06BE;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + seen"> + <test rtl="True"><string><em>&#x06C1;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0633;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Sad form sequences"> + <testgroup label="beh + sad"> + <test rtl="True"><string><em>&#x0628;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + sad"> + <test rtl="True"><string><em>&#x062C;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + sad"> + <test rtl="True"><string><em>&#x0633;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + sad"> + <test rtl="True"><string><em>&#x0635;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + sad"> + <test rtl="True"><string><em>&#x0637;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + sad"> + <test rtl="True"><string><em>&#x0639;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + sad"> + <test rtl="True"><string><em>&#x0641;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + sad"> + <test rtl="True"><string><em>&#x0644;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + sad"> + <test rtl="True"><string><em>&#x0645;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + sad"> + <test rtl="True"><string><em>&#x06A9;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + sad"> + <test rtl="True"><string><em>&#x06AF;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + sad"> + <test rtl="True"><string><em>&#x06BE;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + sad"> + <test rtl="True"><string><em>&#x06C1;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0635;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Tah form sequences"> + <testgroup label="beh + tah"> + <test rtl="True"><string><em>&#x0628;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + tah"> + <test rtl="True"><string><em>&#x062C;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + tah"> + <test rtl="True"><string><em>&#x0633;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + tah"> + <test rtl="True"><string><em>&#x0635;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + tah"> + <test rtl="True"><string><em>&#x0637;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + tah"> + <test rtl="True"><string><em>&#x0639;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + tah"> + <test rtl="True"><string><em>&#x0641;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + tah"> + <test rtl="True"><string><em>&#x0644;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + tah"> + <test rtl="True"><string><em>&#x0645;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + tah"> + <test rtl="True"><string><em>&#x06A9;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + tah"> + <test rtl="True"><string><em>&#x06AF;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + tah"> + <test rtl="True"><string><em>&#x06BE;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + tah"> + <test rtl="True"><string><em>&#x06C1;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0637;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Ain form sequences"> + <testgroup label="beh + ain"> + <test rtl="True"><string><em>&#x0628;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + ain"> + <test rtl="True"><string><em>&#x062C;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + ain"> + <test rtl="True"><string><em>&#x0633;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + ain"> + <test rtl="True"><string><em>&#x0635;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + ain"> + <test rtl="True"><string><em>&#x0637;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + ain"> + <test rtl="True"><string><em>&#x0639;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + ain"> + <test rtl="True"><string><em>&#x0641;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + ain"> + <test rtl="True"><string><em>&#x0644;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + ain"> + <test rtl="True"><string><em>&#x0645;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + ain"> + <test rtl="True"><string><em>&#x06A9;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + ain"> + <test rtl="True"><string><em>&#x06AF;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + ain"> + <test rtl="True"><string><em>&#x06BE;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + ain"> + <test rtl="True"><string><em>&#x06C1;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0639;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Feh form sequences"> + <testgroup label="beh + feh"> + <test rtl="True"><string><em>&#x0628;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + feh"> + <test rtl="True"><string><em>&#x062C;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + feh"> + <test rtl="True"><string><em>&#x0633;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + feh"> + <test rtl="True"><string><em>&#x0635;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + feh"> + <test rtl="True"><string><em>&#x0637;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + feh"> + <test rtl="True"><string><em>&#x0639;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + feh"> + <test rtl="True"><string><em>&#x0641;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + feh"> + <test rtl="True"><string><em>&#x0644;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + feh"> + <test rtl="True"><string><em>&#x0645;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + feh"> + <test rtl="True"><string><em>&#x06A9;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + feh"> + <test rtl="True"><string><em>&#x06AF;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + feh"> + <test rtl="True"><string><em>&#x06BE;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + feh"> + <test rtl="True"><string><em>&#x06C1;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0641;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Qaf form sequences"> + <testgroup label="beh + qaf"> + <test rtl="True"><string><em>&#x0628;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + qaf"> + <test rtl="True"><string><em>&#x062C;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + qaf"> + <test rtl="True"><string><em>&#x0633;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + qaf"> + <test rtl="True"><string><em>&#x0635;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + qaf"> + <test rtl="True"><string><em>&#x0637;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + qaf"> + <test rtl="True"><string><em>&#x0639;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + qaf"> + <test rtl="True"><string><em>&#x0641;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + qaf"> + <test rtl="True"><string><em>&#x0644;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + qaf"> + <test rtl="True"><string><em>&#x0645;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + qaf"> + <test rtl="True"><string><em>&#x06A9;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + qaf"> + <test rtl="True"><string><em>&#x06AF;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + qaf"> + <test rtl="True"><string><em>&#x06BE;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + qaf"> + <test rtl="True"><string><em>&#x06C1;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0642;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Lam form sequences"> + <testgroup label="beh + lam"> + <test rtl="True"><string><em>&#x0628;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + lam"> + <test rtl="True"><string><em>&#x062C;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + lam"> + <test rtl="True"><string><em>&#x0633;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + lam"> + <test rtl="True"><string><em>&#x0635;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + lam"> + <test rtl="True"><string><em>&#x0637;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + lam"> + <test rtl="True"><string><em>&#x0639;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + lam"> + <test rtl="True"><string><em>&#x0641;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + lam"> + <test rtl="True"><string><em>&#x0644;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + lam"> + <test rtl="True"><string><em>&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + lam"> + <test rtl="True"><string><em>&#x06A9;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + lam"> + <test rtl="True"><string><em>&#x06AF;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + lam"> + <test rtl="True"><string><em>&#x06BE;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + lam"> + <test rtl="True"><string><em>&#x06C1;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0644;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Meem form sequences"> + <testgroup label="beh + meem"> + <test rtl="True"><string><em>&#x0628;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + meem"> + <test rtl="True"><string><em>&#x062C;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + meem"> + <test rtl="True"><string><em>&#x0633;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + meem"> + <test rtl="True"><string><em>&#x0635;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + meem"> + <test rtl="True"><string><em>&#x0637;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + meem"> + <test rtl="True"><string><em>&#x0639;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + meem"> + <test rtl="True"><string><em>&#x0641;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + meem"> + <test rtl="True"><string><em>&#x0644;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + meem"> + <test rtl="True"><string><em>&#x0645;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + meem"> + <test rtl="True"><string><em>&#x06A9;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + meem"> + <test rtl="True"><string><em>&#x06AF;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + meem"> + <test rtl="True"><string><em>&#x06BE;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + meem"> + <test rtl="True"><string><em>&#x06C1;&#x0645;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0645;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0645;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0645;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Alternate meem sequences"> + <testgroup label="beh + meem-alt + kaf"> + <test rtl="True"><string><em>&#x0628;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0645;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0645;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + meem-alt + gaf"> + <test rtl="True"><string><em>&#x0628;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + meem-alt + lam"> + <test rtl="True"><string><em>&#x0628;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + meem-alt + alef"> + <test rtl="True"><string><em>&#x0628;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="beh + meem-alt + dal"> + <test rtl="True"><string><em>&#x0628;&#x0645;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0645;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + meem-alt + kaf"> + <test rtl="True"><string><em>&#x062C;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0645;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0645;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + meem-alt + gaf"> + <test rtl="True"><string><em>&#x062C;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + meem-alt + lam"> + <test rtl="True"><string><em>&#x062C;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + meem-alt + alef"> + <test rtl="True"><string><em>&#x062C;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + meem-alt + dal"> + <test rtl="True"><string><em>&#x062C;&#x0645;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0645;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + meem-alt + kaf"> + <test rtl="True"><string><em>&#x0633;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0645;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0645;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + meem-alt + gaf"> + <test rtl="True"><string><em>&#x0633;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + meem-alt + lam"> + <test rtl="True"><string><em>&#x0633;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + meem-alt + alef"> + <test rtl="True"><string><em>&#x0633;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + meem-alt + dal"> + <test rtl="True"><string><em>&#x0633;&#x0645;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0645;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + meem-alt + kaf"> + <test rtl="True"><string><em>&#x0635;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0645;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0645;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + meem-alt + gaf"> + <test rtl="True"><string><em>&#x0635;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + meem-alt + lam"> + <test rtl="True"><string><em>&#x0635;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + meem-alt + alef"> + <test rtl="True"><string><em>&#x0635;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + meem-alt + dal"> + <test rtl="True"><string><em>&#x0635;&#x0645;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0645;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + meem-alt + kaf"> + <test rtl="True"><string><em>&#x0637;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0645;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0645;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + meem-alt + gaf"> + <test rtl="True"><string><em>&#x0637;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + meem-alt + lam"> + <test rtl="True"><string><em>&#x0637;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + meem-alt + alef"> + <test rtl="True"><string><em>&#x0637;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + meem-alt + dal"> + <test rtl="True"><string><em>&#x0637;&#x0645;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0645;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + meem-alt + kaf"> + <test rtl="True"><string><em>&#x0639;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0645;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + meem-alt + gaf"> + <test rtl="True"><string><em>&#x0639;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + meem-alt + lam"> + <test rtl="True"><string><em>&#x0639;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + meem-alt + alef"> + <test rtl="True"><string><em>&#x0639;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + meem-alt + dal"> + <test rtl="True"><string><em>&#x0639;&#x0645;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0645;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + meem-alt + kaf"> + <test rtl="True"><string><em>&#x0641;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0645;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0645;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + meem-alt + gaf"> + <test rtl="True"><string><em>&#x0641;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + meem-alt + lam"> + <test rtl="True"><string><em>&#x0641;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + meem-alt + alef"> + <test rtl="True"><string><em>&#x0641;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + meem-alt + dal"> + <test rtl="True"><string><em>&#x0641;&#x0645;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0645;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + meem-alt + kaf"> + <test rtl="True"><string><em>&#x0645;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0645;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + meem-alt + gaf"> + <test rtl="True"><string><em>&#x0645;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + meem-alt + lam"> + <test rtl="True"><string><em>&#x0645;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + meem-alt + alef"> + <test rtl="True"><string><em>&#x0645;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + meem-alt + dal"> + <test rtl="True"><string><em>&#x0645;&#x0645;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0645;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + meem-alt + kaf"> + <test rtl="True"><string><em>&#x06BE;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0645;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + meem-alt + gaf"> + <test rtl="True"><string><em>&#x06BE;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + meem-alt + lam"> + <test rtl="True"><string><em>&#x06BE;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + meem-alt + alef"> + <test rtl="True"><string><em>&#x06BE;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + meem-alt + dal"> + <test rtl="True"><string><em>&#x06BE;&#x0645;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0645;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + meem-alt + kaf"> + <test rtl="True"><string><em>&#x06C1;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0645;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0645;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + meem-alt + gaf"> + <test rtl="True"><string><em>&#x06C1;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + meem-alt + lam"> + <test rtl="True"><string><em>&#x06C1;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0645;&#x0644;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0645;&#x0644;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0645;&#x0644;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + meem-alt + alef"> + <test rtl="True"><string><em>&#x06C1;&#x0645;&#x0627;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0645;&#x0627;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + meem-alt + dal"> + <test rtl="True"><string><em>&#x06C1;&#x0645;&#x062F;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0645;&#x062F;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Noon form sequences"> + <testgroup label="beh + noon"> + <test rtl="True"><string><em>&#x0628;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + noon"> + <test rtl="True"><string><em>&#x062C;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + noon"> + <test rtl="True"><string><em>&#x0633;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + noon"> + <test rtl="True"><string><em>&#x0635;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + noon"> + <test rtl="True"><string><em>&#x0637;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + noon"> + <test rtl="True"><string><em>&#x0639;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + noon"> + <test rtl="True"><string><em>&#x0641;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + noon"> + <test rtl="True"><string><em>&#x0644;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + noon"> + <test rtl="True"><string><em>&#x0645;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + noon"> + <test rtl="True"><string><em>&#x06A9;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + noon"> + <test rtl="True"><string><em>&#x06AF;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + noon"> + <test rtl="True"><string><em>&#x06BE;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + noon"> + <test rtl="True"><string><em>&#x06C1;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Waw form sequences"> + <testgroup label="beh + waw"> + <test rtl="True"><string><em>&#x0628;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + waw"> + <test rtl="True"><string><em>&#x062C;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + waw"> + <test rtl="True"><string><em>&#x0633;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + waw"> + <test rtl="True"><string><em>&#x0635;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + waw"> + <test rtl="True"><string><em>&#x0637;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + waw"> + <test rtl="True"><string><em>&#x0639;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + waw"> + <test rtl="True"><string><em>&#x0641;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + waw"> + <test rtl="True"><string><em>&#x0644;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + waw"> + <test rtl="True"><string><em>&#x0645;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + waw"> + <test rtl="True"><string><em>&#x06A9;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + waw"> + <test rtl="True"><string><em>&#x06AF;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + waw"> + <test rtl="True"><string><em>&#x06BE;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + waw"> + <test rtl="True"><string><em>&#x06C1;&#x0648;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0648;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Kaf form sequences"> + <testgroup label="beh + kaf"> + <test rtl="True"><string><em>&#x0628;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + kaf"> + <test rtl="True"><string><em>&#x062C;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + kaf"> + <test rtl="True"><string><em>&#x0633;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + kaf"> + <test rtl="True"><string><em>&#x0635;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + kaf"> + <test rtl="True"><string><em>&#x0637;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + kaf"> + <test rtl="True"><string><em>&#x0639;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + kaf"> + <test rtl="True"><string><em>&#x0641;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + kaf"> + <test rtl="True"><string><em>&#x0644;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + kaf"> + <test rtl="True"><string><em>&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + kaf"> + <test rtl="True"><string><em>&#x06A9;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + kaf"> + <test rtl="True"><string><em>&#x06AF;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + kaf"> + <test rtl="True"><string><em>&#x06BE;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + kaf"> + <test rtl="True"><string><em>&#x06C1;&#x06A9;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x06A9;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x06A9;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x06A9;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Gaf form sequences"> + <testgroup label="beh + gaf"> + <test rtl="True"><string><em>&#x0628;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + gaf"> + <test rtl="True"><string><em>&#x062C;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + gaf"> + <test rtl="True"><string><em>&#x0633;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + gaf"> + <test rtl="True"><string><em>&#x0635;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + gaf"> + <test rtl="True"><string><em>&#x0637;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + gaf"> + <test rtl="True"><string><em>&#x0639;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + gaf"> + <test rtl="True"><string><em>&#x0641;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + gaf"> + <test rtl="True"><string><em>&#x0644;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + gaf"> + <test rtl="True"><string><em>&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + gaf"> + <test rtl="True"><string><em>&#x06A9;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + gaf"> + <test rtl="True"><string><em>&#x06AF;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + gaf"> + <test rtl="True"><string><em>&#x06BE;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + gaf"> + <test rtl="True"><string><em>&#x06C1;&#x06AF;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x06AF;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x06AF;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x06AF;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Heh-Doachashmee form sequences"> + <testgroup label="beh + hehDo"> + <test rtl="True"><string><em>&#x0628;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + hehDo"> + <test rtl="True"><string><em>&#x062C;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + hehDo"> + <test rtl="True"><string><em>&#x0633;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + hehDo"> + <test rtl="True"><string><em>&#x0635;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + hehDo"> + <test rtl="True"><string><em>&#x0637;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + hehDo"> + <test rtl="True"><string><em>&#x0639;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + hehDo"> + <test rtl="True"><string><em>&#x0641;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + hehDo"> + <test rtl="True"><string><em>&#x0644;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + hehDo"> + <test rtl="True"><string><em>&#x0645;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + hehDo"> + <test rtl="True"><string><em>&#x06A9;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + hehDo"> + <test rtl="True"><string><em>&#x06AF;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + hehDo"> + <test rtl="True"><string><em>&#x06BE;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + hehDo"> + <test rtl="True"><string><em>&#x06C1;&#x06BE;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x06BE;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x06BE;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x06BE;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Heh-Goal form sequences"> + <testgroup label="beh + hehGoal"> + <test rtl="True"><string><em>&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + hehGoal"> + <test rtl="True"><string><em>&#x062C;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + hehGoal"> + <test rtl="True"><string><em>&#x0633;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + hehGoal"> + <test rtl="True"><string><em>&#x0635;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + hehGoal"> + <test rtl="True"><string><em>&#x0637;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + hehGoal"> + <test rtl="True"><string><em>&#x0639;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + hehGoal"> + <test rtl="True"><string><em>&#x0641;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + hehGoal"> + <test rtl="True"><string><em>&#x0644;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + hehGoal"> + <test rtl="True"><string><em>&#x0645;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + hehGoal"> + <test rtl="True"><string><em>&#x06A9;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + hehGoal"> + <test rtl="True"><string><em>&#x06AF;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + hehGoal"> + <test rtl="True"><string><em>&#x06BE;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + hehGoal"> + <test rtl="True"><string><em>&#x06C1;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x06C1;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x06C1;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x06C1;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Teh-marbuta form sequences"> + <testgroup label="beh + tehMarbuta"> + <test rtl="True"><string><em>&#x0628;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + tehMarbuta"> + <test rtl="True"><string><em>&#x062C;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + tehMarbuta"> + <test rtl="True"><string><em>&#x0633;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + tehMarbuta"> + <test rtl="True"><string><em>&#x0635;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + tehMarbuta"> + <test rtl="True"><string><em>&#x0637;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + tehMarbuta"> + <test rtl="True"><string><em>&#x0639;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + tehMarbuta"> + <test rtl="True"><string><em>&#x0641;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + tehMarbuta"> + <test rtl="True"><string><em>&#x0644;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + tehMarbuta"> + <test rtl="True"><string><em>&#x0645;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + tehMarbuta"> + <test rtl="True"><string><em>&#x06A9;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + tehMarbuta"> + <test rtl="True"><string><em>&#x06AF;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + tehMarbuta"> + <test rtl="True"><string><em>&#x06BE;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + tehMarbuta"> + <test rtl="True"><string><em>&#x06C1;&#x0629;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0629;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Chotiyeh form sequences"> + <testgroup label="beh + chotiyeh"> + <test rtl="True"><string><em>&#x0628;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + chotiyeh"> + <test rtl="True"><string><em>&#x062C;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + chotiyeh"> + <test rtl="True"><string><em>&#x0633;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + chotiyeh"> + <test rtl="True"><string><em>&#x0635;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + chotiyeh"> + <test rtl="True"><string><em>&#x0637;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + chotiyeh"> + <test rtl="True"><string><em>&#x0639;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + chotiyeh"> + <test rtl="True"><string><em>&#x0641;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + chotiyeh"> + <test rtl="True"><string><em>&#x0644;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + chotiyeh"> + <test rtl="True"><string><em>&#x0645;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + chotiyeh"> + <test rtl="True"><string><em>&#x06A9;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + chotiyeh"> + <test rtl="True"><string><em>&#x06AF;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + chotiyeh"> + <test rtl="True"><string><em>&#x06BE;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + chotiyeh"> + <test rtl="True"><string><em>&#x06C1;&#x06CC;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x06CC;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Bariyeh form sequences"> + <testgroup label="beh + bariyeh"> + <test rtl="True"><string><em>&#x0628;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + bariyeh"> + <test rtl="True"><string><em>&#x062C;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + bariyeh"> + <test rtl="True"><string><em>&#x0633;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + bariyeh"> + <test rtl="True"><string><em>&#x0635;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + bariyeh"> + <test rtl="True"><string><em>&#x0637;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + bariyeh"> + <test rtl="True"><string><em>&#x0639;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + bariyeh"> + <test rtl="True"><string><em>&#x0641;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + bariyeh"> + <test rtl="True"><string><em>&#x0644;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + bariyeh"> + <test rtl="True"><string><em>&#x0645;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + bariyeh"> + <test rtl="True"><string><em>&#x06A9;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + bariyeh"> + <test rtl="True"><string><em>&#x06AF;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + bariyeh"> + <test rtl="True"><string><em>&#x06BE;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + bariyeh"> + <test rtl="True"><string><em>&#x06C1;&#x06D2;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x06D2;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Multiple Beh sequences"> + <testgroup label="beh + beh + beh"> + <test rtl="True"><string><em>&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="beh + beh + beh + beh"> + <test rtl="True"><string><em>&#x0628;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + beh + beh"> + <test rtl="True"><string><em>&#x062C;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + beh + beh + beh"> + <test rtl="True"><string><em>&#x062C;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + beh + beh"> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + beh + beh + beh"> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + beh + beh"> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + beh + beh + beh"> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + beh + beh"> + <test rtl="True"><string><em>&#x0637;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + beh + beh + beh"> + <test rtl="True"><string><em>&#x0637;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + beh + beh"> + <test rtl="True"><string><em>&#x0639;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + beh + beh + beh"> + <test rtl="True"><string><em>&#x0639;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + beh + beh"> + <test rtl="True"><string><em>&#x0641;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + beh + beh + beh"> + <test rtl="True"><string><em>&#x0641;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + beh + beh"> + <test rtl="True"><string><em>&#x0644;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + beh + beh + beh"> + <test rtl="True"><string><em>&#x0644;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + beh + beh"> + <test rtl="True"><string><em>&#x0645;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + beh + beh + beh"> + <test rtl="True"><string><em>&#x0645;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + beh + beh"> + <test rtl="True"><string><em>&#x06A9;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + beh + beh + beh"> + <test rtl="True"><string><em>&#x06A9;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + beh + beh"> + <test rtl="True"><string><em>&#x06AF;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + beh + beh + beh"> + <test rtl="True"><string><em>&#x06AF;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + beh + beh"> + <test rtl="True"><string><em>&#x06BE;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + beh + beh + beh"> + <test rtl="True"><string><em>&#x06BE;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + beh + beh"> + <test rtl="True"><string><em>&#x06C1;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + beh + beh + beh"> + <test rtl="True"><string><em>&#x06C1;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0628;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0628;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Seen/Sad + Beh sequences"> + <testgroup label="seen + beh + sad"> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + beh + tah"> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + beh + ain"> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + beh + feh"> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + beh + qaf"> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0642;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0642;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0642;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + beh + beh"> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + beh + sad"> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0635;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0635;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0635;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0635;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + beh + tah"> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0637;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0637;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0637;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0637;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + beh + ain"> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0639;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0639;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0639;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0639;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + beh + feh"> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0641;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0641;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0641;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0641;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + beh + qaf"> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0642;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0642;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0642;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0642;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + beh + beh"> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0628;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0628;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0628;</em>&#x0641;</string></test> + </testgroup> + </testgroup> + <testgroup label="Beh + Reh sequences"> + <testgroup label="beh + beh + reh"> + <test rtl="True"><string><em>&#x0628;&#x0628;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0628;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + beh + reh"> + <test rtl="True"><string><em>&#x062C;&#x0628;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0628;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + beh + reh"> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + beh + reh"> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + beh + reh"> + <test rtl="True"><string><em>&#x0637;&#x0628;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0628;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + beh + reh"> + <test rtl="True"><string><em>&#x0639;&#x0628;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0628;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + beh + reh"> + <test rtl="True"><string><em>&#x0641;&#x0628;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0628;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + beh + reh"> + <test rtl="True"><string><em>&#x0644;&#x0628;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0628;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + beh + reh"> + <test rtl="True"><string><em>&#x0645;&#x0628;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0628;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + beh + reh"> + <test rtl="True"><string><em>&#x06A9;&#x0628;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0628;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + beh + reh"> + <test rtl="True"><string><em>&#x06AF;&#x0628;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0628;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + beh + reh"> + <test rtl="True"><string><em>&#x06BE;&#x0628;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0628;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + beh + reh"> + <test rtl="True"><string><em>&#x06C1;&#x0628;&#x0631;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0628;&#x0631;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Beh + Noon sequences"> + <testgroup label="beh + beh + noon"> + <test rtl="True"><string><em>&#x0628;&#x0628;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0628;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + beh + noon"> + <test rtl="True"><string><em>&#x062C;&#x0628;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0628;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + beh + noon"> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + beh + noon"> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + beh + noon"> + <test rtl="True"><string><em>&#x0637;&#x0628;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0628;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + beh + noon"> + <test rtl="True"><string><em>&#x0639;&#x0628;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0628;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + beh + noon"> + <test rtl="True"><string><em>&#x0641;&#x0628;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0628;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + beh + noon"> + <test rtl="True"><string><em>&#x0644;&#x0628;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0628;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + beh + noon"> + <test rtl="True"><string><em>&#x0645;&#x0628;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0628;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + beh + noon"> + <test rtl="True"><string><em>&#x06A9;&#x0628;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0628;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + beh + noon"> + <test rtl="True"><string><em>&#x06AF;&#x0628;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0628;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + beh + noon"> + <test rtl="True"><string><em>&#x06BE;&#x0628;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0628;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + beh + noon"> + <test rtl="True"><string><em>&#x06C1;&#x0628;&#x0646;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0628;&#x0646;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Beh + Heh-Goal sequences"> + <testgroup label="beh + beh + hehGoal"> + <test rtl="True"><string><em>&#x0628;&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0628;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="jeem + beh + hehGoal"> + <test rtl="True"><string><em>&#x062C;&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0628;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="seen + beh + hehGoal"> + <test rtl="True"><string><em>&#x0633;&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0628;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="sad + beh + hehGoal"> + <test rtl="True"><string><em>&#x0635;&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0628;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="tah + beh + hehGoal"> + <test rtl="True"><string><em>&#x0637;&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0628;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="ain + beh + hehGoal"> + <test rtl="True"><string><em>&#x0639;&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0628;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="feh + beh + hehGoal"> + <test rtl="True"><string><em>&#x0641;&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0628;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="lam + beh + hehGoal"> + <test rtl="True"><string><em>&#x0644;&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0628;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="meem + beh + hehGoal"> + <test rtl="True"><string><em>&#x0645;&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0628;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="kaf + beh + hehGoal"> + <test rtl="True"><string><em>&#x06A9;&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0628;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="gaf + beh + hehGoal"> + <test rtl="True"><string><em>&#x06AF;&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0628;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehDo + beh + hehGoal"> + <test rtl="True"><string><em>&#x06BE;&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0628;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + <testgroup label="hehGoal + beh + hehGoal"> + <test rtl="True"><string><em>&#x06C1;&#x0628;&#x06C1;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0628;&#x06C1;</em></string></test> + <test rtl="True" background="#cfcfcf"><string/></test> + <test rtl="True" background="#cfcfcf"><string/></test> + </testgroup> + </testgroup> + <testgroup label="Seen + seen + seen sequences"> + <testgroup label="beh + seen + seen + seen"> + <test rtl="True"><string><em>&#x0628;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0628;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0628;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="jeem + seen + seen + seen"> + <test rtl="True"><string><em>&#x062C;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x062C;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x062C;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="seen + seen + seen + seen"> + <test rtl="True"><string><em>&#x0633;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0633;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0633;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="sad + seen + seen + seen"> + <test rtl="True"><string><em>&#x0635;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0635;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0635;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="tah + seen + seen + seen"> + <test rtl="True"><string><em>&#x0637;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0637;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0637;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="ain + seen + seen + seen"> + <test rtl="True"><string><em>&#x0639;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0639;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0639;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="feh + seen + seen + seen"> + <test rtl="True"><string><em>&#x0641;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0641;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0641;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="lam + seen + seen + seen"> + <test rtl="True"><string><em>&#x0644;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0644;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0644;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="meem + seen + seen + seen"> + <test rtl="True"><string><em>&#x0645;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x0645;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x0645;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="kaf + seen + seen + seen"> + <test rtl="True"><string><em>&#x06A9;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x06A9;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06A9;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="gaf + seen + seen + seen"> + <test rtl="True"><string><em>&#x06AF;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x06AF;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06AF;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehDo + seen + seen + seen"> + <test rtl="True"><string><em>&#x06BE;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x06BE;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06BE;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + </testgroup> + <testgroup label="hehGoal + seen + seen + seen"> + <test rtl="True"><string><em>&#x06C1;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0633;&#x0633;&#x0633;</em></string></test> + <test rtl="True"><string><em>&#x06C1;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + <test rtl="True"><string>&#x0644;<em>&#x06C1;&#x0633;&#x0633;&#x0633;</em>&#x0641;</string></test> + </testgroup> + </testgroup> +</ftml> diff --git a/text/test.c b/text/test.c @@ -0,0 +1,61 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xos.h> +#include "text.h" +int main(int argc, const char * argv[]) +{ + + Display *display; + int screen; + Window window; + GC gc; + + unsigned long black, white; + Colormap colormap; + display = XOpenDisplay((char *)0); + screen = DefaultScreen(display); + colormap = DefaultColormap(display, screen); + black = BlackPixel(display, screen); + white = WhitePixel(display, screen); + window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 1366, 512, 0, black, white); + XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL); + XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask); + gc = XCreateGC(display, window, 0, 0); + XSetBackground(display, gc, black); + XSetForeground(display, gc, black); + XClearWindow(display, window); + XMapRaised(display, window); + + Pixmap pix = + + XCopyArea(display, pix, window, gc, 0, 0, b_w, b_h, 0, 0); + + XEvent event; + KeySym key; + char text[255]; + + while(1) + { + XNextEvent(display, &event); + if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) + { + XCopyArea(display, pix, window, gc, 0, 0, b_w, b_h, 0, 0); + if (text[0] == 'q') + { + XFreeGC(display, gc); + XFreeColormap(display, colormap); + XDestroyWindow(display, window); + XCloseDisplay(display); + exit(0); + } + } + } + + free(fontBuffer); + free(bitmap); + + return 0; +} diff --git a/text/test23.c b/text/test23.c @@ -0,0 +1,55 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xos.h> +#include <X11/extensions/Xrender.h> + +int main(int argc, const char * argv[]) +{ + Display *display; + int screen; + Window window; + GC gc; + + unsigned long black, white; + Colormap colormap; + display = XOpenDisplay((char *)0); + screen = DefaultScreen(display); + colormap = DefaultColormap(display, screen); + black = BlackPixel(display, screen); + white = WhitePixel(display, screen); + window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 1366, 512, 0, black, white); + XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL); + XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask); + gc = XCreateGC(display, window, 0, 0); + XSetBackground(display, gc, black); + XSetForeground(display, gc, black); + XClearWindow(display, window); + XMapRaised(display, window); + + Picture pic = XRenderCreatePicture(display, window, XRenderFindStandardFormat(display, PictStandardRGB24), 0, 0); + + XEvent event; + KeySym key; + char text[255]; + + while(1) + { + XNextEvent(display, &event); + if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) + { + if (text[0] == 'q') + { + XFreeGC(display, gc); + XFreeColormap(display, colormap); + XDestroyWindow(display, window); + XCloseDisplay(display); + exit(0); + } + } + } + + return 0; +} diff --git a/text/test3.c b/text/test3.c @@ -0,0 +1,131 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include "text.h" + +uint32_t u8_nextmemchar(const char *s, size_t *i) +{ + uint32_t ch = 0; + size_t sz = 0; + do { + ch <<= 6; + ch += (unsigned char)s[(*i)++]; + sz++; + } while (!isutf(s[*i])); + ch -= offsetsFromUTF8[sz-1]; + + return ch; +} + +stbtt_fontinfo ltk_load_font(const char *path) +{ + FILE *f; + long len; + char *contents; + stbtt_fontinfo info; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + contents = malloc(len + 1); + fread(contents, 1, len, f); + contents[len] = '\0'; + fclose(f); + if (!stbtt_InitFont(&info, contents, 0)) + { + fprintf(stderr, "Failed to load font %s\n", path); + exit(1); + } + return info; +} + +int ltk_text_width(uint8_t *text, stbtt_fontinfo fontinfo, int height) +{ + float scale = stbtt_ScaleForPixelHeight(&fontinfo, height); + size_t i = 0; + int length = strlen(text); + if (length < 1) return 0; + int temp_x; + int kern_advance; + int width = 0; + uint32_t char1; + uint32_t char2; + char1 = u8_nextmemchar(text, &i); + while(i <= length) + { + stbtt_GetCodepointHMetrics(&fontinfo, char1, &temp_x, 0); + width += temp_x * scale; + + char2 = u8_nextmemchar(text, &i); + if (!char2) break; + kern_advance = stbtt_GetCodepointKernAdvance(&fontinfo, char1, char2); + width += kern_advance * scale; + char1 = char2; + } + + return width; +} + +unsigned char *ltk_render_text(uint8_t *text, stbtt_fontinfo fontinfo, int height, int width) +{ +/* int width = ltk_text_width(text, fontinfo, height);*/ + unsigned char *bitmap = calloc(sizeof(char), width * height); + float scale = stbtt_ScaleForPixelHeight(&fontinfo, height); + + int ascent, descent, line_gap; + stbtt_GetFontVMetrics(&fontinfo, &ascent, &descent, &line_gap); + ascent *= scale; + descent *= scale; + + size_t i = 0; + int length = strlen(text); + if (length < 1) + { + printf("WARNING: ltk_render_text: length of text is less than 1.\n"); + return bitmap; + } + uint32_t char1, char2; + char1 = u8_nextmemchar(text, &i); + int ax, x = 0, y, x1, y1, x2, y2, byte_offset, kern_advance; + while (i <= length) + { + stbtt_GetCodepointBitmapBox(&fontinfo, char1, scale, scale, &x1, &y1, &x2, &y2); + y = ascent + y1; + byte_offset = x + (y * width); + stbtt_MakeCodepointBitmap(&fontinfo, bitmap + byte_offset, x2 - x1, y2 - y1, width, scale, scale, char1); + + stbtt_GetCodepointHMetrics(&fontinfo, char1, &ax, 0); + x += ax * scale; + + char2 = u8_nextmemchar(text, &i); + if (!char2) break; + kern_advance = stbtt_GetCodepointKernAdvance(&fontinfo, char1, char2); + x += kern_advance * scale; + char1 = char2; + } + + return bitmap; +} diff --git a/text/text-hb.c b/text/text-hb.c @@ -0,0 +1,362 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2017, 2018 lumidify <nobody@lumidify.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <limits.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <harfbuzz/hb.h> +#include <harfbuzz/hb-ot.h> +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */ +#include "khash.h" + +/* TODO: possibly "glyph manager" - only render glyph once and keep info in hash? + -> would be difficult because of different sizes - would need to keep track of all that. + -> reference counter - delete glyph from cache if not used anymore - good when there are many ligatures */ + +/* Font manager: hash for font path -> font id + hash for font id -> ltk font struct */ + +/* FIXME: this needs to be uint32_t, NOT just int! */ +/* font path, size -> glyph cache hash */ +KHASH_MAP_INIT_INT(ihash, khash_t(iglyph)) +/* glyph id -> glyph info struct */ +KHASH_MAP_INIT_INT(iglyph, LtkGlyphInfo*) +/* font path -> font struct */ +KHASH_MAP_INIT_STR(cfont, LtkFont*) + +typedef struct LtkTextManager_ { + khash_t(cfont) *font_cache; + khash_t(ihash) *glyph_cache; + unsigned int font_id_cur; +} LtkTextManager; + +/* Make LtkFont union of LtkFontHB, LtkFontGR, and LtkFontLT to handle harfbuzz, graphite, and normal latin? */ +typedef struct { + stbtt_fontinfo info; + hb_font_t *hb; + unsigned int id; +} LtkFont; + +void +ltk_render_glyph(LtkFont *font, unsigned int id, float scale, khash_t(iglyph) *cache) +{ + int x1, y1, x_off, y_off; + int ret; + khiter t; + + LtkGlyphInfo *info = malloc(sizeof(LtkGlyphInfo)); + info->alphamap = stbtt_GetGlyphBitmap(&font->font_info, scale, scale, id, info->w, info->h, info->x_topleft, info->y_topleft); + k = kh_put(iglyph, cache, info, &ret); + /* x/y_advance? */ +} + +LtkTextManager * +ltk_init_text(void) +{ + LtkTextManager *m = malloc(sizeof LtkTextManager); + if (!m) ltk_fatal("Memory exhausted when trying to create text manager."); + m->font_cache = kh_init(cfont); + m->glyph_cache = kh_init(ihash); + m->font_id_cur = 0; + return m; +} + +void +ltk_create_glyph_cache(LtkTextManager *m, unsigned int font_id, unsigned int font_size) +{ + khash_t(iglyph) *cache = kh_init(iglyph); + int ret; + khiter_t k; + /* I guess I can just ignore ret for now */ + k = kh_put(iglyph, m->glyph_cache, cache, &ret); +} + + + +char * +ltk_load_file(const char *path, unsigned long *len) +{ + FILE *f; + char *contents; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + *len = ftell(f); + fseek(f, 0, SEEK_SET); + contents = malloc(*len + 1); + fread(contents, 1, *len, f); + contents[*len] = '\0'; + fclose(f); + return contents; +} + +unsigned long +ltk_blend_pixel(Display *display, Colormap colormap, XColor fg, XColor bg, double a) +{ + if (a >= 1.0) { + return fg.pixel; + } else if (a == 0.0) { + return bg.pixel; + } + + XColor blended; + blended.red = (int)((fg.red - bg.red) * a + bg.red); + blended.green = (int)((fg.green - bg.green) * a + bg.green); + blended.blue = (int)((fg.blue - bg.blue) * a + bg.blue); + XAllocColor(display, colormap, &blended); + + return blended.pixel; +} + +/* Contains general info on glyphs that doesn't change regardless of the context */ +typedef struct _LtkGlyphInfo { + unsigned char *alphamap; + unsigned int w; + unsigned int h; + unsigned int x_topleft; /* x offset from origin to top left corner of glyph */ + unsigned int y_topleft; /* y offset from origin to top left corner of glyph */ +} LtkGlyphInfo; + +/* Contains glyph info specific to one run of text */ +typedef struct _LtkGlyph { + LtkGlyphInfo *glyph_info; + /* Does all this need to be stored or are just the top left coordinates needed? */ + unsigned int x; /* top left x coordinate */ + unsigned int y; /* top left y coordinate */ + unsigned int x_offset; /* additional x offset given by harfbuzz */ + unsigned int y_offset; /* additional y offset given by harfbuzz */ + unsigned int x_advance; + unsigned int y_advance; + uint32_t cluster; /* index of char in original text - from harfbuzz */ + struct _LtkGlyph *next; +} LtkGlyph; + +typedef struct { + unsigned int width; + unsigned int height; + char *str; + LtkGlyph *start_glyph; +} LtkTextSegment; + +LtkFont * +ltk_load_font(char *path, unsigned int id) +{ + long len; + LtkFont *font = malloc(sizeof(LtkFont)); + if (!font) { + fprintf(stderr, "Out of memory!\n"); + exit(1); + } + char *contents = ltk_load_file(path, &len); + if (!stbtt_InitFont(&font->font_info, contents, 0)) + { + fprintf(stderr, "Failed to load font %s\n", path); + exit(1); + } + hb_blob_t *blob = hb_blob_create(contents, len, HB_MEMORY_MODE_READONLY, NULL, NULL); + hb_face_t *face = hb_face_create(blob, 0); + hb_blob_destroy(blob); + font->hb = hb_font_create(face); + hb_face_destroy(face); + hb_ot_font_set_funcs(font->hb); + font->id = id; + return font; +} + +unsigned char * +ltk_render_text_bitmap( + uint8_t *text, + LtkFont *font, + int size, + int *width, + int *height) +{ + /* (x1*, y1*): top left corner (relative to origin and absolute) + (x2*, y2*): bottom right corner (relative to origin and absolute) */ + int x1, x2, y1, y2, x1_abs, x2_abs, y1_abs, y2_abs, x_abs = 0, y_abs = 0; + int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN; + int char_w, char_h, x_off, y_off; + + int byte_offset; + int alpha; + /* FIXME: Change to uint8_t? */ + unsigned char *bitmap; + unsigned char *char_bitmap; + hb_buffer_t *buf; + hb_glyph_info_t *ginf, *gi; + hb_glyph_position_t *gpos, *gp; + unsigned int text_len = 0; + int text_bytes = strlen(text); + if (text_bytes < 1) { + printf("WARNING: ltk_render_text: length of text is less than 1.\n"); + return ""; + } + + buf = hb_buffer_create(); + hb_buffer_set_flags(buf, HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT); + hb_buffer_add_utf8(buf, text, text_bytes, 0, text_bytes); + hb_buffer_guess_segment_properties(buf); + hb_shape(font->font, buf, NULL, 0); + ginf = hb_buffer_get_glyph_infos(buf, &text_len); + gpos = hb_buffer_get_glyph_positions(buf, &text_len); + float scale = stbtt_ScaleForMappingEmToPixels(&font->font_info, size); + + /* Calculate size of bitmap */ + for (int i = 0; i < text_len; i++) { + gi = &ginf[i]; + gp = &gpos[i]; + stbtt_GetGlyphBitmapBox(&font->font_info, gi->codepoint, scale, scale, &x1, &y1, &x2, &y2); + x1_abs = (int)(x_abs + x1 + gp->x_offset * scale); + y1_abs = (int)(y_abs + y1 - gp->y_offset * scale); + x2_abs = x1_abs + (x2 - x1); + y2_abs = y1_abs + (y2 - y1); + if (x1_abs < x_min) x_min = x1_abs; + if (y1_abs < y_min) y_min = y1_abs; + if (x2_abs > x_max) x_max = x2_abs; + if (y2_abs > y_max) y_max = y2_abs; + x_abs += (gp->x_advance * scale); + y_abs -= (gp->y_advance * scale); + } + x_abs = -x_min; + y_abs = -y_min; + *width = x_max - x_min; + *height = y_max - y_min; + /* FIXME: calloc checks for integer overflow, right? */ + /* FIXME: check if null returned */ + bitmap = calloc(*width * *height, sizeof(char)); + if (!bitmap) { + fprintf(stderr, "Can't allocate memory for bitmap!\n"); + exit(1); + } + for (int i = 0; i < text_len; i++) { + gi = &ginf[i]; + gp = &gpos[i]; + stbtt_GetGlyphBitmapBox(&font->font_info, gi->codepoint, scale, scale, &x1, &y1, &x2, &y2); + char_bitmap = stbtt_GetGlyphBitmap(&font->font_info, scale, scale, gi->codepoint, &char_w, &char_h, &x_off, &y_off); + + x1_abs = (int)(x_abs + x1 + gp->x_offset * scale); + y1_abs = (int)(y_abs + y1 - gp->y_offset * scale); + for (int k = 0; k < char_h; k++) + { + for (int j = 0; j < char_w; j++) + { + byte_offset = (y1_abs + k) * *width + x1_abs + j; + alpha = bitmap[byte_offset] + char_bitmap[k * char_w + j]; + /* Cap at 255 so char doesn't overflow */ + bitmap[byte_offset] = alpha > 255 ? 255 : alpha; + } + } + free(char_bitmap); + + x_abs += gp->x_advance * scale; + y_abs -= gp->y_advance * scale; + } + return bitmap; +} + +Pixmap +ltk_render_text( + Display *dpy, + Window window, + GC gc, + XColor fg, + XColor bg, + Colormap colormap, + unsigned char *bitmap, + int width, + int height) +{ + XWindowAttributes attrs; + XGetWindowAttributes(dpy, window, &attrs); + int depth = attrs.depth; + Pixmap pix = XCreatePixmap(dpy, window, width, height, depth); + XSetForeground(dpy, gc, bg.pixel); + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + XSetForeground(dpy, gc, ltk_blend_pixel(dpy, colormap, fg, bg, bitmap[i * width + j] / 255.0)); + XDrawPoint(dpy, pix, gc, j, i); + } + } + return pix; +} + +int main(int argc, char *argv[]) +{ + Display *display; + int screen; + Window window; + GC gc; + + unsigned long black, white; + Colormap colormap; + display = XOpenDisplay((char *)0); + screen = DefaultScreen(display); + colormap = DefaultColormap(display, screen); + black = BlackPixel(display, screen); + white = WhitePixel(display, screen); + window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 1366, 512, 0, black, white); + XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL); + XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask); + gc = XCreateGC(display, window, 0, 0); + XSetBackground(display, gc, black); + XSetForeground(display, gc, black); + XClearWindow(display, window); + XMapRaised(display, window); + XColor c1, c2; + XParseColor(display, colormap, "#FFFFFF", &c1); + XParseColor(display, colormap, "#FF0000", &c2); + XAllocColor(display, colormap, &c1); + XAllocColor(display, colormap, &c2); + + LtkFont *font = ltk_load_font("NotoNastaliqUrdu-Regular.ttf"); + int w, h; + unsigned char *bitmap = ltk_render_text_bitmap("ہمارے بارے میں", font, 256, &w, &h); + Pixmap pix = ltk_render_text(display, window, gc, c1, c2, colormap, bitmap, w, h); + XCopyArea(display, pix, window, gc, 0, 0, w, h, 0, 0); + + XEvent event; + KeySym key; + char text[255]; + + while(1) + { + XNextEvent(display, &event); + if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) + { + XCopyArea(display, pix, window, gc, 0, 0, w, h, 0, 0); + if (text[0] == 'q') + { + XFreeGC(display, gc); + XFreeColormap(display, colormap); + XDestroyWindow(display, window); + XCloseDisplay(display); + exit(0); + } + } + } + + return 0; +} diff --git a/text/text-hb.new.c b/text/text-hb.new.c @@ -0,0 +1,386 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2017, 2018 lumidify <nobody@lumidify.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <limits.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <harfbuzz/hb.h> +#include <harfbuzz/hb-ot.h> +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */ +#include "khash.h" + +/* TODO: possibly "glyph manager" - only render glyph once and keep info in hash? + -> would be difficult because of different sizes - would need to keep track of all that. + -> reference counter - delete glyph from cache if not used anymore - good when there are many ligatures */ + +/* Font manager: hash for font path -> font id + hash for font id -> ltk font struct */ + +/* glyph id -> glyph info struct */ +KHASH_MAP_INIT_INT(glyphinfo, LtkGlyphInfo*) +/* font path, size -> glyph cache hash +KHASH_MAP_INIT_INT(glyphcache, khash_t(glyphinfo)) +/* font path -> font id */ +KHASH_MAP_INIT_STR(fontid, uint16_t) +/* font id -> font struct */ +KHASH_MAP_INIT_INT(fontstruct, LtkFont*) + +typedef struct LtkTextManager_ { + khash_t(fontid) *font_paths; + khash_t(fontstruct) *font_cache; + khash_t(glyphcache) *glyph_cache; + uint16_t font_id_cur; +} LtkTextManager; + +typedef struct { + stbtt_fontinfo info; + hb_font_t *hb; + uint16_t id; +} LtkFont; + +LtkTextManager * +ltk_init_text(void) +{ + LtkTextManager *m = malloc(sizeof LtkTextManager); + if (!m) ltk_fatal("Memory exhausted when trying to create text manager."); + m->font_paths = kh_init(fontid); + m->font_cache = kh_init(fontstruct); + m->glyph_cache = kh_init(glyphcache); + m->font_id_cur = 0; + return m; +} + +/* Contains general info on glyphs that doesn't change regardless of the context */ +typedef struct _LtkGlyphInfo { + unsigned char *alphamap; + unsigned int w; + unsigned int h; + unsigned int xoff; /* x offset from origin to top left corner of glyph */ + unsigned int yoff; /* y offset from origin to top left corner of glyph */ + unsigned int refs; + /* FIXME: does refs need to be long? It could cause problems if a + program tries to cache/"keep alive" a lot of pages of text. */ +} LtkGlyphInfo; + +LtkGlyphInfo * +ltk_create_glyph_info(LtkFont *font, unsigned int id, float scale) +{ + LtkGlyphInfo *glyph = malloc(sizeof(LtkGlyphInfo)); + if (!glyph) { + printf("Out of memory!\n"); + exit(1); + } + + glyph->alphamap = stbtt_GetGlyphBitmap( + &font->info, scale, scale, id, &glyph->w, + &glyph->h, &glyph->xoff, &glyph->yoff + ); + return glyph; +} + +LtkGlyphInfo * +ltk_get_glyph_info(LtkFont *font, unsigned int id, float scale, khash_t(glyphinfo) *cache) +{ + int ret; + khint_t k; + LtkGlyphInfo *glyph; + k = kh_get(glyphinfo, cache, id); + if (k == kh_end(cache)) { + glyph = ltk_create_glyph_info(font, id, scale); + glyph->refs = 0; + /* FIXME: error checking with ret */ + k = kh_put(glyphinfo, cache, glyph, &ret); + kh_value(cache, k) = glyph; + } else { + glyph = kh_value(cache, k); + glyph->refs++; + } + + return glyph; +} + + +void +ltk_create_glyph_cache(LtkTextManager *m, uint16_t font_id, uint16_t font_size) +{ + khash_t(glyphinfo) *cache = kh_init(glyphinfo); + int ret; + khint_t k; + /* I guess I can just ignore ret for now */ + k = kh_put(glyphcache, m->glyph_cache, font_id << 16 + font_size, &ret); + kh_value(m->glyph_cache, k) = cache; +} + +char * +ltk_load_file(const char *path, unsigned long *len) +{ + FILE *f; + char *contents; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + *len = ftell(f); + fseek(f, 0, SEEK_SET); + contents = malloc(*len + 1); + fread(contents, 1, *len, f); + contents[*len] = '\0'; + fclose(f); + return contents; +} + +unsigned long +ltk_blend_pixel(Display *display, Colormap colormap, XColor fg, XColor bg, double a) +{ + if (a >= 1.0) { + return fg.pixel; + } else if (a == 0.0) { + return bg.pixel; + } + + XColor blended; + blended.red = (int)((fg.red - bg.red) * a + bg.red); + blended.green = (int)((fg.green - bg.green) * a + bg.green); + blended.blue = (int)((fg.blue - bg.blue) * a + bg.blue); + XAllocColor(display, colormap, &blended); + + return blended.pixel; +} + +/* Contains glyph info specific to one run of text */ +typedef struct _LtkGlyph { + LtkGlyphInfo *glyph_info; + unsigned int x_offset; /* additional x offset given by harfbuzz */ + unsigned int y_offset; /* additional y offset given by harfbuzz */ + uint32_t cluster; /* index of char in original text - from harfbuzz */ + struct _LtkGlyph *next; +} LtkGlyph; + +typedef struct { + unsigned int width; + unsigned int height; + char *str; + LtkGlyph *start_glyph; +} LtkTextSegment; + +LtkFont * +ltk_create_font(char *path, unsigned int id) +{ + long len; + LtkFont *font = malloc(sizeof(LtkFont)); + if (!font) { + fprintf(stderr, "Out of memory!\n"); + exit(1); + } + char *contents = ltk_load_file(path, &len); + if (!stbtt_InitFont(&font->font_info, contents, 0)) + { + fprintf(stderr, "Failed to load font %s\n", path); + exit(1); + } + hb_blob_t *blob = hb_blob_create(contents, len, HB_MEMORY_MODE_READONLY, NULL, NULL); + hb_face_t *face = hb_face_create(blob, 0); + hb_blob_destroy(blob); + font->hb = hb_font_create(face); + hb_face_destroy(face); + hb_ot_font_set_funcs(font->hb); + font->id = id; + return font; +} + +unsigned char * +ltk_render_text_bitmap( + uint8_t *text, + LtkFont *font, + int size, + int *width, + int *height) +{ + /* (x1*, y1*): top left corner (relative to origin and absolute) + (x2*, y2*): bottom right corner (relative to origin and absolute) */ + int x1, x2, y1, y2, x1_abs, x2_abs, y1_abs, y2_abs, x_abs = 0, y_abs = 0; + int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN; + int char_w, char_h, x_off, y_off; + + int byte_offset; + int alpha; + /* FIXME: Change to uint8_t? */ + unsigned char *bitmap; + unsigned char *char_bitmap; + hb_buffer_t *buf; + hb_glyph_info_t *ginf, *gi; + hb_glyph_position_t *gpos, *gp; + unsigned int text_len = 0; + int text_bytes = strlen(text); + if (text_bytes < 1) { + printf("WARNING: ltk_render_text: length of text is less than 1.\n"); + return ""; + } + + buf = hb_buffer_create(); + hb_buffer_set_flags(buf, HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT); + hb_buffer_add_utf8(buf, text, text_bytes, 0, text_bytes); + hb_buffer_guess_segment_properties(buf); + hb_shape(font->font, buf, NULL, 0); + ginf = hb_buffer_get_glyph_infos(buf, &text_len); + gpos = hb_buffer_get_glyph_positions(buf, &text_len); + float scale = stbtt_ScaleForMappingEmToPixels(&font->font_info, size); + + /* Calculate size of bitmap */ + for (int i = 0; i < text_len; i++) { + gi = &ginf[i]; + gp = &gpos[i]; + stbtt_GetGlyphBitmapBox(&font->font_info, gi->codepoint, scale, scale, &x1, &y1, &x2, &y2); + x1_abs = (int)(x_abs + x1 + gp->x_offset * scale); + y1_abs = (int)(y_abs + y1 - gp->y_offset * scale); + x2_abs = x1_abs + (x2 - x1); + y2_abs = y1_abs + (y2 - y1); + if (x1_abs < x_min) x_min = x1_abs; + if (y1_abs < y_min) y_min = y1_abs; + if (x2_abs > x_max) x_max = x2_abs; + if (y2_abs > y_max) y_max = y2_abs; + x_abs += (gp->x_advance * scale); + y_abs -= (gp->y_advance * scale); + } + x_abs = -x_min; + y_abs = -y_min; + *width = x_max - x_min; + *height = y_max - y_min; + /* FIXME: calloc checks for integer overflow, right? */ + /* FIXME: check if null returned */ + bitmap = calloc(*width * *height, sizeof(char)); + if (!bitmap) { + fprintf(stderr, "Can't allocate memory for bitmap!\n"); + exit(1); + } + for (int i = 0; i < text_len; i++) { + gi = &ginf[i]; + gp = &gpos[i]; + stbtt_GetGlyphBitmapBox(&font->font_info, gi->codepoint, scale, scale, &x1, &y1, &x2, &y2); + char_bitmap = stbtt_GetGlyphBitmap(&font->font_info, scale, scale, gi->codepoint, &char_w, &char_h, &x_off, &y_off); + + x1_abs = (int)(x_abs + x1 + gp->x_offset * scale); + y1_abs = (int)(y_abs + y1 - gp->y_offset * scale); + for (int k = 0; k < char_h; k++) + { + for (int j = 0; j < char_w; j++) + { + byte_offset = (y1_abs + k) * *width + x1_abs + j; + alpha = bitmap[byte_offset] + char_bitmap[k * char_w + j]; + /* Cap at 255 so char doesn't overflow */ + bitmap[byte_offset] = alpha > 255 ? 255 : alpha; + } + } + free(char_bitmap); + + x_abs += gp->x_advance * scale; + y_abs -= gp->y_advance * scale; + } + return bitmap; +} + +Pixmap +ltk_render_text( + Display *dpy, + Window window, + GC gc, + XColor fg, + XColor bg, + Colormap colormap, + unsigned char *bitmap, + int width, + int height) +{ + XWindowAttributes attrs; + XGetWindowAttributes(dpy, window, &attrs); + int depth = attrs.depth; + Pixmap pix = XCreatePixmap(dpy, window, width, height, depth); + XSetForeground(dpy, gc, bg.pixel); + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + XSetForeground(dpy, gc, ltk_blend_pixel(dpy, colormap, fg, bg, bitmap[i * width + j] / 255.0)); + XDrawPoint(dpy, pix, gc, j, i); + } + } + return pix; +} + +int main(int argc, char *argv[]) +{ + Display *display; + int screen; + Window window; + GC gc; + + unsigned long black, white; + Colormap colormap; + display = XOpenDisplay((char *)0); + screen = DefaultScreen(display); + colormap = DefaultColormap(display, screen); + black = BlackPixel(display, screen); + white = WhitePixel(display, screen); + window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 1366, 512, 0, black, white); + XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL); + XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask); + gc = XCreateGC(display, window, 0, 0); + XSetBackground(display, gc, black); + XSetForeground(display, gc, black); + XClearWindow(display, window); + XMapRaised(display, window); + XColor c1, c2; + XParseColor(display, colormap, "#FFFFFF", &c1); + XParseColor(display, colormap, "#FF0000", &c2); + XAllocColor(display, colormap, &c1); + XAllocColor(display, colormap, &c2); + + LtkFont *font = ltk_load_font("NotoNastaliqUrdu-Regular.ttf"); + int w, h; + unsigned char *bitmap = ltk_render_text_bitmap("ہمارے بارے میں", font, 256, &w, &h); + Pixmap pix = ltk_render_text(display, window, gc, c1, c2, colormap, bitmap, w, h); + XCopyArea(display, pix, window, gc, 0, 0, w, h, 0, 0); + + XEvent event; + KeySym key; + char text[255]; + + while(1) + { + XNextEvent(display, &event); + if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) + { + XCopyArea(display, pix, window, gc, 0, 0, w, h, 0, 0); + if (text[0] == 'q') + { + XFreeGC(display, gc); + XFreeColormap(display, colormap); + XDestroyWindow(display, window); + XCloseDisplay(display); + exit(0); + } + } + } + + return 0; +} diff --git a/text/text-hb.ubernew.c b/text/text-hb.ubernew.c @@ -0,0 +1,452 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2017, 2018 lumidify <nobody@lumidify.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <limits.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <harfbuzz/hb.h> +#include <harfbuzz/hb-ot.h> +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */ +#include "khash.h" + +typedef struct { + stbtt_fontinfo info; + hb_font_t *hb; + uint16_t id; + unsigned int refs; +} LtkFont; + +/* Contains general info on glyphs that doesn't change regardless of the context */ +typedef struct _LtkGlyphInfo { + unsigned char *alphamap; + unsigned int w; + unsigned int h; + unsigned int xoff; /* x offset from origin to top left corner of glyph */ + unsigned int yoff; /* y offset from origin to top left corner of glyph */ + unsigned int refs; + /* FIXME: does refs need to be long? It could cause problems if a + program tries to cache/"keep alive" a lot of pages of text. */ +} LtkGlyphInfo; + +/* Contains glyph info specific to one run of text */ +typedef struct _LtkGlyph { + LtkGlyphInfo *info; + int x_offset; /* additional x offset given by harfbuzz */ + int y_offset; /* additional y offset given by harfbuzz */ + int x_advance; + int y_advance; + uint32_t cluster; /* index of char in original text - from harfbuzz */ + struct _LtkGlyph *next; +} LtkGlyph; + +typedef struct { + unsigned int w; + unsigned int h; + int start_x; + int start_y; + char *str; + LtkGlyph *start_glyph; +} LtkTextSegment; + +/* Hash definitions */ +/* glyph id -> glyph info struct */ +KHASH_MAP_INIT_INT(glyphinfo, LtkGlyphInfo*) +/* font path, size -> glyph cache hash */ +KHASH_MAP_INIT_INT(glyphcache, khash_t(glyphinfo)*) +/* font path -> font id */ +KHASH_MAP_INIT_STR(fontid, uint16_t) +/* font id -> font struct */ +KHASH_MAP_INIT_INT(fontstruct, LtkFont*) + +typedef struct LtkTextManager_ { + khash_t(fontid) *font_paths; + khash_t(fontstruct) *font_cache; + khash_t(glyphcache) *glyph_cache; + uint16_t font_id_cur; +} LtkTextManager; + +LtkTextManager * +ltk_init_text(void) +{ + LtkTextManager *tm = malloc(sizeof(LtkTextManager)); + if (!tm) { + printf("Memory exhausted when trying to create text manager."); + exit(1); + } + tm->font_paths = kh_init(fontid); + tm->font_cache = kh_init(fontstruct); + tm->glyph_cache = kh_init(glyphcache); + tm->font_id_cur = 0; + + return tm; +} + +LtkGlyphInfo * +ltk_create_glyph_info(LtkFont *font, unsigned int id, float scale) +{ + LtkGlyphInfo *glyph = malloc(sizeof(LtkGlyphInfo)); + if (!glyph) { + printf("Out of memory!\n"); + exit(1); + } + + glyph->alphamap = stbtt_GetGlyphBitmap( + &font->info, scale, scale, id, &glyph->w, + &glyph->h, &glyph->xoff, &glyph->yoff + ); + + return glyph; +} + +LtkGlyphInfo * +ltk_get_glyph_info(LtkFont *font, unsigned int id, float scale, khash_t(glyphinfo) *cache) +{ + int ret; + khint_t k; + LtkGlyphInfo *glyph; + k = kh_get(glyphinfo, cache, id); + if (k == kh_end(cache)) { + glyph = ltk_create_glyph_info(font, id, scale); + glyph->refs = 0; + /* FIXME: error checking with ret */ + k = kh_put(glyphinfo, cache, id, &ret); + kh_value(cache, k) = glyph; + } else { + glyph = kh_value(cache, k); + glyph->refs++; + } + + return glyph; +} + + +khint_t +ltk_create_glyph_cache(LtkTextManager *tm, uint16_t font_id, uint16_t font_size) +{ + khash_t(glyphinfo) *cache = kh_init(glyphinfo); + int ret; + khint_t k; + /* I guess I can just ignore ret for now */ + k = kh_put(glyphcache, tm->glyph_cache, font_id << 16 + font_size, &ret); + kh_value(tm->glyph_cache, k) = cache; + + return k; +} + +char * +ltk_load_file(const char *path, unsigned long *len) +{ + FILE *f; + char *contents; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + *len = ftell(f); + fseek(f, 0, SEEK_SET); + contents = malloc(*len + 1); + fread(contents, 1, *len, f); + contents[*len] = '\0'; + fclose(f); + return contents; +} + +unsigned long +ltk_blend_pixel(Display *display, Colormap colormap, XColor fg, XColor bg, double a) +{ + if (a >= 1.0) { + return fg.pixel; + } else if (a == 0.0) { + return bg.pixel; + } + + XColor blended; + blended.red = (int)((fg.red - bg.red) * a + bg.red); + blended.green = (int)((fg.green - bg.green) * a + bg.green); + blended.blue = (int)((fg.blue - bg.blue) * a + bg.blue); + XAllocColor(display, colormap, &blended); + + return blended.pixel; +} + +LtkFont * +ltk_create_font(char *path, uint16_t id) +{ + long len; + LtkFont *font = malloc(sizeof(LtkFont)); + if (!font) { + fprintf(stderr, "Out of memory!\n"); + exit(1); + } + char *contents = ltk_load_file(path, &len); + if (!stbtt_InitFont(&font->info, contents, 0)) + { + fprintf(stderr, "Failed to load font %s\n", path); + exit(1); + } + hb_blob_t *blob = hb_blob_create(contents, len, HB_MEMORY_MODE_READONLY, NULL, NULL); + hb_face_t *face = hb_face_create(blob, 0); + hb_blob_destroy(blob); + font->hb = hb_font_create(face); + hb_face_destroy(face); + hb_ot_font_set_funcs(font->hb); + font->id = id; + return font; +} + +/* FIXME: need to figure out how exactly the whole font system is going to work, especially with default fonts, etc. */ +uint16_t +ltk_load_font(LtkTextManager *tm, char *path) +{ + LtkFont *font = ltk_create_font(path, tm->font_id_cur++); + int ret; + khint_t k; + k = kh_put(fontid, tm->font_paths, path, &ret); + kh_value(tm->font_paths, k) = font->id; + k = kh_put(fontstruct, tm->font_cache, (khint_t) font->id, &ret); + kh_value(tm->font_cache, k) = font; + + return font->id; +} + +uint16_t +ltk_get_font(LtkTextManager *tm, char *path) +{ + int ret; + khint_t k; + uint16_t id; + k = kh_get(fontid, tm->font_paths, path); + if (k == kh_end(tm->font_paths)) { + id = ltk_load_font(tm, path); + } else { + id = kh_value(tm->font_paths, k); + } + + return id; +} + +/* FIXME: could use unsigned int for fontid and size as long as there is code to check neither of them become too large + -> in case I want to get rid of uint_16_t, etc. */ +LtkTextSegment * +ltk_create_text_segment(LtkTextManager *tm, char *text, uint16_t fontid, uint16_t size) +{ + /* (x1*, y1*): top left corner (relative to origin and absolute) + (x2*, y2*): bottom right corner (relative to origin and absolute) */ + LtkFont *font; + khash_t(glyphinfo) *glyph_cache; + khint_t k; + + k = kh_get(fontstruct, tm->font_cache, fontid); + font = kh_value(tm->font_cache, k); + /* FIXME: when should refs be increased? maybe only at the end in case + it has to return for some other reason (out of memory, etc.) */ + font->refs++; + + uint32_t attr = fontid << 16 + size; + /* FIXME: turn this int ltk_get_glyph_cache */ + k = kh_get(glyphcache, tm->glyph_cache, attr); + if (k == kh_end(tm->glyph_cache)) { + k = ltk_create_glyph_cache(tm, fontid, size); + } + glyph_cache = kh_value(tm->glyph_cache, k); + + LtkTextSegment *ts = malloc(sizeof(LtkTextSegment)); + if (!ts) { + fprintf(stderr, "Out of memory!\n"); + exit(1); + } + ts->str = text; + + hb_buffer_t *buf; + hb_glyph_info_t *ginf, *gi; + hb_glyph_position_t *gpos, *gp; + unsigned int text_len = 0; + int text_bytes = strlen(text); + if (text_bytes < 1) { + printf("WARNING: ltk_render_text_segment: length of text is less than 1.\n"); + } + + buf = hb_buffer_create(); + hb_buffer_set_flags(buf, HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT); + hb_buffer_add_utf8(buf, text, text_bytes, 0, text_bytes); + hb_buffer_guess_segment_properties(buf); + hb_shape(font->hb, buf, NULL, 0); + ginf = hb_buffer_get_glyph_infos(buf, &text_len); + gpos = hb_buffer_get_glyph_positions(buf, &text_len); + float scale = stbtt_ScaleForMappingEmToPixels(&font->info, size); + LtkGlyph *last_glyph = NULL; + + int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN; + int x_abs = 0, y_abs = 0, x1_abs, y1_abs, x2_abs, y2_abs; + for (int i = 0; i < text_len; i++) { + gi = &ginf[i]; + gp = &gpos[i]; + LtkGlyph *glyph = malloc(sizeof(LtkGlyph)); + glyph->info = ltk_get_glyph_info(font, gi->codepoint, scale, glyph_cache); + /* FIXME: round instead of just casting */ + glyph->x_offset = (int)(gp->x_offset * scale); + glyph->y_offset = (int)(gp->y_offset * scale); + glyph->x_advance = (int)(gp->x_advance * scale); + glyph->y_advance = (int)(gp->y_advance * scale); + glyph->next = NULL; + if (i == 0) { + ts->start_glyph = glyph; + } else { + last_glyph->next = glyph; + } + last_glyph = glyph; + + /* Calculate position in order to determine full size of text segment */ + x1_abs = x_abs + glyph->info->xoff + glyph->x_offset; + y1_abs = y_abs + glyph->info->yoff - glyph->y_offset; + x2_abs = x1_abs + glyph->info->w; + y2_abs = y1_abs + glyph->info->h; + if (x1_abs < x_min) x_min = x1_abs; + if (y1_abs < y_min) y_min = y1_abs; + if (x2_abs > x_max) x_max = x2_abs; + if (y2_abs > y_max) y_max = y2_abs; + x_abs += glyph->x_advance; + y_abs -= glyph->y_advance; + } + /* FIXME: what was this supposed to do? + I think it was supposed to be the start drawing position, but why would this not be 0? + I'm guessing it had something to do with the fact that I need to calculate where the + actual top left corner of the glyph is since harfbuzz gives me the origin - maybe I + should just store that position directly in LtkGlyph? Is there any need to advance, etc. + later on after I've positioned the glyphs? Well, I guess I'll figure that out eventually... */ + ts->start_x = -x_min; + ts->start_y = -y_min; + ts->w = x_max - x_min; + ts->h = y_max - y_min; + return ts; +} + +Pixmap +ltk_render_text_segment( + LtkTextSegment *ts, + Display *dpy, + Window window, + GC gc, + Colormap colormap, + XColor fg, + XColor bg) +{ + XWindowAttributes attrs; + XGetWindowAttributes(dpy, window, &attrs); + int depth = attrs.depth; + Pixmap pix = XCreatePixmap(dpy, window, ts->w, ts->h, depth); + unsigned char *alphamap = calloc(ts->w * ts->h, sizeof(char)); + XSetForeground(dpy, gc, bg.pixel); + XFillRectangle(dpy, pix, gc, 0, 0, ts->w, ts->h); + LtkGlyph *glyph = ts->start_glyph; + int x_cur = ts->start_x; + int y_cur = ts->start_y; + int x, y; + int alpha, byte_offset; + /* Create alphamap of entire text segment */ + do { + x = x_cur + glyph->info->xoff + glyph->x_offset; + y = y_cur + glyph->info->yoff - glyph->y_offset; + for (int i = 0; i < glyph->info->h; i++) { + for (int j = 0; j < glyph->info->w; j++) { + byte_offset = (y + i) * ts->w + x + j; + alpha = alphamap[byte_offset] + glyph->info->alphamap[i * glyph->info->w + j]; + /* Cap at 255 so char doesn't overflow */ + alphamap[byte_offset] = alpha > 255 ? 255 : alpha; + } + } + x_cur += glyph->x_advance; + y_cur -= glyph->y_advance; + } while (glyph = glyph->next); + + /* Draw alphamap to pixmap */ + for (int i = 0; i < ts->h; i++) { + for (int j = 0; j < ts->w; j++) { + XSetForeground(dpy, gc, ltk_blend_pixel(dpy, colormap, fg, bg, alphamap[i * ts->w + j] / 255.0)); + XDrawPoint(dpy, pix, gc, j, i); + } + } + + return pix; +} + +int main(int argc, char *argv[]) +{ + Display *display; + int screen; + Window window; + GC gc; + + unsigned long black, white; + Colormap colormap; + display = XOpenDisplay((char *)0); + screen = DefaultScreen(display); + colormap = DefaultColormap(display, screen); + black = BlackPixel(display, screen); + white = WhitePixel(display, screen); + window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 1366, 512, 0, black, white); + XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL); + XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask); + gc = XCreateGC(display, window, 0, 0); + XSetBackground(display, gc, black); + XSetForeground(display, gc, black); + XClearWindow(display, window); + XMapRaised(display, window); + XColor c1, c2; + XParseColor(display, colormap, "#FFFFFF", &c1); + XParseColor(display, colormap, "#FF0000", &c2); + XAllocColor(display, colormap, &c1); + XAllocColor(display, colormap, &c2); + + LtkTextManager *tm = ltk_init_text(); + uint16_t font_id = ltk_get_font(tm, "NotoNastaliqUrdu-Regular.ttf"); + uint16_t font_size = 100; + LtkTextSegment *text = ltk_create_text_segment(tm, "ہمارے بارے میں", font_id, font_size); + Pixmap pix = ltk_render_text_segment(text, display, window, gc, colormap, c1, c2); + XCopyArea(display, pix, window, gc, 0, 0, text->w, text->h, 0, 0); + + XEvent event; + KeySym key; + char txt[255]; + + while(1) + { + XNextEvent(display, &event); + if (event.type == KeyPress && XLookupString(&event.xkey, txt, 255, &key, 0) == 1) + { + XCopyArea(display, pix, window, gc, 0, 0, text->w, text->h, 0, 0); + if (txt[0] == 'q') + { + XFreeGC(display, gc); + XFreeColormap(display, colormap); + XDestroyWindow(display, window); + XCloseDisplay(display); + exit(0); + } + } + } + + return 0; +} diff --git a/text/text.c b/text/text.c @@ -0,0 +1,247 @@ +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include "stb_truetype.h" +#include <graphite2/Segment.h> + +/* These unicode routines are taken from + * https://github.com/JeffBezanson/cutef8 */ + +/* is c the start of a utf8 sequence? */ +#define isutf(c) (((c)&0xC0)!=0x80) + +static const uint32_t offsetsFromUTF8[6] = { + 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL +}; + +/* next character without NUL character terminator */ +uint32_t u8_nextmemchar(const char *s, size_t *i) +{ + uint32_t ch = 0; + size_t sz = 0; + do { + ch <<= 6; + ch += (unsigned char)s[(*i)++]; + sz++; + } while (!isutf(s[*i])); + ch -= offsetsFromUTF8[sz-1]; + + return ch; +} + + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */ + +stbtt_fontinfo ltk_load_font(const char *path) +{ + FILE *f; + long len; + char *contents; + stbtt_fontinfo info; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + contents = malloc(len + 1); + fread(contents, 1, len, f); + contents[len] = '\0'; + fclose(f); + if (!stbtt_InitFont(&info, contents, 0)) + { + fprintf(stderr, "Failed to load font %s\n", path); + exit(1); + } + return info; +} + +int ltk_text_width(uint8_t *text, stbtt_fontinfo fontinfo, int height) +{ + float scale = stbtt_ScaleForPixelHeight(&fontinfo, height); + size_t i = 0; + int length = strlen(text); + if (length < 1) return 0; + int temp_x; + int kern_advance; + int width = 0; + uint32_t char1; + uint32_t char2; + char1 = u8_nextmemchar(text, &i); + while(i <= length) + { + stbtt_GetCodepointHMetrics(&fontinfo, char1, &temp_x, 0); + width += temp_x * scale; + + char2 = u8_nextmemchar(text, &i); + if (!char2) break; + kern_advance = stbtt_GetCodepointKernAdvance(&fontinfo, char1, char2); + width += kern_advance * scale; + char1 = char2; + } + + return width; +} + +Pixmap ltk_render_text( + Display *display, + Window window, + GC gc, + uint8_t *text, + stbtt_fontinfo fontinfo, + int height, + unsigned long fg, + unsigned long bg, + const char *font_ +) +{ + int rtl = 1; /*are we rendering right to left? probably not */ + int pointsize = 24; /*point size in points */ + int dpi = 96; /*work with this many dots per inch */ + char *pError; /*location of faulty utf-8 */ + gr_font *font = NULL; + size_t numCodePoints = 0; + gr_segment *seg = NULL; + const gr_slot *s; + gr_face *face = gr_make_file_face(font_, 0); + if (!face) return 1; + //font = gr_make_font(pointsize * dpi / 72.0f, face); + font = gr_make_font(pointsize * dpi / 72.0f, face); + if (!font) return 2; + numCodePoints = gr_count_unicode_characters(gr_utf8, text, NULL, (const void **)(&pError)); + if (pError) return 3; + seg = gr_make_seg(font, face, 0, 0, gr_utf8, text, numCodePoints, rtl); + if (!seg) return 3; + for (s = gr_seg_first_slot(seg); s; s = gr_slot_next_in_segment(s)) + printf("%d(%f,%f) ", gr_slot_gid(s), gr_slot_origin_X(s), gr_slot_origin_Y(s)); + //printf("\n%f\n", gr_seg_advance_X(seg)); + + XWindowAttributes attrs; + XGetWindowAttributes(display, window, &attrs); + int depth = attrs.depth; + +// int width = ltk_text_width(text, fontinfo, height); + int width = (int)gr_seg_advance_X(seg) + 100; + unsigned char *bitmap = calloc(sizeof(char), width * (height + 200)); + //float scale = stbtt_ScaleForPixelHeight(&fontinfo, pointsize * dpi / 72.0f); + float scale = stbtt_ScaleForMappingEmToPixels(&fontinfo, pointsize * dpi / 72.0f); + + int ascent, descent, line_gap; + stbtt_GetFontVMetrics(&fontinfo, &ascent, &descent, &line_gap); + ascent *= scale; + descent *= scale; + + size_t i = 0; + int length = strlen(text); + if (length < 1) + { + printf("WARNING: ltk_render_text: length of text is less than 1.\n"); + return XCreatePixmap(display, window, 0, 0, depth); + } + uint32_t char1, char2; + char1 = u8_nextmemchar(text, &i); + int ax, lsb, x = 0, y = 0, x1, y1, x2, y2, byte_offset, kern_advance; + int w, h, xoff, yoff; + int y_abs = 0; +// while (i <= length) + unsigned char *b; + for (s = gr_seg_first_slot(seg); s; s = gr_slot_next_in_segment(s)) + { + stbtt_GetGlyphBitmapBox(&fontinfo, gr_slot_gid(s), scale, scale, &x1, &y1, &x2, &y2); + //stbtt_MakeGlyphBitmap(&fontinfo, bitmap + byte_offset, x2 - x1, y2 - y1, width, scale, scale, gr_slot_gid(s)); + b = stbtt_GetGlyphBitmap(&fontinfo, scale, scale, gr_slot_gid(s), &w, &h, &xoff, &yoff); + x = (int)(50 + xoff + gr_slot_origin_X(s)); + y = (int)(200 + yoff - gr_slot_origin_Y(s)); + for (int i = 0; i < h; i++) + { + for (int j = 0; j < w; j++) + { + byte_offset = (y + i) * width + x + j; + bitmap[byte_offset] = bitmap[byte_offset] + b[i * w + j]; + if (bitmap[byte_offset] > 255) bitmap[byte_offset] = 255; + } + } + free(b); + } + gr_seg_destroy(seg); + gr_font_destroy(font); + gr_face_destroy(face); + + /* TODO: separate this into a separate function so that one function only creates + * the bitmap and other functions to turn that into a pixmap with solid color + * background, image background, etc. + */ + Pixmap rendered = XCreatePixmap(display, window, width, height + 200, depth); + XSetForeground(display, gc, bg); + for (int i = 0; i < (height + 200); i++) + { + for (int j = 0; j < width; j++) + { + /* Yay! Magic! */ + XSetForeground(display, gc, (bitmap[i * width + j] / 255.0) * fg + (1 - bitmap[i * width + j] / 255.0) * bg); + XDrawPoint(display, rendered, gc, j, i); + } + } + XSetForeground(display, gc, bg); + return rendered; +} + +int main(int argc, char *argv[]) +{ + Display *display; + int screen; + Window window; + GC gc; + + unsigned long black, white; + Colormap colormap; + display = XOpenDisplay((char *)0); + screen = DefaultScreen(display); + colormap = DefaultColormap(display, screen); + black = BlackPixel(display, screen); + white = WhitePixel(display, screen); + window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 1366, 512, 0, black, white); + XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL); + XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask); + gc = XCreateGC(display, window, 0, 0); + XSetBackground(display, gc, black); + XSetForeground(display, gc, black); + XClearWindow(display, window); + XMapRaised(display, window); + XColor c1, c2; + XParseColor(display, colormap, "#FFFFFF", &c1); + XParseColor(display, colormap, "#FF0000", &c2); + XAllocColor(display, colormap, &c1); + XAllocColor(display, colormap, &c2); + + stbtt_fontinfo fontinfo = ltk_load_font("Awami_beta3.ttf"); + int width = ltk_text_width("ہمارے بارے میںffsafdsfasfasf", fontinfo, 100); + Pixmap pix = ltk_render_text(display, window, gc, "ہمارے بارے میں", fontinfo, 64, c1.pixel, c2.pixel, "Awami_beta3.ttf"); +// Pixmap pix = ltk_render_text(display, window, gc, "Bob", fontinfo, 64, c1.pixel, c2.pixel, "GentiumPlus-R.ttf"); + XCopyArea(display, pix, window, gc, 0, 0, width, 64, 0, 0); + + XEvent event; + KeySym key; + char text[255]; + + while(1) + { + XNextEvent(display, &event); + if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) + { + XCopyArea(display, pix, window, gc, 0, 0, width, 300, 0, 0); + if (text[0] == 'q') + { + XFreeGC(display, gc); + XFreeColormap(display, colormap); + XDestroyWindow(display, window); + XCloseDisplay(display); + exit(0); + } + } + } + + return 0; +} diff --git a/text/text1.c b/text/text1.c @@ -0,0 +1,312 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include "stb_truetype.h" +#include <harfbuzz/hb.h> +#include <harfbuzz/hb-ot.h> +#include <limits.h> + +/* These unicode routines are taken from + * https://github.com/JeffBezanson/cutef8 */ + +/* is c the start of a utf8 sequence? */ +#define isutf(c) (((c)&0xC0)!=0x80) + +static const uint32_t offsetsFromUTF8[6] = { + 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL +}; + +/* next character without NUL character terminator */ +uint32_t u8_nextmemchar(const char *s, size_t *i) +{ + uint32_t ch = 0; + size_t sz = 0; + do { + ch <<= 6; + ch += (unsigned char)s[(*i)++]; + sz++; + } while (!isutf(s[*i])); + ch -= offsetsFromUTF8[sz-1]; + + return ch; +} + + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */ + +stbtt_fontinfo ltk_load_font(const char *path) +{ + FILE *f; + long len; + char *contents; + stbtt_fontinfo info; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + contents = malloc(len + 1); + fread(contents, 1, len, f); + contents[len] = '\0'; + fclose(f); + if (!stbtt_InitFont(&info, contents, 0)) + { + fprintf(stderr, "Failed to load font %s\n", path); + exit(1); + } + return info; +} + +char *ltk_load_file(const char *path, unsigned long *len) +{ + FILE *f; + char *contents; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + *len = ftell(f); + fseek(f, 0, SEEK_SET); + contents = malloc(*len + 1); + fread(contents, 1, *len, f); + contents[*len] = '\0'; + fclose(f); + return contents; +} + +int ltk_text_width(uint8_t *text, stbtt_fontinfo fontinfo, int height) +{ + float scale = stbtt_ScaleForMappingEmToPixels(&fontinfo, height); + size_t i = 0; + int length = strlen(text); + if (length < 1) return 0; + int temp_x; + int kern_advance; + int width = 0; + uint32_t char1; + uint32_t char2; + char1 = u8_nextmemchar(text, &i); + while(i <= length) + { + stbtt_GetCodepointHMetrics(&fontinfo, char1, &temp_x, 0); + width += temp_x * scale; + + char2 = u8_nextmemchar(text, &i); + if (!char2) break; + kern_advance = stbtt_GetCodepointKernAdvance(&fontinfo, char1, char2); + width += kern_advance * scale; + char1 = char2; + } + + return width; +} + +unsigned long ltk_blend_pixel(Display *display, Colormap colormap, XColor fg, XColor bg, double a) +{ + XColor blended; + if (a == 1.0) + return fg.pixel; + else if (a == 0.0) + return bg.pixel; + blended.red = (int)((fg.red - bg.red) * a + bg.red); + blended.green = (int)((fg.green - bg.green) * a + bg.green); + blended.blue = (int)((fg.blue - bg.blue) * a + bg.blue); + XAllocColor(display, colormap, &blended); + + return blended.pixel; +} + +Pixmap ltk_render_text( + Display *display, + Window window, + GC gc, + uint8_t *text, + stbtt_fontinfo fontinfo, + int height, + XColor fg, + XColor bg, + const char *font_, + Colormap colormap, + int *w_total, + int *h_total +) +{ + hb_blob_t *blob; + hb_face_t *face; + size_t filelen = 0; + char *filedata = ltk_load_file(font_, &filelen); + blob = hb_blob_create(filedata, filelen, HB_MEMORY_MODE_READONLY, NULL, NULL); + face = hb_face_create(blob, 0); + hb_blob_destroy(blob); + hb_font_t *hbfont = hb_font_create(face); + hb_face_destroy(face); + hb_ot_font_set_funcs(hbfont); + + hb_buffer_t *buf; + hb_glyph_info_t *ginf; + hb_glyph_position_t *gpos; + unsigned int len = 0; + + buf = hb_buffer_create(); + hb_buffer_set_flags(buf, HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT); + hb_buffer_add_utf8(buf, text, strlen(text), 0, strlen(text)); + hb_buffer_guess_segment_properties(buf); + hb_shape(hbfont, buf, NULL, 0); + + ginf = hb_buffer_get_glyph_infos(buf, &len); + gpos = hb_buffer_get_glyph_positions(buf, &len); + + XWindowAttributes attrs; + XGetWindowAttributes(display, window, &attrs); + int depth = attrs.depth; + + int width = ltk_text_width(text, fontinfo, height) + 200; + float scale = stbtt_ScaleForMappingEmToPixels(&fontinfo, height); + + int ascent, descent, line_gap; + stbtt_GetFontVMetrics(&fontinfo, &ascent, &descent, &line_gap); + ascent *= scale; + descent *= scale; + + Pixmap rendered = XCreatePixmap(display, window, width, height + 200, depth); + int length = strlen(text); + if (length < 1) + { + printf("WARNING: ltk_render_text: length of text is less than 1.\n"); + return XCreatePixmap(display, window, 0, 0, depth); + } + int ax, x = 0, y = 0, x1, y1, x2, y2, byte_offset, kern_advance; + double x_abs = 0, y_abs = 0; + unsigned char *b; + int w, h, xoff, yoff; + int x_min = INT_MAX, x_max = INT_MIN, y_min = INT_MAX, y_max = INT_MIN; + int x_min1, x_max1, y_min1, y_max1; + for (int i = 0; i < len; i++) { + hb_glyph_info_t *gi = &ginf[i]; + hb_glyph_position_t *gp = &gpos[i]; + stbtt_GetGlyphBitmapBox(&fontinfo, gi->codepoint, scale, scale, &x1, &y1, &x2, &y2); + x_min1 = (int)(x_abs + x1 + gp->x_offset * scale); + y_min1 = (int)(y_abs + y1 - gp->y_offset * scale); + x_max1 = x_min1 + (x2 - x1); + y_max1 = y_min1 + (y2 - y1); + if (x_min1 < x_min) x_min = x_min1; + if (y_min1 < y_min) y_min = y_min1; + if (x_max1 > x_max) x_max = x_max1; + if (y_max1 > y_max) y_max = y_max1; + x_abs += (gp->x_advance * scale); + y_abs -= (gp->y_advance * scale); + } + x_abs = -x_min; + y_abs = -y_min; + *w_total = x_max - x_min; + *h_total = y_max - y_min; + /* FIXME: calloc checks for integer overflow, right? */ + /* FIXME: check if null returned */ + unsigned char *bitmap = calloc(*w_total * *h_total, sizeof(char)); + for (int i = 0; i < len; i++) { + hb_glyph_info_t *gi = &ginf[i]; + hb_glyph_position_t *gp = &gpos[i]; + stbtt_GetGlyphBitmapBox(&fontinfo, gi->codepoint, scale, scale, &x1, &y1, &x2, &y2); + b = stbtt_GetGlyphBitmap(&fontinfo, scale, scale, gi->codepoint, &w, &h, &xoff, &yoff); + + //x = (int)(x_abs + xoff + gp->x_offset * scale); + //y = (int)(y_abs + yoff - gp->y_offset * scale); + x = (int)(x_abs + x1 + gp->x_offset * scale); + y = (int)(y_abs + y1 - gp->y_offset * scale); + for (int i = 0; i < h; i++) + { + for (int j = 0; j < w; j++) + { + byte_offset = (y + i) * *w_total + x + j; + bitmap[byte_offset] = bitmap[byte_offset] + b[i * w + j]; + /* This *should* always be false, right? */ + if (bitmap[byte_offset] > 255) bitmap[byte_offset] = 255; + } + } + free(b); + + x_abs += (gp->x_advance * scale); + y_abs -= (gp->y_advance * scale); + } + + XSetForeground(display, gc, bg.pixel); + for (int i = 0; i < *h_total; i++) + { + for (int j = 0; j < *w_total; j++) + { + /* Yay! Magic! */ + XSetForeground(display, gc, ltk_blend_pixel(display, colormap, fg, bg, (bitmap[i * *w_total + j] / 255.0))); + XDrawPoint(display, rendered, gc, j, i); + } + } + XSetForeground(display, gc, bg.pixel); + + /* TODO: separate this into a separate function so that one function only creates + * the bitmap and other functions to turn that into a pixmap with solid color + * background, image background, etc. + */ + return rendered; +} + +int main(int argc, char *argv[]) +{ + Display *display; + int screen; + Window window; + GC gc; + + unsigned long black, white; + Colormap colormap; + display = XOpenDisplay((char *)0); + screen = DefaultScreen(display); + colormap = DefaultColormap(display, screen); + black = BlackPixel(display, screen); + white = WhitePixel(display, screen); + window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 1366, 512, 0, black, white); + XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL); + XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask); + gc = XCreateGC(display, window, 0, 0); + XSetBackground(display, gc, black); + XSetForeground(display, gc, black); + XClearWindow(display, window); + XMapRaised(display, window); + XColor c1, c2; + XParseColor(display, colormap, "#FFFFFF", &c1); + XParseColor(display, colormap, "#FF0000", &c2); + XAllocColor(display, colormap, &c1); + XAllocColor(display, colormap, &c2); + +// stbtt_fontinfo fontinfo = ltk_load_font("GentiumPlus-R.ttf"); + // stbtt_fontinfo fontinfo = ltk_load_font("NotoNastaliqUrdu-Regular.ttf"); + stbtt_fontinfo fontinfo = ltk_load_font("Awami_beta3.ttf"); + int width = ltk_text_width("ہمارے بارے میںsdfasdfdsfasf", fontinfo, 200); + int w, h; +// Pixmap pix = ltk_render_text(display, window, gc, "ہمارے بارے میں", fontinfo, 32, c1, c2, "NotoNastaliqUrdu-Regular.ttf", colormap, &w, &h); + Pixmap pix = ltk_render_text(display, window, gc, "ہمارے بارے میں", fontinfo, 32, c1, c2, "Awami_beta3.ttf", colormap, &w, &h); +// Pixmap pix = ltk_render_text(display, window, gc, "Bob", fontinfo, 100, c1, c2, "GentiumPlus-R.ttf", colormap); + XCopyArea(display, pix, window, gc, 0, 0, w, h, 0, 0); + + XEvent event; + KeySym key; + char text[255]; + + while(1) + { + XNextEvent(display, &event); + if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) + { + XCopyArea(display, pix, window, gc, 0, 0, width, 300, 0, 0); + if (text[0] == 'q') + { + XFreeGC(display, gc); + XFreeColormap(display, colormap); + XDestroyWindow(display, window); + XCloseDisplay(display); + exit(0); + } + } + } + + return 0; +} diff --git a/text/text2.c b/text/text2.c @@ -0,0 +1,282 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include "stb_truetype.h" +#include <harfbuzz/hb.h> +#include <harfbuzz/hb-ot.h> + +/* These unicode routines are taken from + * https://github.com/JeffBezanson/cutef8 */ + +/* is c the start of a utf8 sequence? */ +#define isutf(c) (((c)&0xC0)!=0x80) + +static const uint32_t offsetsFromUTF8[6] = { + 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL +}; + +/* next character without NUL character terminator */ +uint32_t u8_nextmemchar(const char *s, size_t *i) +{ + uint32_t ch = 0; + size_t sz = 0; + do { + ch <<= 6; + ch += (unsigned char)s[(*i)++]; + sz++; + } while (!isutf(s[*i])); + ch -= offsetsFromUTF8[sz-1]; + + return ch; +} + + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */ + +stbtt_fontinfo ltk_load_font(const char *path) +{ + FILE *f; + long len; + char *contents; + stbtt_fontinfo info; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + contents = malloc(len + 1); + fread(contents, 1, len, f); + contents[len] = '\0'; + fclose(f); + if (!stbtt_InitFont(&info, contents, 0)) + { + fprintf(stderr, "Failed to load font %s\n", path); + exit(1); + } + return info; +} + +char *ltk_load_file(const char *path, unsigned long *len) +{ + FILE *f; + char *contents; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + *len = ftell(f); + fseek(f, 0, SEEK_SET); + contents = malloc(*len + 1); + fread(contents, 1, *len, f); + contents[*len] = '\0'; + fclose(f); + return contents; +} + +int ltk_text_width(uint8_t *text, stbtt_fontinfo fontinfo, int height) +{ + float scale = stbtt_ScaleForMappingEmToPixels(&fontinfo, height); + size_t i = 0; + int length = strlen(text); + if (length < 1) return 0; + int temp_x; + int kern_advance; + int width = 0; + uint32_t char1; + uint32_t char2; + char1 = u8_nextmemchar(text, &i); + while(i <= length) + { + stbtt_GetCodepointHMetrics(&fontinfo, char1, &temp_x, 0); + width += temp_x * scale; + + char2 = u8_nextmemchar(text, &i); + if (!char2) break; + kern_advance = stbtt_GetCodepointKernAdvance(&fontinfo, char1, char2); + width += kern_advance * scale; + char1 = char2; + } + + return width; +} + +unsigned long ltk_blend_pixel(Display *display, Colormap colormap, XColor fg, XColor bg, double a) +{ + XColor blended; + if (a == 1.0) + return fg.pixel; + else if (a == 0.0) + return bg.pixel; + blended.red = (int)((fg.red - bg.red) * a + bg.red); + blended.green = (int)((fg.green - bg.green) * a + bg.green); + blended.blue = (int)((fg.blue - bg.blue) * a + bg.blue); + XAllocColor(display, colormap, &blended); + + return blended.pixel; +} + +Pixmap ltk_render_text( + Display *display, + Window window, + GC gc, + uint8_t *text, + stbtt_fontinfo fontinfo, + int height, + XColor fg, + XColor bg, + const char *font_, + Colormap colormap +) +{ + hb_blob_t *blob; + hb_face_t *face; + size_t filelen = 0; + char *filedata = ltk_load_file(font_, &filelen); + blob = hb_blob_create(filedata, filelen, HB_MEMORY_MODE_READONLY, NULL, NULL); + face = hb_face_create(blob, 0); + hb_blob_destroy(blob); + hb_font_t *hbfont = hb_font_create(face); + hb_face_destroy(face); + hb_ot_font_set_funcs(hbfont); + + hb_buffer_t *buf; + hb_glyph_info_t *ginf; + hb_glyph_position_t *gpos; + unsigned int len = 0; + + buf = hb_buffer_create(); + hb_buffer_set_flags(buf, HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT); + hb_buffer_add_utf8(buf, text, -1, 0, -1); + hb_buffer_guess_segment_properties(buf); + hb_shape(hbfont, buf, NULL, 0); + + ginf = hb_buffer_get_glyph_infos(buf, &len); + gpos = hb_buffer_get_glyph_positions(buf, &len); + + XWindowAttributes attrs; + XGetWindowAttributes(display, window, &attrs); + int depth = attrs.depth; + + int width = ltk_text_width(text, fontinfo, height) + 200; + float scale = stbtt_ScaleForMappingEmToPixels(&fontinfo, height); + + int ascent, descent, line_gap; + stbtt_GetFontVMetrics(&fontinfo, &ascent, &descent, &line_gap); + ascent *= scale; + descent *= scale; + + Pixmap rendered = XCreatePixmap(display, window, width, height + 200, depth); + unsigned char *bitmap = calloc(width * (height + 200), sizeof(char)); + int length = strlen(text); + if (length < 1) + { + printf("WARNING: ltk_render_text: length of text is less than 1.\n"); + return XCreatePixmap(display, window, 0, 0, depth); + } + int ax, x = 0, y = 0, x1, y1, x2, y2, byte_offset, kern_advance; + double x_abs = 0, y_abs = 0; + unsigned char *b; + int w, h, xoff, yoff; + for (int i = 0; i < len; i++) + { + hb_glyph_info_t *gi = &ginf[i]; + hb_glyph_position_t *gp = &gpos[i]; + stbtt_GetGlyphBitmapBox(&fontinfo, gi->codepoint, scale, scale, &x1, &y1, &x2, &y2); + x = (int)(x_abs + (gp->x_offset * scale)); + y = (int)((y_abs + 20) + (gp->y_offset * scale)); + printf("%d\n", (int)(gp->y_offset * scale)); + byte_offset = x + (y * width); + b = stbtt_GetGlyphBitmap(&fontinfo, scale, scale, gi->codepoint, &w, &h, &xoff, &yoff); + + for (int i = 0; i < h; i++) + { + for (int j = 0; j < w; j++) + { + bitmap[(y + i) * width + (x + j)] = bitmap[(y + i) * width + (x + j)] + b[i * w + j]; + if (bitmap[(y + i) * width + (x + j)] > 255) bitmap[(y + i) * width + (x + j)] = 255; + } + } + free(b); + + x_abs += (gp->x_advance * scale); + y_abs -= (gp->y_advance * scale); + } + + XSetForeground(display, gc, bg.pixel); + for (int i = 0; i < height + 200; i++) + { + for (int j = 0; j < width; j++) + { + /* Yay! Magic! */ + XSetForeground(display, gc, ltk_blend_pixel(display, colormap, fg, bg, (bitmap[i * width + j] / 255.0))); + XDrawPoint(display, rendered, gc, j, i); + } + } + XSetForeground(display, gc, bg.pixel); + + /* TODO: separate this into a separate function so that one function only creates + * the bitmap and other functions to turn that into a pixmap with solid color + * background, image background, etc. + */ + return rendered; +} + +int main(int argc, char *argv[]) +{ + Display *display; + int screen; + Window window; + GC gc; + + unsigned long black, white; + Colormap colormap; + display = XOpenDisplay((char *)0); + screen = DefaultScreen(display); + colormap = DefaultColormap(display, screen); + black = BlackPixel(display, screen); + white = WhitePixel(display, screen); + window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 1366, 512, 0, black, white); + XSetStandardProperties(display, window, "Random Window", NULL, None, NULL, 0, NULL); + XSelectInput(display, window, ExposureMask|ButtonPressMask|KeyPressMask); + gc = XCreateGC(display, window, 0, 0); + XSetBackground(display, gc, black); + XSetForeground(display, gc, black); + XClearWindow(display, window); + XMapRaised(display, window); + XColor c1, c2; + XParseColor(display, colormap, "#FFFFFF", &c1); + XParseColor(display, colormap, "#FF0000", &c2); + XAllocColor(display, colormap, &c1); + XAllocColor(display, colormap, &c2); + +// stbtt_fontinfo fontinfo = ltk_load_font("GentiumPlus-R.ttf"); + stbtt_fontinfo fontinfo = ltk_load_font("NotoNastaliqUrdu-Regular.ttf"); + int width = ltk_text_width("ہمارے بارے میںdsfasffsfadsf", fontinfo, 200); + Pixmap pix = ltk_render_text(display, window, gc, "ہمارے بارے میں", fontinfo, 100, c1, c2, "NotoNastaliqUrdu-Regular.ttf", colormap); +// Pixmap pix = ltk_render_text(display, window, gc, "Bob", fontinfo, 200, c1.pixel, c2.pixel, "GentiumPlus-R.ttf"); + XCopyArea(display, pix, window, gc, 0, 0, width, 200, 0, 0); + + XEvent event; + KeySym key; + char text[255]; + + while(1) + { + XNextEvent(display, &event); + if (event.type == KeyPress && XLookupString(&event.xkey, text, 255, &key, 0) == 1) + { + XCopyArea(display, pix, window, gc, 0, 0, width, 300, 0, 0); + if (text[0] == 'q') + { + XFreeGC(display, gc); + XFreeColormap(display, colormap); + XDestroyWindow(display, window); + XCloseDisplay(display); + exit(0); + } + } + } + + return 0; +} diff --git a/text/utf8.c b/text/utf8.c @@ -0,0 +1,730 @@ +/* + Basic UTF-8 manipulation routines + by Jeff Bezanson + placed in the public domain Fall 2005 + + This code is designed to provide the utilities you need to manipulate + UTF-8 as an internal string encoding. These functions do not perform the + error checking normally needed when handling UTF-8 data, so if you happen + to be from the Unicode Consortium you will want to flay me alive. + I do this because error checking can be performed at the boundaries (I/O), + with these routines reserved for higher performance on data known to be + valid. + A UTF-8 validation routine is included. +*/ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdint.h> +#include <wchar.h> +#include <wctype.h> + +#ifdef WIN32 +#include <malloc.h> +#define snprintf _snprintf +#else +#ifndef __FreeBSD__ +#include <alloca.h> +#endif /* __FreeBSD__ */ +#endif +#include <assert.h> + +#include "utf8.h" + +static const uint32_t offsetsFromUTF8[6] = { + 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL +}; + +static const char trailingBytesForUTF8[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 +}; + +/* returns length of next utf-8 sequence */ +size_t u8_seqlen(const char *s) +{ + return trailingBytesForUTF8[(unsigned int)(unsigned char)s[0]] + 1; +} + +/* returns the # of bytes needed to encode a certain character + 0 means the character cannot (or should not) be encoded. */ +size_t u8_charlen(uint32_t ch) +{ + if (ch < 0x80) + return 1; + else if (ch < 0x800) + return 2; + else if (ch < 0x10000) + return 3; + else if (ch < 0x110000) + return 4; + return 0; +} + +size_t u8_codingsize(uint32_t *wcstr, size_t n) +{ + size_t i, c=0; + + for(i=0; i < n; i++) + c += u8_charlen(wcstr[i]); + return c; +} + +/* conversions without error checking + only works for valid UTF-8, i.e. no 5- or 6-byte sequences + srcsz = source size in bytes + sz = dest size in # of wide characters + + returns # characters converted + if sz == srcsz+1 (i.e. 4*srcsz+4 bytes), there will always be enough space. +*/ +size_t u8_toucs(uint32_t *dest, size_t sz, const char *src, size_t srcsz) +{ + uint32_t ch; + const char *src_end = src + srcsz; + size_t nb; + size_t i=0; + + if (sz == 0 || srcsz == 0) + return 0; + + while (i < sz) { + if (!isutf(*src)) { // invalid sequence + dest[i++] = 0xFFFD; + src++; + if (src >= src_end) break; + continue; + } + nb = trailingBytesForUTF8[(unsigned char)*src]; + if (src + nb >= src_end) + break; + ch = 0; + switch (nb) { + /* these fall through deliberately */ + case 5: ch += (unsigned char)*src++; ch <<= 6; + case 4: ch += (unsigned char)*src++; ch <<= 6; + case 3: ch += (unsigned char)*src++; ch <<= 6; + case 2: ch += (unsigned char)*src++; ch <<= 6; + case 1: ch += (unsigned char)*src++; ch <<= 6; + case 0: ch += (unsigned char)*src++; + } + ch -= offsetsFromUTF8[nb]; + dest[i++] = ch; + } + return i; +} + +/* srcsz = number of source characters + sz = size of dest buffer in bytes + + returns # bytes stored in dest + the destination string will never be bigger than the source string. +*/ +size_t u8_toutf8(char *dest, size_t sz, const uint32_t *src, size_t srcsz) +{ + uint32_t ch; + size_t i = 0; + char *dest0 = dest; + char *dest_end = dest + sz; + + while (i < srcsz) { + ch = src[i]; + if (ch < 0x80) { + if (dest >= dest_end) + break; + *dest++ = (char)ch; + } + else if (ch < 0x800) { + if (dest >= dest_end-1) + break; + *dest++ = (ch>>6) | 0xC0; + *dest++ = (ch & 0x3F) | 0x80; + } + else if (ch < 0x10000) { + if (dest >= dest_end-2) + break; + *dest++ = (ch>>12) | 0xE0; + *dest++ = ((ch>>6) & 0x3F) | 0x80; + *dest++ = (ch & 0x3F) | 0x80; + } + else if (ch < 0x110000) { + if (dest >= dest_end-3) + break; + *dest++ = (ch>>18) | 0xF0; + *dest++ = ((ch>>12) & 0x3F) | 0x80; + *dest++ = ((ch>>6) & 0x3F) | 0x80; + *dest++ = (ch & 0x3F) | 0x80; + } + i++; + } + return (dest-dest0); +} + +size_t u8_wc_toutf8(char *dest, uint32_t ch) +{ + if (ch < 0x80) { + dest[0] = (char)ch; + return 1; + } + if (ch < 0x800) { + dest[0] = (ch>>6) | 0xC0; + dest[1] = (ch & 0x3F) | 0x80; + return 2; + } + if (ch < 0x10000) { + dest[0] = (ch>>12) | 0xE0; + dest[1] = ((ch>>6) & 0x3F) | 0x80; + dest[2] = (ch & 0x3F) | 0x80; + return 3; + } + if (ch < 0x110000) { + dest[0] = (ch>>18) | 0xF0; + dest[1] = ((ch>>12) & 0x3F) | 0x80; + dest[2] = ((ch>>6) & 0x3F) | 0x80; + dest[3] = (ch & 0x3F) | 0x80; + return 4; + } + return 0; +} + +/* charnum => byte offset */ +size_t u8_offset(const char *s, size_t charnum) +{ + size_t i=0; + + while (charnum > 0) { + if (s[i++] & 0x80) { + (void)(isutf(s[++i]) || isutf(s[++i]) || ++i); + } + charnum--; + } + return i; +} + +/* byte offset => charnum */ +size_t u8_charnum(const char *s, size_t offset) +{ + size_t charnum = 0, i=0; + + while (i < offset) { + if (s[i++] & 0x80) { + (void)(isutf(s[++i]) || isutf(s[++i]) || ++i); + } + charnum++; + } + return charnum; +} + +/* number of characters in NUL-terminated string */ +size_t u8_strlen(const char *s) +{ + size_t count = 0; + size_t i = 0, lasti; + + while (1) { + lasti = i; + while (s[i] > 0) + i++; + count += (i-lasti); + if (s[i++]==0) break; + (void)(isutf(s[++i]) || isutf(s[++i]) || ++i); + count++; + } + return count; +} + +int wcwidth(wchar_t c); + +size_t u8_strwidth(const char *s) +{ + uint32_t ch; + size_t nb, tot=0; + int w; + signed char sc; + + while ((sc = (signed char)*s) != 0) { + if (sc >= 0) { + s++; + if (sc) tot++; + } + else { + if (!isutf(sc)) { tot++; s++; continue; } + nb = trailingBytesForUTF8[(unsigned char)sc]; + ch = 0; + switch (nb) { + /* these fall through deliberately */ + case 5: ch += (unsigned char)*s++; ch <<= 6; + case 4: ch += (unsigned char)*s++; ch <<= 6; + case 3: ch += (unsigned char)*s++; ch <<= 6; + case 2: ch += (unsigned char)*s++; ch <<= 6; + case 1: ch += (unsigned char)*s++; ch <<= 6; + case 0: ch += (unsigned char)*s++; + } + ch -= offsetsFromUTF8[nb]; + w = wcwidth(ch); // might return -1 + if (w > 0) tot += w; + } + } + return tot; +} + +/* reads the next utf-8 sequence out of a string, updating an index */ +uint32_t u8_nextchar(const char *s, size_t *i) +{ + uint32_t ch = 0; + size_t sz = 0; + + do { + ch <<= 6; + ch += (unsigned char)s[(*i)]; + sz++; + } while (s[*i] && (++(*i)) && !isutf(s[*i])); + ch -= offsetsFromUTF8[sz-1]; + + return ch; +} + +/* next character without NUL character terminator */ +uint32_t u8_nextmemchar(const char *s, size_t *i) +{ + uint32_t ch = 0; + size_t sz = 0; + do { + ch <<= 6; + ch += (unsigned char)s[(*i)++]; + sz++; + } while (!isutf(s[*i])); + ch -= offsetsFromUTF8[sz-1]; + + return ch; +} + +void u8_inc(const char *s, size_t *i) +{ + (void)(isutf(s[++(*i)]) || isutf(s[++(*i)]) || isutf(s[++(*i)]) || ++(*i)); +} + +void u8_dec(const char *s, size_t *i) +{ + (void)(isutf(s[--(*i)]) || isutf(s[--(*i)]) || isutf(s[--(*i)]) || --(*i)); +} + +int octal_digit(char c) +{ + return (c >= '0' && c <= '7'); +} + +int hex_digit(char c) +{ + return ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f')); +} + +char read_escape_control_char(char c) +{ + if (c == 'n') + return '\n'; + else if (c == 't') + return '\t'; + else if (c == 'r') + return '\r'; + else if (c == 'e') + return 033; // '\e' + else if (c == 'b') + return '\b'; + else if (c == 'f') + return '\f'; + else if (c == 'v') + return '\v'; + else if (c == 'a') + return '\a'; + return c; +} + +/* assumes that src points to the character after a backslash + returns number of input characters processed, 0 if error */ +size_t u8_read_escape_sequence(const char *str, size_t ssz, uint32_t *dest) +{ + uint32_t ch; + char digs[10]; + int dno=0, ndig; + size_t i=1; + char c0 = str[0]; + assert(ssz > 0); + + if (octal_digit(c0)) { + i = 0; + do { + digs[dno++] = str[i++]; + } while (i<ssz && octal_digit(str[i]) && dno<3); + digs[dno] = '\0'; + ch = strtol(digs, NULL, 8); + } + else if ((c0=='x' && (ndig=2)) || + (c0=='u' && (ndig=4)) || + (c0=='U' && (ndig=8))) { + while (i<ssz && hex_digit(str[i]) && dno<ndig) { + digs[dno++] = str[i++]; + } + if (dno == 0) return 0; + digs[dno] = '\0'; + ch = strtol(digs, NULL, 16); + } + else { + ch = (uint32_t)read_escape_control_char(c0); + } + *dest = ch; + + return i; +} + +/* convert a string with literal \uxxxx or \Uxxxxxxxx characters to UTF-8 + example: u8_unescape(mybuf, 256, "hello\\u220e") + note the double backslash is needed if called on a C string literal */ +size_t u8_unescape(char *buf, size_t sz, const char *src) +{ + size_t c=0, amt; + uint32_t ch = 0; + char temp[4]; + + while (*src && c < sz) { + if (*src == '\\') { + src++; + amt = u8_read_escape_sequence(src, 1000, &ch); + } + else { + ch = (uint32_t)*src; + amt = 1; + } + src += amt; + amt = u8_wc_toutf8(temp, ch); + if (amt > sz-c) + break; + memcpy(&buf[c], temp, amt); + c += amt; + } + if (c < sz) + buf[c] = '\0'; + return c; +} + +static int buf_put2c(char *buf, const char *src) +{ + buf[0] = src[0]; + buf[1] = src[1]; + buf[2] = '\0'; + return 2; +} + +int u8_escape_wchar(char *buf, size_t sz, uint32_t ch) +{ + assert(sz > 2); + if (ch == L'\n') + return buf_put2c(buf, "\\n"); + else if (ch == L'\t') + return buf_put2c(buf, "\\t"); + else if (ch == L'\r') + return buf_put2c(buf, "\\r"); + else if (ch == 033) // L'\e' + return buf_put2c(buf, "\\e"); + else if (ch == L'\b') + return buf_put2c(buf, "\\b"); + else if (ch == L'\f') + return buf_put2c(buf, "\\f"); + else if (ch == L'\v') + return buf_put2c(buf, "\\v"); + else if (ch == L'\a') + return buf_put2c(buf, "\\a"); + else if (ch == L'\\') + return buf_put2c(buf, "\\\\"); + else if (ch < 32 || ch == 0x7f) + return snprintf(buf, sz, "\\x%.2hhx", (unsigned char)ch); + else if (ch > 0xFFFF) + return snprintf(buf, sz, "\\U%.8x", (uint32_t)ch); + else if (ch >= 0x80) + return snprintf(buf, sz, "\\u%.4hx", (unsigned short)ch); + + buf[0] = (char)ch; + buf[1] = '\0'; + return 1; +} + +size_t u8_escape(char *buf, size_t sz, const char *src, size_t *pi, size_t end, + int escape_quotes, int ascii) +{ + size_t i = *pi, i0; + uint32_t ch; + char *start = buf; + char *blim = start + sz-11; + assert(sz > 11); + + while (i<end && buf<blim) { + // sz-11: leaves room for longest escape sequence + if (escape_quotes && src[i] == '"') { + buf += buf_put2c(buf, "\\\""); + i++; + } + else if (src[i] == '\\') { + buf += buf_put2c(buf, "\\\\"); + i++; + } + else { + i0 = i; + ch = u8_nextmemchar(src, &i); + if (ascii || !iswprint((wint_t)ch)) { + buf += u8_escape_wchar(buf, sz - (buf-start), ch); + } + else { + i = i0; + do { + *buf++ = src[i++]; + } while (!isutf(src[i])); + } + } + } + *buf++ = '\0'; + *pi = i; + return (buf-start); +} + +char *u8_strchr(const char *s, uint32_t ch, size_t *charn) +{ + size_t i = 0, lasti=0; + uint32_t c; + + *charn = 0; + while (s[i]) { + c = u8_nextchar(s, &i); + if (c == ch) { + /* it's const for us, but not necessarily the caller */ + return (char*)&s[lasti]; + } + lasti = i; + (*charn)++; + } + return NULL; +} + +char *u8_memchr(const char *s, uint32_t ch, size_t sz, size_t *charn) +{ + size_t i = 0, lasti=0; + uint32_t c; + int csz; + + *charn = 0; + while (i < sz) { + c = csz = 0; + do { + c <<= 6; + c += (unsigned char)s[i++]; + csz++; + } while (i < sz && !isutf(s[i])); + c -= offsetsFromUTF8[csz-1]; + + if (c == ch) { + return (char*)&s[lasti]; + } + lasti = i; + (*charn)++; + } + return NULL; +} + +char *u8_memrchr(const char *s, uint32_t ch, size_t sz) +{ + size_t i = sz-1, tempi=0; + uint32_t c; + + if (sz == 0) return NULL; + + while (i && !isutf(s[i])) i--; + + while (1) { + tempi = i; + c = u8_nextmemchar(s, &tempi); + if (c == ch) { + return (char*)&s[i]; + } + if (i == 0) + break; + tempi = i; + u8_dec(s, &i); + if (i > tempi) + break; + } + return NULL; +} + +int u8_is_locale_utf8(const char *locale) +{ + /* this code based on libutf8 */ + const char* cp = locale; + + if (locale == NULL) return 0; + + for (; *cp != '\0' && *cp != '@' && *cp != '+' && *cp != ','; cp++) { + if (*cp == '.') { + const char* encoding = ++cp; + for (; *cp != '\0' && *cp != '@' && *cp != '+' && *cp != ','; cp++) + ; + if ((cp-encoding == 5 && !strncmp(encoding, "UTF-8", 5)) + || (cp-encoding == 4 && !strncmp(encoding, "utf8", 4))) + return 1; /* it's UTF-8 */ + break; + } + } + return 0; +} + +size_t u8_vprintf(const char *fmt, va_list ap) +{ + int cnt, sz=0, nc, needfree=0; + char *buf; + uint32_t *wcs; + + sz = 512; + buf = (char*)alloca(sz); + cnt = vsnprintf(buf, sz, fmt, ap); + if (cnt < 0) + return 0; + if (cnt >= sz) { + buf = (char*)malloc(cnt + 1); + needfree = 1; + vsnprintf(buf, cnt+1, fmt, ap); + } + wcs = (uint32_t*)alloca((cnt+1) * sizeof(uint32_t)); + nc = u8_toucs(wcs, (size_t)cnt+1, buf, cnt); + wcs[nc] = 0; + printf("%ls", (wchar_t*)wcs); + if (needfree) free(buf); + return nc; +} + +size_t u8_printf(const char *fmt, ...) +{ + size_t cnt; + va_list args; + + va_start(args, fmt); + + cnt = u8_vprintf(fmt, args); + + va_end(args); + return cnt; +} + +/* based on the valid_utf8 routine from the PCRE library by Philip Hazel + + length is in bytes, since without knowing whether the string is valid + it's hard to know how many characters there are! */ +int u8_isvalid(const char *str, size_t length) +{ + const unsigned char *p, *pend = (unsigned char*)str + length; + unsigned char c; + int ret = 1; /* ASCII */ + size_t ab; + + for (p = (unsigned char*)str; p < pend; p++) { + c = *p; + if (c < 128) + continue; + ret = 2; /* non-ASCII UTF-8 */ + if ((c & 0xc0) != 0xc0) + return 0; + ab = trailingBytesForUTF8[c]; + if (length < ab) + return 0; + length -= ab; + + p++; + /* Check top bits in the second byte */ + if ((*p & 0xc0) != 0x80) + return 0; + + /* Check for overlong sequences for each different length */ + switch (ab) { + /* Check for xx00 000x */ + case 1: + if ((c & 0x3e) == 0) return 0; + continue; /* We know there aren't any more bytes to check */ + + /* Check for 1110 0000, xx0x xxxx */ + case 2: + if (c == 0xe0 && (*p & 0x20) == 0) return 0; + break; + + /* Check for 1111 0000, xx00 xxxx */ + case 3: + if (c == 0xf0 && (*p & 0x30) == 0) return 0; + break; + + /* Check for 1111 1000, xx00 0xxx */ + case 4: + if (c == 0xf8 && (*p & 0x38) == 0) return 0; + break; + + /* Check for leading 0xfe or 0xff, + and then for 1111 1100, xx00 00xx */ + case 5: + if (c == 0xfe || c == 0xff || + (c == 0xfc && (*p & 0x3c) == 0)) return 0; + break; + } + + /* Check for valid bytes after the 2nd, if any; all must start 10 */ + while (--ab > 0) { + if ((*(++p) & 0xc0) != 0x80) return 0; + } + } + + return ret; +} + +int u8_reverse(char *dest, char * src, size_t len) +{ + size_t si=0, di=len; + unsigned char c; + + dest[di] = '\0'; + while (si < len) { + c = (unsigned char)src[si]; + if ((~c) & 0x80) { + di--; + dest[di] = c; + si++; + } + else { + switch (c>>4) { + case 0xC: + case 0xD: + di -= 2; + *((int16_t*)&dest[di]) = *((int16_t*)&src[si]); + si += 2; + break; + case 0xE: + di -= 3; + dest[di] = src[si]; + *((int16_t*)&dest[di+1]) = *((int16_t*)&src[si+1]); + si += 3; + break; + case 0xF: + di -= 4; + *((int32_t*)&dest[di]) = *((int32_t*)&src[si]); + si += 4; + break; + default: + return 1; + } + } + } + return 0; +} diff --git a/text/utf8.h b/text/utf8.h @@ -0,0 +1,115 @@ +#ifndef UTF8_H +#define UTF8_H + +extern int locale_is_utf8; + +/* is c the start of a utf8 sequence? */ +#define isutf(c) (((c)&0xC0)!=0x80) + +#define UEOF ((uint32_t)-1) + +/* convert UTF-8 data to wide character */ +size_t u8_toucs(uint32_t *dest, size_t sz, const char *src, size_t srcsz); + +/* the opposite conversion */ +size_t u8_toutf8(char *dest, size_t sz, const uint32_t *src, size_t srcsz); + +/* single character to UTF-8, returns # bytes written */ +size_t u8_wc_toutf8(char *dest, uint32_t ch); + +/* character number to byte offset */ +size_t u8_offset(const char *str, size_t charnum); + +/* byte offset to character number */ +size_t u8_charnum(const char *s, size_t offset); + +/* return next character, updating an index variable */ +uint32_t u8_nextchar(const char *s, size_t *i); + +/* next character without NUL character terminator */ +uint32_t u8_nextmemchar(const char *s, size_t *i); + +/* move to next character */ +void u8_inc(const char *s, size_t *i); + +/* move to previous character */ +void u8_dec(const char *s, size_t *i); + +/* returns length of next utf-8 sequence */ +size_t u8_seqlen(const char *s); + +/* returns the # of bytes needed to encode a certain character */ +size_t u8_charlen(uint32_t ch); + +/* computes the # of bytes needed to encode a WC string as UTF-8 */ +size_t u8_codingsize(uint32_t *wcstr, size_t n); + +char read_escape_control_char(char c); + +/* assuming src points to the character after a backslash, read an + escape sequence, storing the result in dest and returning the number of + input characters processed */ +size_t u8_read_escape_sequence(const char *src, size_t ssz, uint32_t *dest); + +/* given a wide character, convert it to an ASCII escape sequence stored in + buf, where buf is "sz" bytes. returns the number of characters output. + sz must be at least 3. */ +int u8_escape_wchar(char *buf, size_t sz, uint32_t ch); + +/* convert a string "src" containing escape sequences to UTF-8 */ +size_t u8_unescape(char *buf, size_t sz, const char *src); + +/* convert UTF-8 "src" to escape sequences. + + sz is buf size in bytes. must be at least 12. + + if escape_quotes is nonzero, quote characters will be escaped. + + if ascii is nonzero, the output is 7-bit ASCII, no UTF-8 survives. + + starts at src[*pi], updates *pi to point to the first unprocessed + byte of the input. + + end is one more than the last allowable value of *pi. + + returns number of bytes placed in buf, including a NUL terminator. +*/ +size_t u8_escape(char *buf, size_t sz, const char *src, size_t *pi, size_t end, + int escape_quotes, int ascii); + +/* utility predicates used by the above */ +int octal_digit(char c); +int hex_digit(char c); + +/* return a pointer to the first occurrence of ch in s, or NULL if not + found. character index of found character returned in *charn. */ +char *u8_strchr(const char *s, uint32_t ch, size_t *charn); + +/* same as the above, but searches a buffer of a given size instead of + a NUL-terminated string. */ +char *u8_memchr(const char *s, uint32_t ch, size_t sz, size_t *charn); + +char *u8_memrchr(const char *s, uint32_t ch, size_t sz); + +/* count the number of characters in a UTF-8 string */ +size_t u8_strlen(const char *s); + +/* number of columns occupied by a string */ +size_t u8_strwidth(const char *s); + +int u8_is_locale_utf8(const char *locale); + +/* printf where the format string and arguments may be in UTF-8. + you can avoid this function and just use ordinary printf() if the current + locale is UTF-8. */ +size_t u8_vprintf(const char *fmt, va_list ap); +size_t u8_printf(const char *fmt, ...); + +/* determine whether a sequence of bytes is valid UTF-8. length is in bytes */ +int u8_isvalid(const char *str, size_t length); + +/* reverse a UTF-8 string. len is length in bytes. dest and src must both + be allocated to at least len+1 bytes. returns 1 for error, 0 otherwise */ +int u8_reverse(char *dest, char *src, size_t len); + +#endif diff --git a/text/utf8_new.c b/text/utf8_new.c @@ -0,0 +1,22 @@ +/* is c the start of a utf8 sequence? */ +#define isutf(c) (((c)&0xC0)!=0x80) + +static const uint32_t offsetsFromUTF8[6] = { + 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL +}; + +/* next character without NUL character terminator */ +uint32_t u8_nextmemchar(const char *s, size_t *i) +{ + uint32_t ch = 0; + size_t sz = 0; + do { + ch <<= 6; + ch += (unsigned char)s[(*i)++]; + sz++; + } while (!isutf(s[*i])); + ch -= offsetsFromUTF8[sz-1]; + + return ch; +} diff --git a/theme.c b/theme.c @@ -0,0 +1,81 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "theme.h" + +LtkTheme *ltk_load_theme(const char *path) +{ + char *file_contents = ltk_read_file(path); + + cJSON *json = cJSON_Parse(file_contents); + if (!json) + { + printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + cJSON *button_json = cJSON_GetObjectItem(json, "button"); + if (!button_json) + { + printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + cJSON *window_json = cJSON_GetObjectItem(json, "window"); + if (!window_json) + { + printf("Theme error before: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + + LtkTheme *theme = malloc(sizeof(LtkTheme)); + theme->button = ltk_parse_button_theme(button_json); + theme->window = ltk_parse_window_theme(window_json); + + free(file_contents); + cJSON_Delete(json); + + return theme; +} + +void ltk_destroy_theme(LtkTheme *theme) +{ + free(theme->button); + free(theme->window); + free(theme); +} + +char *ltk_read_file(const char *path) +{ + FILE *f; + long len; + char *file_contents; + f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + file_contents = malloc(len + 1); + fread(file_contents, 1, len, f); + file_contents[len] = '\0'; + fclose(f); + + return file_contents; +}+ \ No newline at end of file diff --git a/theme.h b/theme.h @@ -0,0 +1,22 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2016, 2017 Lumidify Productions <lumidify@openmailbox.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */+ \ No newline at end of file diff --git a/themes/default.ini b/themes/default.ini @@ -1,21 +1,24 @@ [window] border_width = 0 -background = #000000 -foreground = #FFFFFF +bg = #000000 +fg = #FFFFFF +font = Lumidify_Casual.ttf [button] border_width = 2 -font_size = 20 -border_color = #339999 -fill_color = #113355 -pad_left = 5 -pad_right = 5 -pad_top = 5 -pad_bottom = 5 -fill_color_hover = 738194 -border_color_pressed = #FFFFFF -fill_color_pressed = 113355 -border_color_active = #FFFFFF -fill_color_active = #113355 -border_color_disabled = #FFFFFF -fill_color_disabled = #292929 +font_size = 30 +text_color = #FFFFFF +padl = 5 +padr = 5 +padt = 5 +padb = 5 +border = #339999 +fill = #113355 +border_hover = #FFFFFF +fill_hover = #738194 +border_pressed = #FFFFFF +fill_pressed = #113355 +border_active = #FFFFFF +fill_active = #113355 +border_disabled = #FFFFFF +fill_disabled = #292929