From e5e5761da4860d8284ca757ee828ff8ef0df720a Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 08:29:34 +0000 Subject: [PATCH 01/21] add raqm to thirdparty directory --- src/thirdparty/raqm/raqm-version.h | 44 + src/thirdparty/raqm/raqm.c | 2069 ++++++++++++++++++++++++++++ src/thirdparty/raqm/raqm.h | 185 +++ 3 files changed, 2298 insertions(+) create mode 100644 src/thirdparty/raqm/raqm-version.h create mode 100644 src/thirdparty/raqm/raqm.c create mode 100644 src/thirdparty/raqm/raqm.h diff --git a/src/thirdparty/raqm/raqm-version.h b/src/thirdparty/raqm/raqm-version.h new file mode 100644 index 00000000000..4fd5c684287 --- /dev/null +++ b/src/thirdparty/raqm/raqm-version.h @@ -0,0 +1,44 @@ +/* + * Copyright © 2011 Google, Inc. + * + * This is part of HarfBuzz, a text shaping library. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, provided that the + * above copyright notice and the following two paragraphs appear in + * all copies of this software. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES + * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN + * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * Google Author(s): Behdad Esfahbod + */ + +#ifndef _RAQM_H_IN_ +#error "Include instead." +#endif + +#ifndef _RAQM_VERSION_H_ +#define _RAQM_VERSION_H_ + +#define RAQM_VERSION_MAJOR 0 +#define RAQM_VERSION_MINOR 7 +#define RAQM_VERSION_MICRO 1 + +#define RAQM_VERSION_STRING "0.7.1" + +#define RAQM_VERSION_ATLEAST(major,minor,micro) \ + ((major)*10000+(minor)*100+(micro) <= \ + RAQM_VERSION_MAJOR*10000+RAQM_VERSION_MINOR*100+RAQM_VERSION_MICRO) + +#endif /* _RAQM_VERSION_H_ */ diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c new file mode 100644 index 00000000000..27e59b5fc20 --- /dev/null +++ b/src/thirdparty/raqm/raqm.c @@ -0,0 +1,2069 @@ +/* + * Copyright © 2015 Information Technology Authority (ITA) + * Copyright © 2016 Khaled Hosny + * + * 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#undef HAVE_CONFIG_H // Workaround for Fribidi 1.0.5 and earlier +#endif + +#include +#include + +#include +#include +#include + +#include "raqm.h" + +#if FRIBIDI_MAJOR_VERSION >= 1 +#define USE_FRIBIDI_EX_API +#endif + +/** + * SECTION:raqm + * @title: Raqm + * @short_description: A library for complex text layout + * @include: raqm.h + * + * Raqm is a light weight text layout library with strong emphasis on + * supporting languages and writing systems that require complex text layout. + * + * The main object in Raqm API is #raqm_t, it stores all the states of the + * input text, its properties, and the output of the layout process. + * + * To start, you create a #raqm_t object, add text and font(s) to it, run the + * layout process, and finally query about the output. For example: + * + * |[ + * #include "raqm.h" + * + * int + * main (int argc, char *argv[]) + * { + * const char *fontfile; + * const char *text; + * const char *direction; + * const char *language; + * int ret = 1; + * + * FT_Library library = NULL; + * FT_Face face = NULL; + * + * if (argc < 5) + * { + * printf ("Usage: %s FONT_FILE TEXT DIRECTION LANG\n", argv[0]); + * return 1; + * } + * + * fontfile = argv[1]; + * text = argv[2]; + * direction = argv[3]; + * language = argv[4]; + * + * if (FT_Init_FreeType (&library) == 0) + * { + * if (FT_New_Face (library, fontfile, 0, &face) == 0) + * { + * if (FT_Set_Char_Size (face, face->units_per_EM, 0, 0, 0) == 0) + * { + * raqm_t *rq = raqm_create (); + * if (rq != NULL) + * { + * raqm_direction_t dir = RAQM_DIRECTION_DEFAULT; + * + * if (strcmp (direction, "r") == 0) + * dir = RAQM_DIRECTION_RTL; + * else if (strcmp (direction, "l") == 0) + * dir = RAQM_DIRECTION_LTR; + * + * if (raqm_set_text_utf8 (rq, text, strlen (text)) && + * raqm_set_freetype_face (rq, face) && + * raqm_set_par_direction (rq, dir) && + * raqm_set_language (rq, language, 0, strlen (text)) && + * raqm_layout (rq)) + * { + * size_t count, i; + * raqm_glyph_t *glyphs = raqm_get_glyphs (rq, &count); + * + * ret = !(glyphs != NULL || count == 0); + * + * printf("glyph count: %zu\n", count); + * for (i = 0; i < count; i++) + * { + * printf ("gid#%d off: (%d, %d) adv: (%d, %d) idx: %d\n", + * glyphs[i].index, + * glyphs[i].x_offset, + * glyphs[i].y_offset, + * glyphs[i].x_advance, + * glyphs[i].y_advance, + * glyphs[i].cluster); + * } + * } + * + * raqm_destroy (rq); + * } + * } + * + * FT_Done_Face (face); + * } + * + * FT_Done_FreeType (library); + * } + * + * return ret; + * } + * ]| + * To compile this example: + * |[ + * cc -o test test.c `pkg-config --libs --cflags raqm` + * ]| + */ + +/* For enabling debug mode */ +/*#define RAQM_DEBUG 1*/ +#ifdef RAQM_DEBUG +#define RAQM_DBG(...) fprintf (stderr, __VA_ARGS__) +#else +#define RAQM_DBG(...) +#endif + +#ifdef RAQM_TESTING +# define RAQM_TEST(...) printf (__VA_ARGS__) +# define SCRIPT_TO_STRING(script) \ + char buff[5]; \ + hb_tag_to_string (hb_script_to_iso15924_tag (script), buff); \ + buff[4] = '\0'; +#else +# define RAQM_TEST(...) +#endif + +typedef enum { + RAQM_FLAG_NONE = 0, + RAQM_FLAG_UTF8 = 1 << 0 +} _raqm_flags_t; + +typedef struct { + FT_Face ftface; + hb_language_t lang; + hb_script_t script; +} _raqm_text_info; + +typedef struct _raqm_run raqm_run_t; + +struct _raqm { + int ref_count; + + uint32_t *text; + char *text_utf8; + size_t text_len; + + _raqm_text_info *text_info; + + raqm_direction_t base_dir; + raqm_direction_t resolved_dir; + + hb_feature_t *features; + size_t features_len; + + raqm_run_t *runs; + raqm_glyph_t *glyphs; + + _raqm_flags_t flags; + + int ft_loadflags; + int invisible_glyph; +}; + +struct _raqm_run { + int pos; + int len; + + hb_direction_t direction; + hb_script_t script; + hb_font_t *font; + hb_buffer_t *buffer; + + raqm_run_t *next; +}; + +static uint32_t +_raqm_u8_to_u32_index (raqm_t *rq, + uint32_t index); + +static bool +_raqm_init_text_info (raqm_t *rq) +{ + hb_language_t default_lang; + + if (rq->text_info) + return true; + + rq->text_info = malloc (sizeof (_raqm_text_info) * rq->text_len); + if (!rq->text_info) + return false; + + default_lang = hb_language_get_default (); + for (size_t i = 0; i < rq->text_len; i++) + { + rq->text_info[i].ftface = NULL; + rq->text_info[i].lang = default_lang; + rq->text_info[i].script = HB_SCRIPT_INVALID; + } + + return true; +} + +static void +_raqm_free_text_info (raqm_t *rq) +{ + if (!rq->text_info) + return; + + for (size_t i = 0; i < rq->text_len; i++) + { + if (rq->text_info[i].ftface) + FT_Done_Face (rq->text_info[i].ftface); + } + + free (rq->text_info); + rq->text_info = NULL; +} + +static bool +_raqm_compare_text_info (_raqm_text_info a, + _raqm_text_info b) +{ + if (a.ftface != b.ftface) + return false; + + if (a.lang != b.lang) + return false; + + if (a.script != b.script) + return false; + + return true; +} + +/** + * raqm_create: + * + * Creates a new #raqm_t with all its internal states initialized to their + * defaults. + * + * Return value: + * A newly allocated #raqm_t with a reference count of 1. The initial reference + * count should be released with raqm_destroy() when you are done using the + * #raqm_t. Returns %NULL in case of error. + * + * Since: 0.1 + */ +raqm_t * +raqm_create (void) +{ + raqm_t *rq; + + rq = malloc (sizeof (raqm_t)); + if (!rq) + return NULL; + + rq->ref_count = 1; + + rq->text = NULL; + rq->text_utf8 = NULL; + rq->text_len = 0; + + rq->text_info = NULL; + + rq->base_dir = RAQM_DIRECTION_DEFAULT; + rq->resolved_dir = RAQM_DIRECTION_DEFAULT; + + rq->features = NULL; + rq->features_len = 0; + + rq->runs = NULL; + rq->glyphs = NULL; + + rq->flags = RAQM_FLAG_NONE; + + rq->ft_loadflags = -1; + rq->invisible_glyph = 0; + + return rq; +} + +/** + * raqm_reference: + * @rq: a #raqm_t. + * + * Increases the reference count on @rq by one. This prevents @rq from being + * destroyed until a matching call to raqm_destroy() is made. + * + * Return value: + * The referenced #raqm_t. + * + * Since: 0.1 + */ +raqm_t * +raqm_reference (raqm_t *rq) +{ + if (rq) + rq->ref_count++; + + return rq; +} + +static void +_raqm_free_runs (raqm_t *rq) +{ + raqm_run_t *runs = rq->runs; + while (runs) + { + raqm_run_t *run = runs; + runs = runs->next; + + hb_buffer_destroy (run->buffer); + hb_font_destroy (run->font); + free (run); + } +} + +/** + * raqm_destroy: + * @rq: a #raqm_t. + * + * Decreases the reference count on @rq by one. If the result is zero, then @rq + * and all associated resources are freed. + * See cairo_reference(). + * + * Since: 0.1 + */ +void +raqm_destroy (raqm_t *rq) +{ + if (!rq || --rq->ref_count != 0) + return; + + free (rq->text); + free (rq->text_utf8); + _raqm_free_text_info (rq); + _raqm_free_runs (rq); + free (rq->glyphs); + free (rq); +} + +/** + * raqm_set_text: + * @rq: a #raqm_t. + * @text: a UTF-32 encoded text string. + * @len: the length of @text. + * + * Adds @text to @rq to be used for layout. It must be a valid UTF-32 text, any + * invalid character will be replaced with U+FFFD. The text should typically + * represent a full paragraph, since doing the layout of chunks of text + * separately can give improper output. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_text (raqm_t *rq, + const uint32_t *text, + size_t len) +{ + if (!rq || !text) + return false; + + rq->text_len = len; + + /* Empty string, don’t fail but do nothing */ + if (!len) + return true; + + free (rq->text); + + rq->text = malloc (sizeof (uint32_t) * rq->text_len); + if (!rq->text) + return false; + + _raqm_free_text_info (rq); + if (!_raqm_init_text_info (rq)) + return false; + + memcpy (rq->text, text, sizeof (uint32_t) * rq->text_len); + + return true; +} + +/** + * raqm_set_text_utf8: + * @rq: a #raqm_t. + * @text: a UTF-8 encoded text string. + * @len: the length of @text in UTF-8 bytes. + * + * Same as raqm_set_text(), but for text encoded in UTF-8 encoding. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_text_utf8 (raqm_t *rq, + const char *text, + size_t len) +{ + uint32_t *unicode; + size_t ulen; + bool ok; + + if (!rq || !text) + return false; + + /* Empty string, don’t fail but do nothing */ + if (!len) + { + rq->text_len = len; + return true; + } + + RAQM_TEST ("Text is: %s\n", text); + + rq->flags |= RAQM_FLAG_UTF8; + + rq->text_utf8 = malloc (sizeof (char) * len); + if (!rq->text_utf8) + return false; + + unicode = malloc (sizeof (uint32_t) * len); + if (!unicode) + return false; + + memcpy (rq->text_utf8, text, sizeof (char) * len); + + ulen = fribidi_charset_to_unicode (FRIBIDI_CHAR_SET_UTF8, + text, len, unicode); + + ok = raqm_set_text (rq, unicode, ulen); + + free (unicode); + return ok; +} + +/** + * raqm_set_par_direction: + * @rq: a #raqm_t. + * @dir: the direction of the paragraph. + * + * Sets the paragraph direction, also known as block direction in CSS. For + * horizontal text, this controls the overall direction in the Unicode + * Bidirectional Algorithm, so when the text is mainly right-to-left (with or + * without some left-to-right) text, then the base direction should be set to + * #RAQM_DIRECTION_RTL and vice versa. + * + * The default is #RAQM_DIRECTION_DEFAULT, which determines the paragraph + * direction based on the first character with strong bidi type (see [rule + * P2](http://unicode.org/reports/tr9/#P2) in Unicode Bidirectional Algorithm), + * which can be good enough for many cases but has problems when a mainly + * right-to-left paragraph starts with a left-to-right character and vice versa + * as the detected paragraph direction will be the wrong one, or when text does + * not contain any characters with string bidi types (e.g. only punctuation or + * numbers) as this will default to left-to-right paragraph direction. + * + * For vertical, top-to-bottom text, #RAQM_DIRECTION_TTB should be used. Raqm, + * however, provides limited vertical text support and does not handle rotated + * horizontal text in vertical text, instead everything is treated as vertical + * text. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_par_direction (raqm_t *rq, + raqm_direction_t dir) +{ + if (!rq) + return false; + + rq->base_dir = dir; + + return true; +} + +/** + * raqm_set_language: + * @rq: a #raqm_t. + * @lang: a BCP47 language code. + * @start: index of first character that should use @face. + * @len: number of characters using @face. + * + * Sets a [BCP47 language + * code](https://www.w3.org/International/articles/language-tags/) to be used + * for @len-number of characters staring at @start. The @start and @len are + * input string array indices (i.e. counting bytes in UTF-8 and scaler values + * in UTF-32). + * + * This method can be used repeatedly to set different languages for different + * parts of the text. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Stability: + * Unstable + * + * Since: 0.2 + */ +bool +raqm_set_language (raqm_t *rq, + const char *lang, + size_t start, + size_t len) +{ + hb_language_t language; + size_t end = start + len; + + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (rq->flags & RAQM_FLAG_UTF8) + { + start = _raqm_u8_to_u32_index (rq, start); + end = _raqm_u8_to_u32_index (rq, end); + } + + if (start >= rq->text_len || end > rq->text_len) + return false; + + if (!rq->text_info) + return false; + + language = hb_language_from_string (lang, -1); + for (size_t i = start; i < end; i++) + { + rq->text_info[i].lang = language; + } + + return true; +} + +/** + * raqm_add_font_feature: + * @rq: a #raqm_t. + * @feature: (transfer none): a font feature string. + * @len: length of @feature, -1 for %NULL-terminated. + * + * Adds a font feature to be used by the #raqm_t during text layout. This is + * usually used to turn on optional font features that are not enabled by + * default, for example `dlig` or `ss01`, but can be also used to turn off + * default font features. + * + * @feature is string representing a single font feature, in the syntax + * understood by hb_feature_from_string(). + * + * This function can be called repeatedly, new features will be appended to the + * end of the features list and can potentially override previous features. + * + * Return value: + * %true if parsing @feature succeeded, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_add_font_feature (raqm_t *rq, + const char *feature, + int len) +{ + hb_bool_t ok; + hb_feature_t fea; + + if (!rq) + return false; + + ok = hb_feature_from_string (feature, len, &fea); + if (ok) + { + rq->features_len++; + rq->features = realloc (rq->features, + sizeof (hb_feature_t) * (rq->features_len)); + if (!rq->features) + return false; + + rq->features[rq->features_len - 1] = fea; + } + + return ok; +} + +static hb_font_t * +_raqm_create_hb_font (raqm_t *rq, + FT_Face face) +{ + hb_font_t *font = hb_ft_font_create_referenced (face); + + if (rq->ft_loadflags >= 0) + hb_ft_font_set_load_flags (font, rq->ft_loadflags); + + return font; +} + +static bool +_raqm_set_freetype_face (raqm_t *rq, + FT_Face face, + size_t start, + size_t end) +{ + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (start >= rq->text_len || end > rq->text_len) + return false; + + if (!rq->text_info) + return false; + + for (size_t i = start; i < end; i++) + { + if (rq->text_info[i].ftface) + FT_Done_Face (rq->text_info[i].ftface); + rq->text_info[i].ftface = face; + FT_Reference_Face (face); + } + + return true; +} + +/** + * raqm_set_freetype_face: + * @rq: a #raqm_t. + * @face: an #FT_Face. + * + * Sets an #FT_Face to be used for all characters in @rq. + * + * See also raqm_set_freetype_face_range(). + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_freetype_face (raqm_t *rq, + FT_Face face) +{ + return _raqm_set_freetype_face (rq, face, 0, rq->text_len); +} + +/** + * raqm_set_freetype_face_range: + * @rq: a #raqm_t. + * @face: an #FT_Face. + * @start: index of first character that should use @face. + * @len: number of characters using @face. + * + * Sets an #FT_Face to be used for @len-number of characters staring at @start. + * The @start and @len are input string array indices (i.e. counting bytes in + * UTF-8 and scaler values in UTF-32). + * + * This method can be used repeatedly to set different faces for different + * parts of the text. It is the responsibility of the client to make sure that + * face ranges cover the whole text. + * + * See also raqm_set_freetype_face(). + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_freetype_face_range (raqm_t *rq, + FT_Face face, + size_t start, + size_t len) +{ + size_t end = start + len; + + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (rq->flags & RAQM_FLAG_UTF8) + { + start = _raqm_u8_to_u32_index (rq, start); + end = _raqm_u8_to_u32_index (rq, end); + } + + return _raqm_set_freetype_face (rq, face, start, end); +} + +/** + * raqm_set_freetype_load_flags: + * @rq: a #raqm_t. + * @flags: FreeType load flags. + * + * Sets the load flags passed to FreeType when loading glyphs, should be the + * same flags used by the client when rendering FreeType glyphs. + * + * This requires version of HarfBuzz that has hb_ft_font_set_load_flags(), for + * older version the flags will be ignored. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.3 + */ +bool +raqm_set_freetype_load_flags (raqm_t *rq, + int flags) +{ + if (!rq) + return false; + + rq->ft_loadflags = flags; + + return true; +} + +/** + * raqm_set_invisible_glyph: + * @rq: a #raqm_t. + * @gid: glyph id to use for invisible glyphs. + * + * Sets the glyph id to be used for invisible glyhphs. + * + * If @gid is negative, invisible glyphs will be suppressed from the output. + * This requires HarfBuzz 1.8.0 or later. If raqm is used with an earlier + * HarfBuzz version, the return value will be %false and the shaping behavior + * does not change. + * + * If @gid is zero, invisible glyphs will be rendered as space. + * This works on all versions of HarfBuzz. + * + * If @gid is a positive number, it will be used for invisible glyphs. + * This requires a version of HarfBuzz that has + * hb_buffer_set_invisible_glyph(). For older versions, the return value + * will be %false and the shaping behavior does not change. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.6 + */ +bool +raqm_set_invisible_glyph (raqm_t *rq, + int gid) +{ + if (!rq) + return false; + +#ifndef HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH + if (gid > 0) + return false; +#endif + +#if !defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) || \ + !HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES + if (gid < 0) + return false; +#endif + + rq->invisible_glyph = gid; + return true; +} + +static bool +_raqm_itemize (raqm_t *rq); + +static bool +_raqm_shape (raqm_t *rq); + +/** + * raqm_layout: + * @rq: a #raqm_t. + * + * Run the text layout process on @rq. This is the main Raqm function where the + * Unicode Bidirectional Text algorithm will be applied to the text in @rq, + * text shaping, and any other part of the layout process. + * + * Return value: + * %true if the layout process was successful, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_layout (raqm_t *rq) +{ + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (!rq->text_info) + return false; + + for (size_t i = 0; i < rq->text_len; i++) + { + if (!rq->text_info[i].ftface) + return false; + } + + if (!_raqm_itemize (rq)) + return false; + + if (!_raqm_shape (rq)) + return false; + + return true; +} + +static uint32_t +_raqm_u32_to_u8_index (raqm_t *rq, + uint32_t index); + +/** + * raqm_get_glyphs: + * @rq: a #raqm_t. + * @length: (out): output array length. + * + * Gets the final result of Raqm layout process, an array of #raqm_glyph_t + * containing the glyph indices in the font, their positions and other possible + * information. + * + * Return value: (transfer none): + * An array of #raqm_glyph_t, or %NULL in case of error. This is owned by @rq + * and must not be freed. + * + * Since: 0.1 + */ +raqm_glyph_t * +raqm_get_glyphs (raqm_t *rq, + size_t *length) +{ + size_t count = 0; + + if (!rq || !rq->runs || !length) + { + if (length) + *length = 0; + return NULL; + } + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + count += hb_buffer_get_length (run->buffer); + + *length = count; + + if (rq->glyphs) + free (rq->glyphs); + + rq->glyphs = malloc (sizeof (raqm_glyph_t) * count); + if (!rq->glyphs) + { + *length = 0; + return NULL; + } + + RAQM_TEST ("Glyph information:\n"); + + count = 0; + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + rq->glyphs[count + i].index = info[i].codepoint; + rq->glyphs[count + i].cluster = info[i].cluster; + rq->glyphs[count + i].x_advance = position[i].x_advance; + rq->glyphs[count + i].y_advance = position[i].y_advance; + rq->glyphs[count + i].x_offset = position[i].x_offset; + rq->glyphs[count + i].y_offset = position[i].y_offset; + rq->glyphs[count + i].ftface = rq->text_info[info[i].cluster].ftface; + + RAQM_TEST ("glyph [%d]\tx_offset: %d\ty_offset: %d\tx_advance: %d\tfont: %s\n", + rq->glyphs[count + i].index, rq->glyphs[count + i].x_offset, + rq->glyphs[count + i].y_offset, rq->glyphs[count + i].x_advance, + rq->glyphs[count + i].ftface->family_name); + } + + count += len; + } + + if (rq->flags & RAQM_FLAG_UTF8) + { +#ifdef RAQM_TESTING + RAQM_TEST ("\nUTF-32 clusters:"); + for (size_t i = 0; i < count; i++) + RAQM_TEST (" %02d", rq->glyphs[i].cluster); + RAQM_TEST ("\n"); +#endif + + for (size_t i = 0; i < count; i++) + rq->glyphs[i].cluster = _raqm_u32_to_u8_index (rq, + rq->glyphs[i].cluster); + +#ifdef RAQM_TESTING + RAQM_TEST ("UTF-8 clusters: "); + for (size_t i = 0; i < count; i++) + RAQM_TEST (" %02d", rq->glyphs[i].cluster); + RAQM_TEST ("\n"); +#endif + } + return rq->glyphs; +} + +static bool +_raqm_resolve_scripts (raqm_t *rq); + +static hb_direction_t +_raqm_hb_dir (raqm_t *rq, FriBidiLevel level) +{ + hb_direction_t dir = HB_DIRECTION_LTR; + + if (rq->base_dir == RAQM_DIRECTION_TTB) + dir = HB_DIRECTION_TTB; + else if (FRIBIDI_LEVEL_IS_RTL (level)) + dir = HB_DIRECTION_RTL; + + return dir; +} + +typedef struct { + size_t pos; + size_t len; + FriBidiLevel level; +} _raqm_bidi_run; + +static void +_raqm_reverse_run (_raqm_bidi_run *run, const size_t len) +{ + assert (run); + + for (size_t i = 0; i < len / 2; i++) + { + _raqm_bidi_run temp = run[i]; + run[i] = run[len - 1 - i]; + run[len - 1 - i] = temp; + } +} + +static _raqm_bidi_run * +_raqm_reorder_runs (const FriBidiCharType *types, + const size_t len, + const FriBidiParType base_dir, + /* input and output */ + FriBidiLevel *levels, + /* output */ + size_t *run_count) +{ + FriBidiLevel level; + FriBidiLevel last_level = -1; + FriBidiLevel max_level = 0; + size_t run_start = 0; + size_t run_index = 0; + _raqm_bidi_run *runs = NULL; + size_t count = 0; + + if (len == 0) + { + *run_count = 0; + return NULL; + } + + assert (types); + assert (levels); + + /* L1. Reset the embedding levels of some chars: + 4. any sequence of white space characters at the end of the line. */ + for (int i = len - 1; + i >= 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); i--) + { + levels[i] = FRIBIDI_DIR_TO_LEVEL (base_dir); + } + + /* Find max_level of the line. We don't reuse the paragraph + * max_level, both for a cleaner API, and that the line max_level + * may be far less than paragraph max_level. */ + for (int i = len - 1; i >= 0; i--) + { + if (levels[i] > max_level) + max_level = levels[i]; + } + + for (size_t i = 0; i < len; i++) + { + if (levels[i] != last_level) + count++; + + last_level = levels[i]; + } + + runs = malloc (sizeof (_raqm_bidi_run) * count); + + while (run_start < len) + { + size_t run_end = run_start; + while (run_end < len && levels[run_start] == levels[run_end]) + { + run_end++; + } + + runs[run_index].pos = run_start; + runs[run_index].level = levels[run_start]; + runs[run_index].len = run_end - run_start; + run_start = run_end; + run_index++; + } + + /* L2. Reorder. */ + for (level = max_level; level > 0; level--) + { + for (int i = count - 1; i >= 0; i--) + { + if (runs[i].level >= level) + { + int end = i; + for (i--; (i >= 0 && runs[i].level >= level); i--) + ; + _raqm_reverse_run (runs + i + 1, end - i); + } + } + } + + *run_count = count; + return runs; +} + +static bool +_raqm_itemize (raqm_t *rq) +{ + FriBidiParType par_type = FRIBIDI_PAR_ON; + FriBidiCharType *types; +#ifdef USE_FRIBIDI_EX_API + FriBidiBracketType *btypes; +#endif + FriBidiLevel *levels; + _raqm_bidi_run *runs = NULL; + raqm_run_t *last; + int max_level; + size_t run_count; + bool ok = true; + +#ifdef RAQM_TESTING + switch (rq->base_dir) + { + case RAQM_DIRECTION_RTL: + RAQM_TEST ("Direction is: RTL\n\n"); + break; + case RAQM_DIRECTION_LTR: + RAQM_TEST ("Direction is: LTR\n\n"); + break; + case RAQM_DIRECTION_TTB: + RAQM_TEST ("Direction is: TTB\n\n"); + break; + case RAQM_DIRECTION_DEFAULT: + default: + RAQM_TEST ("Direction is: DEFAULT\n\n"); + break; + } +#endif + + types = calloc (rq->text_len, sizeof (FriBidiCharType)); +#ifdef USE_FRIBIDI_EX_API + btypes = calloc (rq->text_len, sizeof (FriBidiBracketType)); +#endif + levels = calloc (rq->text_len, sizeof (FriBidiLevel)); + if (!types || !levels +#ifdef USE_FRIBIDI_EX_API + || !btypes +#endif + ) + { + ok = false; + goto done; + } + + if (rq->base_dir == RAQM_DIRECTION_RTL) + par_type = FRIBIDI_PAR_RTL; + else if (rq->base_dir == RAQM_DIRECTION_LTR) + par_type = FRIBIDI_PAR_LTR; + + if (rq->base_dir == RAQM_DIRECTION_TTB) + { + /* Treat every thing as LTR in vertical text */ + max_level = 1; + memset (types, FRIBIDI_TYPE_LTR, rq->text_len); + memset (levels, 0, rq->text_len); + rq->resolved_dir = RAQM_DIRECTION_LTR; + } + else + { + fribidi_get_bidi_types (rq->text, rq->text_len, types); +#ifdef USE_FRIBIDI_EX_API + fribidi_get_bracket_types (rq->text, rq->text_len, types, btypes); + max_level = fribidi_get_par_embedding_levels_ex (types, btypes, + rq->text_len, &par_type, + levels); +#else + max_level = fribidi_get_par_embedding_levels (types, rq->text_len, + &par_type, levels); +#endif + + if (par_type == FRIBIDI_PAR_LTR) + rq->resolved_dir = RAQM_DIRECTION_LTR; + else + rq->resolved_dir = RAQM_DIRECTION_RTL; + } + + if (max_level == 0) + { + ok = false; + goto done; + } + + if (!_raqm_resolve_scripts (rq)) + { + ok = false; + goto done; + } + + /* Get the number of bidi runs */ + runs = _raqm_reorder_runs (types, rq->text_len, par_type, levels, &run_count); + if (!runs) + { + ok = false; + goto done; + } + +#ifdef RAQM_TESTING + RAQM_TEST ("Number of runs before script itemization: %zu\n\n", run_count); + + RAQM_TEST ("Fribidi Runs:\n"); + for (size_t i = 0; i < run_count; i++) + { + RAQM_TEST ("run[%zu]:\t start: %zu\tlength: %zu\tlevel: %d\n", + i, runs[i].pos, runs[i].len, runs[i].level); + } + RAQM_TEST ("\n"); +#endif + + last = NULL; + for (size_t i = 0; i < run_count; i++) + { + raqm_run_t *run = calloc (1, sizeof (raqm_run_t)); + if (!run) + { + ok = false; + goto done; + } + + if (!rq->runs) + rq->runs = run; + + if (last) + last->next = run; + + run->direction = _raqm_hb_dir (rq, runs[i].level); + + if (HB_DIRECTION_IS_BACKWARD (run->direction)) + { + run->pos = runs[i].pos + runs[i].len - 1; + run->script = rq->text_info[run->pos].script; + run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); + for (int j = runs[i].len - 1; j >= 0; j--) + { + _raqm_text_info info = rq->text_info[runs[i].pos + j]; + if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) + { + raqm_run_t *newrun = calloc (1, sizeof (raqm_run_t)); + if (!newrun) + { + ok = false; + goto done; + } + newrun->pos = runs[i].pos + j; + newrun->len = 1; + newrun->direction = _raqm_hb_dir (rq, runs[i].level); + newrun->script = info.script; + newrun->font = _raqm_create_hb_font (rq, info.ftface); + run->next = newrun; + run = newrun; + } + else + { + run->len++; + run->pos = runs[i].pos + j; + } + } + } + else + { + run->pos = runs[i].pos; + run->script = rq->text_info[run->pos].script; + run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); + for (size_t j = 0; j < runs[i].len; j++) + { + _raqm_text_info info = rq->text_info[runs[i].pos + j]; + if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) + { + raqm_run_t *newrun = calloc (1, sizeof (raqm_run_t)); + if (!newrun) + { + ok = false; + goto done; + } + newrun->pos = runs[i].pos + j; + newrun->len = 1; + newrun->direction = _raqm_hb_dir (rq, runs[i].level); + newrun->script = info.script; + newrun->font = _raqm_create_hb_font (rq, info.ftface); + run->next = newrun; + run = newrun; + } + else + run->len++; + } + } + + last = run; + last->next = NULL; + } + +#ifdef RAQM_TESTING + run_count = 0; + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + run_count++; + RAQM_TEST ("Number of runs after script itemization: %zu\n\n", run_count); + + run_count = 0; + RAQM_TEST ("Final Runs:\n"); + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + SCRIPT_TO_STRING (run->script); + RAQM_TEST ("run[%zu]:\t start: %d\tlength: %d\tdirection: %s\tscript: %s\tfont: %s\n", + run_count++, run->pos, run->len, + hb_direction_to_string (run->direction), buff, + rq->text_info[run->pos].ftface->family_name); + } + RAQM_TEST ("\n"); +#endif + +done: + free (runs); + free (types); +#ifdef USE_FRIBIDI_EX_API + free (btypes); +#endif + free (levels); + + return ok; +} + +/* Stack to handle script detection */ +typedef struct { + size_t capacity; + size_t size; + int *pair_index; + hb_script_t *script; +} _raqm_stack_t; + +/* Special paired characters for script detection */ +static size_t paired_len = 34; +static const FriBidiChar paired_chars[] = +{ + 0x0028, 0x0029, /* ascii paired punctuation */ + 0x003c, 0x003e, + 0x005b, 0x005d, + 0x007b, 0x007d, + 0x00ab, 0x00bb, /* guillemets */ + 0x2018, 0x2019, /* general punctuation */ + 0x201c, 0x201d, + 0x2039, 0x203a, + 0x3008, 0x3009, /* chinese paired punctuation */ + 0x300a, 0x300b, + 0x300c, 0x300d, + 0x300e, 0x300f, + 0x3010, 0x3011, + 0x3014, 0x3015, + 0x3016, 0x3017, + 0x3018, 0x3019, + 0x301a, 0x301b +}; + +static void +_raqm_stack_free (_raqm_stack_t *stack) +{ + free (stack->script); + free (stack->pair_index); + free (stack); +} + +/* Stack handling functions */ +static _raqm_stack_t * +_raqm_stack_new (size_t max) +{ + _raqm_stack_t *stack; + stack = calloc (1, sizeof (_raqm_stack_t)); + if (!stack) + return NULL; + + stack->script = malloc (sizeof (hb_script_t) * max); + if (!stack->script) + { + _raqm_stack_free (stack); + return NULL; + } + + stack->pair_index = malloc (sizeof (int) * max); + if (!stack->pair_index) + { + _raqm_stack_free (stack); + return NULL; + } + + stack->size = 0; + stack->capacity = max; + + return stack; +} + +static bool +_raqm_stack_pop (_raqm_stack_t *stack) +{ + if (!stack->size) + { + RAQM_DBG ("Stack is Empty\n"); + return false; + } + + stack->size--; + + return true; +} + +static hb_script_t +_raqm_stack_top (_raqm_stack_t *stack) +{ + if (!stack->size) + { + RAQM_DBG ("Stack is Empty\n"); + return HB_SCRIPT_INVALID; /* XXX: check this */ + } + + return stack->script[stack->size]; +} + +static bool +_raqm_stack_push (_raqm_stack_t *stack, + hb_script_t script, + int pair_index) +{ + if (stack->size == stack->capacity) + { + RAQM_DBG ("Stack is Full\n"); + return false; + } + + stack->size++; + stack->script[stack->size] = script; + stack->pair_index[stack->size] = pair_index; + + return true; +} + +static int +_get_pair_index (const FriBidiChar ch) +{ + int lower = 0; + int upper = paired_len - 1; + + while (lower <= upper) + { + int mid = (lower + upper) / 2; + if (ch < paired_chars[mid]) + upper = mid - 1; + else if (ch > paired_chars[mid]) + lower = mid + 1; + else + return mid; + } + + return -1; +} + +#define STACK_IS_EMPTY(script) ((script)->size <= 0) +#define IS_OPEN(pair_index) (((pair_index) & 1) == 0) + +/* Resolve the script for each character in the input string, if the character + * script is common or inherited it takes the script of the character before it + * except paired characters which we try to make them use the same script. We + * then split the BiDi runs, if necessary, on script boundaries. + */ +static bool +_raqm_resolve_scripts (raqm_t *rq) +{ + int last_script_index = -1; + int last_set_index = -1; + hb_script_t last_script = HB_SCRIPT_INVALID; + _raqm_stack_t *stack = NULL; + hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); + + for (size_t i = 0; i < rq->text_len; ++i) + rq->text_info[i].script = hb_unicode_script (unicode_funcs, rq->text[i]); + +#ifdef RAQM_TESTING + RAQM_TEST ("Before script detection:\n"); + for (size_t i = 0; i < rq->text_len; ++i) + { + SCRIPT_TO_STRING (rq->text_info[i].script); + RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); + } + RAQM_TEST ("\n"); +#endif + + stack = _raqm_stack_new (rq->text_len); + if (!stack) + return false; + + for (int i = 0; i < (int) rq->text_len; i++) + { + if (rq->text_info[i].script == HB_SCRIPT_COMMON && last_script_index != -1) + { + int pair_index = _get_pair_index (rq->text[i]); + if (pair_index >= 0) + { + if (IS_OPEN (pair_index)) + { + /* is a paired character */ + rq->text_info[i].script = last_script; + last_set_index = i; + _raqm_stack_push (stack, rq->text_info[i].script, pair_index); + } + else + { + /* is a close paired character */ + /* find matching opening (by getting the last even index for current + * odd index) */ + while (!STACK_IS_EMPTY (stack) && + stack->pair_index[stack->size] != (pair_index & ~1)) + { + _raqm_stack_pop (stack); + } + if (!STACK_IS_EMPTY (stack)) + { + rq->text_info[i].script = _raqm_stack_top (stack); + last_script = rq->text_info[i].script; + last_set_index = i; + } + else + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + } + } + else + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + } + else if (rq->text_info[i].script == HB_SCRIPT_INHERITED && + last_script_index != -1) + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + else + { + for (int j = last_set_index + 1; j < i; ++j) + rq->text_info[j].script = rq->text_info[i].script; + last_script = rq->text_info[i].script; + last_script_index = i; + last_set_index = i; + } + } + + /* Loop backwards and change any remaining Common or Inherit characters to + * take the script if the next character. + * https://github.com/HOST-Oman/libraqm/issues/95 + */ + for (int i = rq->text_len - 2; i >= 0; --i) + { + if (rq->text_info[i].script == HB_SCRIPT_INHERITED || + rq->text_info[i].script == HB_SCRIPT_COMMON) + rq->text_info[i].script = rq->text_info[i + 1].script; + } + +#ifdef RAQM_TESTING + RAQM_TEST ("After script detection:\n"); + for (size_t i = 0; i < rq->text_len; ++i) + { + SCRIPT_TO_STRING (rq->text_info[i].script); + RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); + } + RAQM_TEST ("\n"); +#endif + + _raqm_stack_free (stack); + + return true; +} + +static bool +_raqm_shape (raqm_t *rq) +{ + hb_buffer_flags_t hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT; + +#if defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) && \ + HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES + if (rq->invisible_glyph < 0) + hb_buffer_flags |= HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES; +#endif + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + run->buffer = hb_buffer_create (); + + hb_buffer_add_utf32 (run->buffer, rq->text, rq->text_len, + run->pos, run->len); + hb_buffer_set_script (run->buffer, run->script); + hb_buffer_set_language (run->buffer, rq->text_info[run->pos].lang); + hb_buffer_set_direction (run->buffer, run->direction); + hb_buffer_set_flags (run->buffer, hb_buffer_flags); + +#ifdef HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH + if (rq->invisible_glyph > 0) + hb_buffer_set_invisible_glyph (run->buffer, rq->invisible_glyph); +#endif + + hb_shape_full (run->font, run->buffer, rq->features, rq->features_len, + NULL); + } + + return true; +} + +/* Convert index from UTF-32 to UTF-8 */ +static uint32_t +_raqm_u32_to_u8_index (raqm_t *rq, + uint32_t index) +{ + FriBidiStrIndex length; + char *output = malloc ((sizeof (char) * 4 * index) + 1); + + length = fribidi_unicode_to_charset (FRIBIDI_CHAR_SET_UTF8, + rq->text, + index, + output); + + free (output); + return length; +} + +/* Convert index from UTF-8 to UTF-32 */ +static uint32_t +_raqm_u8_to_u32_index (raqm_t *rq, + uint32_t index) +{ + FriBidiStrIndex length; + uint32_t *output = malloc (sizeof (uint32_t) * (index + 1)); + + length = fribidi_charset_to_unicode (FRIBIDI_CHAR_SET_UTF8, + rq->text_utf8, + index, + output); + + free (output); + return length; +} + +static bool +_raqm_allowed_grapheme_boundary (hb_codepoint_t l_char, + hb_codepoint_t r_char); + +static bool +_raqm_in_hangul_syllable (hb_codepoint_t ch); + +/** + * raqm_index_to_position: + * @rq: a #raqm_t. + * @index: (inout): character index. + * @x: (out): output x position. + * @y: (out): output y position. + * + * Calculates the cursor position after the character at @index. If the character + * is right-to-left, then the cursor will be at the left of it, whereas if the + * character is left-to-right, then the cursor will be at the right of it. + * + * Return value: + * %true if the process was successful, %false otherwise. + * + * Since: 0.2 + */ +bool +raqm_index_to_position (raqm_t *rq, + size_t *index, + int *x, + int *y) +{ + /* We don't currently support multiline, so y is always 0 */ + *y = 0; + *x = 0; + + if (rq == NULL) + return false; + + if (rq->flags & RAQM_FLAG_UTF8) + *index = _raqm_u8_to_u32_index (rq, *index); + + if (*index >= rq->text_len) + return false; + + RAQM_TEST ("\n"); + + while (*index < rq->text_len) + { + if (_raqm_allowed_grapheme_boundary (rq->text[*index], rq->text[*index + 1])) + break; + + ++*index; + } + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + uint32_t curr_cluster = info[i].cluster; + uint32_t next_cluster = curr_cluster; + *x += position[i].x_advance; + + if (run->direction == HB_DIRECTION_LTR) + { + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + next_cluster = info[j].cluster; + } + else + { + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) + next_cluster = info[j].cluster; + } + + if (next_cluster == curr_cluster) + next_cluster = run->pos + run->len; + + if (*index < next_cluster && *index >= curr_cluster) + { + if (run->direction == HB_DIRECTION_RTL) + *x -= position[i].x_advance; + *index = curr_cluster; + goto found; + } + } + } + +found: + if (rq->flags & RAQM_FLAG_UTF8) + *index = _raqm_u32_to_u8_index (rq, *index); + RAQM_TEST ("The position is %d at index %zu\n",*x ,*index); + return true; +} + +/** + * raqm_position_to_index: + * @rq: a #raqm_t. + * @x: x position. + * @y: y position. + * @index: (out): output character index. + * + * Returns the @index of the character at @x and @y position within text. + * If the position is outside the text, the last character is chosen as + * @index. + * + * Return value: + * %true if the process was successful, %false in case of error. + * + * Since: 0.2 + */ +bool +raqm_position_to_index (raqm_t *rq, + int x, + int y, + size_t *index) +{ + int delta_x = 0, current_x = 0; + (void)y; + + if (rq == NULL) + return false; + + if (x < 0) /* Get leftmost index */ + { + if (rq->resolved_dir == RAQM_DIRECTION_RTL) + *index = rq->text_len; + else + *index = 0; + return true; + } + + RAQM_TEST ("\n"); + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + delta_x = position[i].x_advance; + if (x < (current_x + delta_x)) + { + bool before = false; + if (run->direction == HB_DIRECTION_LTR) + before = (x < current_x + (delta_x / 2)); + else + before = (x > current_x + (delta_x / 2)); + + if (before) + *index = info[i].cluster; + else + { + uint32_t curr_cluster = info[i].cluster; + uint32_t next_cluster = curr_cluster; + if (run->direction == HB_DIRECTION_LTR) + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + next_cluster = info[j].cluster; + else + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) + next_cluster = info[j].cluster; + + if (next_cluster == curr_cluster) + next_cluster = run->pos + run->len; + + *index = next_cluster; + } + if (_raqm_allowed_grapheme_boundary (rq->text[*index],rq->text[*index + 1])) + { + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + return true; + } + + while (*index < (unsigned)run->pos + run->len) + { + if (_raqm_allowed_grapheme_boundary (rq->text[*index], + rq->text[*index + 1])) + { + *index += 1; + break; + } + *index += 1; + } + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + return true; + } + else + current_x += delta_x; + } + } + + /* Get rightmost index*/ + if (rq->resolved_dir == RAQM_DIRECTION_RTL) + *index = 0; + else + *index = rq->text_len; + + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + + return true; +} + +typedef enum +{ + RAQM_GRAPHEM_CR, + RAQM_GRAPHEM_LF, + RAQM_GRAPHEM_CONTROL, + RAQM_GRAPHEM_EXTEND, + RAQM_GRAPHEM_REGIONAL_INDICATOR, + RAQM_GRAPHEM_PREPEND, + RAQM_GRAPHEM_SPACING_MARK, + RAQM_GRAPHEM_HANGUL_SYLLABLE, + RAQM_GRAPHEM_OTHER +} _raqm_grapheme_t; + +static _raqm_grapheme_t +_raqm_get_grapheme_break (hb_codepoint_t ch, + hb_unicode_general_category_t category); + +static bool +_raqm_allowed_grapheme_boundary (hb_codepoint_t l_char, + hb_codepoint_t r_char) +{ + hb_unicode_general_category_t l_category; + hb_unicode_general_category_t r_category; + _raqm_grapheme_t l_grapheme, r_grapheme; + hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); + + l_category = hb_unicode_general_category (unicode_funcs, l_char); + r_category = hb_unicode_general_category (unicode_funcs, r_char); + l_grapheme = _raqm_get_grapheme_break (l_char, l_category); + r_grapheme = _raqm_get_grapheme_break (r_char, r_category); + + if (l_grapheme == RAQM_GRAPHEM_CR && r_grapheme == RAQM_GRAPHEM_LF) + return false; /*Do not break between a CR and LF GB3*/ + if (l_grapheme == RAQM_GRAPHEM_CONTROL || l_grapheme == RAQM_GRAPHEM_CR || + l_grapheme == RAQM_GRAPHEM_LF || r_grapheme == RAQM_GRAPHEM_CONTROL || + r_grapheme == RAQM_GRAPHEM_CR || r_grapheme == RAQM_GRAPHEM_LF) + return true; /*Break before and after CONTROL GB4, GB5*/ + if (r_grapheme == RAQM_GRAPHEM_HANGUL_SYLLABLE) + return false; /*Do not break Hangul syllable sequences. GB6, GB7, GB8*/ + if (l_grapheme == RAQM_GRAPHEM_REGIONAL_INDICATOR && + r_grapheme == RAQM_GRAPHEM_REGIONAL_INDICATOR) + return false; /*Do not break between regional indicator symbols. GB8a*/ + if (r_grapheme == RAQM_GRAPHEM_EXTEND) + return false; /*Do not break before extending characters. GB9*/ + /*Do not break before SpacingMarks, or after Prepend characters.GB9a, GB9b*/ + if (l_grapheme == RAQM_GRAPHEM_PREPEND) + return false; + if (r_grapheme == RAQM_GRAPHEM_SPACING_MARK) + return false; + return true; /*Otherwise, break everywhere. GB1, GB2, GB10*/ +} + +static _raqm_grapheme_t +_raqm_get_grapheme_break (hb_codepoint_t ch, + hb_unicode_general_category_t category) +{ + _raqm_grapheme_t gb_type; + + gb_type = RAQM_GRAPHEM_OTHER; + switch ((int)category) + { + case HB_UNICODE_GENERAL_CATEGORY_FORMAT: + if (ch == 0x200C || ch == 0x200D) + gb_type = RAQM_GRAPHEM_EXTEND; + else + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_CONTROL: + if (ch == 0x000D) + gb_type = RAQM_GRAPHEM_CR; + else if (ch == 0x000A) + gb_type = RAQM_GRAPHEM_LF; + else + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_SURROGATE: + case HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR: + case HB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR: + case HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED: + if ((ch >= 0xFFF0 && ch <= 0xFFF8) || + (ch >= 0xE0000 && ch <= 0xE0FFF)) + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK: + case HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK: + case HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK: + if (ch != 0x102B && ch != 0x102C && ch != 0x1038 && + (ch < 0x1062 || ch > 0x1064) && (ch < 0x1067 || ch > 0x106D) && + ch != 0x1083 && (ch < 0x1087 || ch > 0x108C) && ch != 0x108F && + (ch < 0x109A || ch > 0x109C) && ch != 0x1A61 && ch != 0x1A63 && + ch != 0x1A64 && ch != 0xAA7B && ch != 0xAA70 && ch != 0x11720 && + ch != 0x11721) /**/ + gb_type = RAQM_GRAPHEM_SPACING_MARK; + + else if (ch == 0x09BE || ch == 0x09D7 || + ch == 0x0B3E || ch == 0x0B57 || ch == 0x0BBE || ch == 0x0BD7 || + ch == 0x0CC2 || ch == 0x0CD5 || ch == 0x0CD6 || + ch == 0x0D3E || ch == 0x0D57 || ch == 0x0DCF || ch == 0x0DDF || + ch == 0x1D165 || (ch >= 0x1D16E && ch <= 0x1D172)) + gb_type = RAQM_GRAPHEM_EXTEND; + break; + + case HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER: + if (ch == 0x0E33 || ch == 0x0EB3) + gb_type = RAQM_GRAPHEM_SPACING_MARK; + break; + + case HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL: + if (ch >= 0x1F1E6 && ch <= 0x1F1FF) + gb_type = RAQM_GRAPHEM_REGIONAL_INDICATOR; + break; + + default: + gb_type = RAQM_GRAPHEM_OTHER; + break; + } + + if (_raqm_in_hangul_syllable (ch)) + gb_type = RAQM_GRAPHEM_HANGUL_SYLLABLE; + + return gb_type; +} + +static bool +_raqm_in_hangul_syllable (hb_codepoint_t ch) +{ + (void)ch; + return false; +} + +/** + * raqm_version: + * @major: (out): Library major version component. + * @minor: (out): Library minor version component. + * @micro: (out): Library micro version component. + * + * Returns library version as three integer components. + * + * Since: 0.7 + **/ +void +raqm_version (unsigned int *major, + unsigned int *minor, + unsigned int *micro) +{ + *major = RAQM_VERSION_MAJOR; + *minor = RAQM_VERSION_MINOR; + *micro = RAQM_VERSION_MICRO; +} + +/** + * raqm_version_string: + * + * Returns library version as a string with three components. + * + * Return value: library version string. + * + * Since: 0.7 + **/ +const char * +raqm_version_string (void) +{ + return RAQM_VERSION_STRING; +} + +/** + * raqm_version_atleast: + * @major: Library major version component. + * @minor: Library minor version component. + * @micro: Library micro version component. + * + * Checks if library version is less than or equal the specified version. + * + * Return value: + * %true if library version is less than or equal the specfied version, %false + * otherwise. + * + * Since: 0.7 + **/ +bool +raqm_version_atleast (unsigned int major, + unsigned int minor, + unsigned int micro) +{ + return RAQM_VERSION_ATLEAST (major, minor, micro); +} + +/** + * RAQM_VERSION_ATLEAST: + * @major: Library major version component. + * @minor: Library minor version component. + * @micro: Library micro version component. + * + * Checks if library version is less than or equal the specified version. + * + * Return value: + * %true if library version is less than or equal the specfied version, %false + * otherwise. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_STRING: + * + * Library version as a string with three components. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MAJOR: + * + * Library major version component. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MINOR: + * + * Library minor version component. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MICRO: + * + * Library micro version component. + * + * Since: 0.7 + **/ diff --git a/src/thirdparty/raqm/raqm.h b/src/thirdparty/raqm/raqm.h new file mode 100644 index 00000000000..1a33fe8bad2 --- /dev/null +++ b/src/thirdparty/raqm/raqm.h @@ -0,0 +1,185 @@ +/* + * Copyright © 2015 Information Technology Authority (ITA) + * Copyright © 2016 Khaled Hosny + * + * 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 _RAQM_H_ +#define _RAQM_H_ +#define _RAQM_H_IN_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include FT_FREETYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "raqm-version.h" + +/** + * raqm_t: + * + * This is the main object holding all state of the currently processed text as + * well as its output. + * + * Since: 0.1 + */ +typedef struct _raqm raqm_t; + +/** + * raqm_direction_t: + * @RAQM_DIRECTION_DEFAULT: Detect paragraph direction automatically. + * @RAQM_DIRECTION_RTL: Paragraph is mainly right-to-left text. + * @RAQM_DIRECTION_LTR: Paragraph is mainly left-to-right text. + * @RAQM_DIRECTION_TTB: Paragraph is mainly vertical top-to-bottom text. + * + * Base paragraph direction, see raqm_set_par_direction(). + * + * Since: 0.1 + */ +typedef enum +{ + RAQM_DIRECTION_DEFAULT, + RAQM_DIRECTION_RTL, + RAQM_DIRECTION_LTR, + RAQM_DIRECTION_TTB +} raqm_direction_t; + +/** + * raqm_glyph_t: + * @index: the index of the glyph in the font file. + * @x_advance: the glyph advance width in horizontal text. + * @y_advance: the glyph advance width in vertical text. + * @x_offset: the horizontal movement of the glyph from the current point. + * @y_offset: the vertical movement of the glyph from the current point. + * @cluster: the index of original character in input text. + * @ftface: the @FT_Face of the glyph. + * + * The structure that holds information about output glyphs, returned from + * raqm_get_glyphs(). + */ +typedef struct raqm_glyph_t { + unsigned int index; + int x_advance; + int y_advance; + int x_offset; + int y_offset; + uint32_t cluster; + FT_Face ftface; +} raqm_glyph_t; + +raqm_t * +raqm_create (void); + +raqm_t * +raqm_reference (raqm_t *rq); + +void +raqm_destroy (raqm_t *rq); + +bool +raqm_set_text (raqm_t *rq, + const uint32_t *text, + size_t len); + +bool +raqm_set_text_utf8 (raqm_t *rq, + const char *text, + size_t len); + +bool +raqm_set_par_direction (raqm_t *rq, + raqm_direction_t dir); + +bool +raqm_set_language (raqm_t *rq, + const char *lang, + size_t start, + size_t len); + +bool +raqm_add_font_feature (raqm_t *rq, + const char *feature, + int len); + +bool +raqm_set_freetype_face (raqm_t *rq, + FT_Face face); + +bool +raqm_set_freetype_face_range (raqm_t *rq, + FT_Face face, + size_t start, + size_t len); + +bool +raqm_set_freetype_load_flags (raqm_t *rq, + int flags); + +bool +raqm_set_invisible_glyph (raqm_t *rq, + int gid); + +bool +raqm_layout (raqm_t *rq); + +raqm_glyph_t * +raqm_get_glyphs (raqm_t *rq, + size_t *length); + +bool +raqm_index_to_position (raqm_t *rq, + size_t *index, + int *x, + int *y); + +bool +raqm_position_to_index (raqm_t *rq, + int x, + int y, + size_t *index); + +void +raqm_version (unsigned int *major, + unsigned int *minor, + unsigned int *micro); + +const char * +raqm_version_string (void); + +bool +raqm_version_atleast (unsigned int major, + unsigned int minor, + unsigned int micro); + + +#ifdef __cplusplus +} +#endif +#undef _RAQM_H_IN_ +#endif /* _RAQM_H_ */ From 8bc1ff35b4d87003e54d7f8cdcbc687ad3a62762 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 11:21:42 +0000 Subject: [PATCH 02/21] use FriBiDi shim in Raqm --- setup.py | 16 +- src/_imagingft.c | 225 ++++++-------------------- src/libImaging/raqm.h | 156 ------------------ src/thirdparty/fribidi-shim/fribidi.c | 68 ++++++++ src/thirdparty/fribidi-shim/fribidi.h | 104 ++++++++++++ src/thirdparty/raqm/raqm.c | 7 +- winbuild/build_prepare.py | 20 +-- winbuild/fribidi.cmake | 4 +- 8 files changed, 243 insertions(+), 357 deletions(-) delete mode 100644 src/libImaging/raqm.h create mode 100644 src/thirdparty/fribidi-shim/fribidi.c create mode 100644 src/thirdparty/fribidi-shim/fribidi.h diff --git a/setup.py b/setup.py index cbc2641c59f..3e0ec557676 100755 --- a/setup.py +++ b/setup.py @@ -267,6 +267,7 @@ class feature: "jpeg", "tiff", "freetype", + "harfbuzz", "lcms", "webp", "webpmux", @@ -656,6 +657,12 @@ def build_extensions(self): if subdir: _add_directory(self.compiler.include_dirs, subdir, 0) + if feature.want("harfbuzz"): + _dbg("Looking for harfbuzz") + if _find_include_file(self, "hb-version.h"): + if _find_library_file(self, "harfbuzz"): + feature.harfbuzz = "harfbuzz" + if feature.want("lcms"): _dbg("Looking for lcms") if _find_include_file(self, "lcms2.h"): @@ -850,7 +857,14 @@ def debug_build(): files.append(os.path.join("src/libImaging", src_file + ".c")) ext_modules = [ Extension("PIL._imaging", files), - Extension("PIL._imagingft", ["src/_imagingft.c"]), + Extension( + "PIL._imagingft", + [ + "src/_imagingft.c", + "src/thirdparty/raqm/raqm.c", + "src/thirdparty/fribidi-shim/fribidi.c", + ], + ), Extension("PIL._imagingcms", ["src/_imagingcms.c"]), Extension("PIL._webp", ["src/_webp.c"]), Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), diff --git a/src/_imagingft.c b/src/_imagingft.c index d73c6c2d557..4a4084e9fd9 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -35,10 +35,6 @@ #define KEEP_PY_UNICODE -#ifndef _WIN32 -#include -#endif - #if !defined(FT_LOAD_TARGET_MONO) #define FT_LOAD_TARGET_MONO FT_LOAD_MONOCHROME #endif @@ -56,7 +52,8 @@ } \ ; -#include "libImaging/raqm.h" +#include "thirdparty/raqm/raqm.h" +#include "thirdparty/fribidi-shim/fribidi.h" #define LAYOUT_FALLBACK 0 #define LAYOUT_RAQM 1 @@ -86,42 +83,6 @@ typedef struct { static PyTypeObject Font_Type; -typedef const char *(*t_raqm_version_string)(void); -typedef bool (*t_raqm_version_atleast)( - unsigned int major, unsigned int minor, unsigned int micro); -typedef raqm_t *(*t_raqm_create)(void); -typedef int (*t_raqm_set_text)(raqm_t *rq, const uint32_t *text, size_t len); -typedef bool (*t_raqm_set_text_utf8)(raqm_t *rq, const char *text, size_t len); -typedef bool (*t_raqm_set_par_direction)(raqm_t *rq, raqm_direction_t dir); -typedef bool (*t_raqm_set_language)( - raqm_t *rq, const char *lang, size_t start, size_t len); -typedef bool (*t_raqm_add_font_feature)(raqm_t *rq, const char *feature, int len); -typedef bool (*t_raqm_set_freetype_face)(raqm_t *rq, FT_Face face); -typedef bool (*t_raqm_layout)(raqm_t *rq); -typedef raqm_glyph_t *(*t_raqm_get_glyphs)(raqm_t *rq, size_t *length); -typedef raqm_glyph_t_01 *(*t_raqm_get_glyphs_01)(raqm_t *rq, size_t *length); -typedef void (*t_raqm_destroy)(raqm_t *rq); - -typedef struct { - void *raqm; - int version; - t_raqm_version_string version_string; - t_raqm_version_atleast version_atleast; - t_raqm_create create; - t_raqm_set_text set_text; - t_raqm_set_text_utf8 set_text_utf8; - t_raqm_set_par_direction set_par_direction; - t_raqm_set_language set_language; - t_raqm_add_font_feature add_font_feature; - t_raqm_set_freetype_face set_freetype_face; - t_raqm_layout layout; - t_raqm_get_glyphs get_glyphs; - t_raqm_get_glyphs_01 get_glyphs_01; - t_raqm_destroy destroy; -} p_raqm_func; - -static p_raqm_func p_raqm; - /* round a 26.6 pixel coordinate to the nearest integer */ #define PIXEL(x) ((((x) + 32) & -64) >> 6) @@ -142,101 +103,7 @@ geterror(int code) { static int setraqm(void) { - /* set the static function pointers for dynamic raqm linking */ - p_raqm.raqm = NULL; - - /* Microsoft needs a totally different system */ -#ifndef _WIN32 - p_raqm.raqm = dlopen("libraqm.so.0", RTLD_LAZY); - if (!p_raqm.raqm) { - p_raqm.raqm = dlopen("libraqm.dylib", RTLD_LAZY); - } -#else - p_raqm.raqm = LoadLibrary("libraqm"); - /* MSYS */ - if (!p_raqm.raqm) { - p_raqm.raqm = LoadLibrary("libraqm-0"); - } -#endif - - if (!p_raqm.raqm) { - return 1; - } - -#ifndef _WIN32 - p_raqm.version_string = - (t_raqm_version_string)dlsym(p_raqm.raqm, "raqm_version_string"); - p_raqm.version_atleast = - (t_raqm_version_atleast)dlsym(p_raqm.raqm, "raqm_version_atleast"); - p_raqm.create = (t_raqm_create)dlsym(p_raqm.raqm, "raqm_create"); - p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text"); - p_raqm.set_text_utf8 = - (t_raqm_set_text_utf8)dlsym(p_raqm.raqm, "raqm_set_text_utf8"); - p_raqm.set_par_direction = - (t_raqm_set_par_direction)dlsym(p_raqm.raqm, "raqm_set_par_direction"); - p_raqm.set_language = (t_raqm_set_language)dlsym(p_raqm.raqm, "raqm_set_language"); - p_raqm.add_font_feature = - (t_raqm_add_font_feature)dlsym(p_raqm.raqm, "raqm_add_font_feature"); - p_raqm.set_freetype_face = - (t_raqm_set_freetype_face)dlsym(p_raqm.raqm, "raqm_set_freetype_face"); - p_raqm.layout = (t_raqm_layout)dlsym(p_raqm.raqm, "raqm_layout"); - p_raqm.destroy = (t_raqm_destroy)dlsym(p_raqm.raqm, "raqm_destroy"); - if (dlsym(p_raqm.raqm, "raqm_index_to_position")) { - p_raqm.get_glyphs = (t_raqm_get_glyphs)dlsym(p_raqm.raqm, "raqm_get_glyphs"); - p_raqm.version = 2; - } else { - p_raqm.version = 1; - p_raqm.get_glyphs_01 = - (t_raqm_get_glyphs_01)dlsym(p_raqm.raqm, "raqm_get_glyphs"); - } - if (dlerror() || - !(p_raqm.create && p_raqm.set_text && p_raqm.set_text_utf8 && - p_raqm.set_par_direction && p_raqm.set_language && p_raqm.add_font_feature && - p_raqm.set_freetype_face && p_raqm.layout && - (p_raqm.get_glyphs || p_raqm.get_glyphs_01) && p_raqm.destroy)) { - dlclose(p_raqm.raqm); - p_raqm.raqm = NULL; - return 2; - } -#else - p_raqm.version_string = - (t_raqm_version_string)GetProcAddress(p_raqm.raqm, "raqm_version_string"); - p_raqm.version_atleast = - (t_raqm_version_atleast)GetProcAddress(p_raqm.raqm, "raqm_version_atleast"); - p_raqm.create = (t_raqm_create)GetProcAddress(p_raqm.raqm, "raqm_create"); - p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text"); - p_raqm.set_text_utf8 = - (t_raqm_set_text_utf8)GetProcAddress(p_raqm.raqm, "raqm_set_text_utf8"); - p_raqm.set_par_direction = - (t_raqm_set_par_direction)GetProcAddress(p_raqm.raqm, "raqm_set_par_direction"); - p_raqm.set_language = - (t_raqm_set_language)GetProcAddress(p_raqm.raqm, "raqm_set_language"); - p_raqm.add_font_feature = - (t_raqm_add_font_feature)GetProcAddress(p_raqm.raqm, "raqm_add_font_feature"); - p_raqm.set_freetype_face = - (t_raqm_set_freetype_face)GetProcAddress(p_raqm.raqm, "raqm_set_freetype_face"); - p_raqm.layout = (t_raqm_layout)GetProcAddress(p_raqm.raqm, "raqm_layout"); - p_raqm.destroy = (t_raqm_destroy)GetProcAddress(p_raqm.raqm, "raqm_destroy"); - if (GetProcAddress(p_raqm.raqm, "raqm_index_to_position")) { - p_raqm.get_glyphs = - (t_raqm_get_glyphs)GetProcAddress(p_raqm.raqm, "raqm_get_glyphs"); - p_raqm.version = 2; - } else { - p_raqm.version = 1; - p_raqm.get_glyphs_01 = - (t_raqm_get_glyphs_01)GetProcAddress(p_raqm.raqm, "raqm_get_glyphs"); - } - if (!(p_raqm.create && p_raqm.set_text && p_raqm.set_text_utf8 && - p_raqm.set_par_direction && p_raqm.set_language && p_raqm.add_font_feature && - p_raqm.set_freetype_face && p_raqm.layout && - (p_raqm.get_glyphs || p_raqm.get_glyphs_01) && p_raqm.destroy)) { - FreeLibrary(p_raqm.raqm); - p_raqm.raqm = NULL; - return 2; - } -#endif - - return 0; + return load_fribidi(); } static PyObject * @@ -359,10 +226,10 @@ text_layout_raqm( size_t i = 0, count = 0, start = 0; raqm_t *rq; raqm_glyph_t *glyphs = NULL; - raqm_glyph_t_01 *glyphs_01 = NULL; +// raqm_glyph_t_01 *glyphs_01 = NULL; raqm_direction_t direction; - rq = (*p_raqm.create)(); + rq = raqm_create(); if (rq == NULL) { PyErr_SetString(PyExc_ValueError, "raqm_create() failed."); goto failed; @@ -376,14 +243,14 @@ text_layout_raqm( and raqm fails with empty strings */ goto failed; } - int set_text = (*p_raqm.set_text)(rq, text, size); + int set_text = raqm_set_text(rq, text, size); PyMem_Free(text); if (!set_text) { PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed"); goto failed; } if (lang) { - if (!(*p_raqm.set_language)(rq, lang, start, size)) { + if (!raqm_set_language(rq, lang, start, size)) { PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed"); goto failed; } @@ -401,12 +268,12 @@ text_layout_raqm( direction = RAQM_DIRECTION_LTR; } else if (strcmp(dir, "ttb") == 0) { direction = RAQM_DIRECTION_TTB; - if (p_raqm.version_atleast == NULL || !(*p_raqm.version_atleast)(0, 7, 0)) { - PyErr_SetString( - PyExc_ValueError, - "libraqm 0.7 or greater required for 'ttb' direction"); - goto failed; - } +// if (p_raqm.version_atleast == NULL || !(*p_raqm.version_atleast)(0, 7, 0)) { +// PyErr_SetString( +// PyExc_ValueError, +// "libraqm 0.7 or greater required for 'ttb' direction"); +// goto failed; +// } } else { PyErr_SetString( PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"); @@ -414,7 +281,7 @@ text_layout_raqm( } } - if (!(*p_raqm.set_par_direction)(rq, direction)) { + if (!raqm_set_par_direction(rq, direction)) { PyErr_SetString(PyExc_ValueError, "raqm_set_par_direction() failed"); goto failed; } @@ -446,38 +313,38 @@ text_layout_raqm( feature = PyBytes_AS_STRING(bytes); size = PyBytes_GET_SIZE(bytes); } - if (!(*p_raqm.add_font_feature)(rq, feature, size)) { + if (!raqm_add_font_feature(rq, feature, size)) { PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed"); goto failed; } } } - if (!(*p_raqm.set_freetype_face)(rq, self->face)) { + if (!raqm_set_freetype_face(rq, self->face)) { PyErr_SetString(PyExc_RuntimeError, "raqm_set_freetype_face() failed."); goto failed; } - if (!(*p_raqm.layout)(rq)) { + if (!raqm_layout(rq)) { PyErr_SetString(PyExc_RuntimeError, "raqm_layout() failed."); goto failed; } - if (p_raqm.version == 1) { - glyphs_01 = (*p_raqm.get_glyphs_01)(rq, &count); - if (glyphs_01 == NULL) { - PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); - count = 0; - goto failed; - } - } else { /* version == 2 */ - glyphs = (*p_raqm.get_glyphs)(rq, &count); +// if (p_raqm.version == 1) { +// glyphs_01 = raqm_get_glyphs_01(rq, &count); +// if (glyphs_01 == NULL) { +// PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); +// count = 0; +// goto failed; +// } +// } else { /* version == 2 */ + glyphs = raqm_get_glyphs(rq, &count); if (glyphs == NULL) { PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); count = 0; goto failed; } - } +// } (*glyph_info) = PyMem_New(GlyphInfo, count); if ((*glyph_info) == NULL) { @@ -486,16 +353,16 @@ text_layout_raqm( goto failed; } - if (p_raqm.version == 1) { - for (i = 0; i < count; i++) { - (*glyph_info)[i].index = glyphs_01[i].index; - (*glyph_info)[i].x_offset = glyphs_01[i].x_offset; - (*glyph_info)[i].x_advance = glyphs_01[i].x_advance; - (*glyph_info)[i].y_offset = glyphs_01[i].y_offset; - (*glyph_info)[i].y_advance = glyphs_01[i].y_advance; - (*glyph_info)[i].cluster = glyphs_01[i].cluster; - } - } else { +// if (p_raqm.version == 1) { +// for (i = 0; i < count; i++) { +// (*glyph_info)[i].index = glyphs_01[i].index; +// (*glyph_info)[i].x_offset = glyphs_01[i].x_offset; +// (*glyph_info)[i].x_advance = glyphs_01[i].x_advance; +// (*glyph_info)[i].y_offset = glyphs_01[i].y_offset; +// (*glyph_info)[i].y_advance = glyphs_01[i].y_advance; +// (*glyph_info)[i].cluster = glyphs_01[i].cluster; +// } +// } else { for (i = 0; i < count; i++) { (*glyph_info)[i].index = glyphs[i].index; (*glyph_info)[i].x_offset = glyphs[i].x_offset; @@ -504,10 +371,10 @@ text_layout_raqm( (*glyph_info)[i].y_advance = glyphs[i].y_advance; (*glyph_info)[i].cluster = glyphs[i].cluster; } - } +// } failed: - (*p_raqm.destroy)(rq); + raqm_destroy(rq); return count; } @@ -607,9 +474,9 @@ text_layout( int color) { size_t count; - if (p_raqm.raqm && self->layout_engine == LAYOUT_RAQM) { + if (p_fribidi && self->layout_engine == LAYOUT_RAQM) { count = text_layout_raqm( - string, self, dir, features, lang, glyph_info, mask, color); + string, self, dir, features, lang, glyph_info, mask, color); } else { count = text_layout_fallback( string, self, dir, features, lang, glyph_info, mask, color); @@ -1491,12 +1358,14 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "freetype2_version", v); setraqm(); - v = PyBool_FromLong(!!p_raqm.raqm); + v = PyBool_FromLong(!!p_fribidi); PyDict_SetItemString(d, "HAVE_RAQM", v); - if (p_raqm.version_string) { +// if (p_raqm.version_string) { PyDict_SetItemString( - d, "raqm_version", PyUnicode_FromString(p_raqm.version_string())); - } + d, "raqm_version", PyUnicode_FromString(raqm_version_string())); +// }; + + PyDict_SetItemString(d, "HAVE_FRIBIDI", v); return 0; } diff --git a/src/libImaging/raqm.h b/src/libImaging/raqm.h deleted file mode 100644 index 5f865853aa1..00000000000 --- a/src/libImaging/raqm.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright © 2015 Information Technology Authority (ITA) - * Copyright © 2016 Khaled Hosny - * - * 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 _RAQM_H_ -#define _RAQM_H_ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifndef bool -typedef int bool; -#endif -#ifndef uint32_t -typedef UINT32 uint32_t; -#endif -#include -#include FT_FREETYPE_H - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * raqm_t: - * - * This is the main object holding all state of the currently processed text as - * well as its output. - * - * Since: 0.1 - */ -typedef struct _raqm raqm_t; - -/** - * raqm_direction_t: - * @RAQM_DIRECTION_DEFAULT: Detect paragraph direction automatically. - * @RAQM_DIRECTION_RTL: Paragraph is mainly right-to-left text. - * @RAQM_DIRECTION_LTR: Paragraph is mainly left-to-right text. - * @RAQM_DIRECTION_TTB: Paragraph is mainly vertical top-to-bottom text. - * - * Base paragraph direction, see raqm_set_par_direction(). - * - * Since: 0.1 - */ -typedef enum { - RAQM_DIRECTION_DEFAULT, - RAQM_DIRECTION_RTL, - RAQM_DIRECTION_LTR, - RAQM_DIRECTION_TTB -} raqm_direction_t; - -/** - * raqm_glyph_t: - * @index: the index of the glyph in the font file. - * @x_advance: the glyph advance width in horizontal text. - * @y_advance: the glyph advance width in vertical text. - * @x_offset: the horizontal movement of the glyph from the current point. - * @y_offset: the vertical movement of the glyph from the current point. - * @cluster: the index of original character in input text. - * @ftface: the @FT_Face of the glyph. - * - * The structure that holds information about output glyphs, returned from - * raqm_get_glyphs(). - */ -typedef struct raqm_glyph_t { - unsigned int index; - int x_advance; - int y_advance; - int x_offset; - int y_offset; - uint32_t cluster; - FT_Face ftface; -} raqm_glyph_t; - -/** - * version 0.1 of the raqm_glyph_t structure - */ -typedef struct raqm_glyph_t_01 { - unsigned int index; - int x_advance; - int y_advance; - int x_offset; - int y_offset; - uint32_t cluster; -} raqm_glyph_t_01; - -raqm_t * -raqm_create(void); - -raqm_t * -raqm_reference(raqm_t *rq); - -void -raqm_destroy(raqm_t *rq); - -bool -raqm_set_text(raqm_t *rq, const uint32_t *text, size_t len); - -bool -raqm_set_text_utf8(raqm_t *rq, const char *text, size_t len); - -bool -raqm_set_par_direction(raqm_t *rq, raqm_direction_t dir); - -bool -raqm_set_language(raqm_t *rq, const char *lang, size_t start, size_t len); - -bool -raqm_add_font_feature(raqm_t *rq, const char *feature, int len); - -bool -raqm_set_freetype_face(raqm_t *rq, FT_Face face); - -bool -raqm_set_freetype_face_range(raqm_t *rq, FT_Face face, size_t start, size_t len); - -bool -raqm_set_freetype_load_flags(raqm_t *rq, int flags); - -bool -raqm_layout(raqm_t *rq); - -raqm_glyph_t * -raqm_get_glyphs(raqm_t *rq, size_t *length); - -bool -raqm_index_to_position(raqm_t *rq, size_t *index, int *x, int *y); - -bool -raqm_position_to_index(raqm_t *rq, int x, int y, size_t *index); - -#ifdef __cplusplus -} -#endif -#endif /* _RAQM_H_ */ diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c new file mode 100644 index 00000000000..64ff7e11544 --- /dev/null +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -0,0 +1,68 @@ + +#ifndef _WIN32 +#include +#else +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#define FRIBIDI_SHIM_IMPLEMENTATION + +#include "fribidi.h" + +int load_fribidi(void) { + int error = 0; + + p_fribidi = NULL; + + /* Microsoft needs a totally different system */ +#ifndef _WIN32 + p_fribidi = dlopen("libfribidi.so.1", RTLD_LAZY); + if (!p_fribidi) { + p_fribidi = dlopen("libfribidi.dylib", RTLD_LAZY); + } +#else + p_fribidi = LoadLibrary("fribidi"); + /* MSYS2 */ + if (!p_fribidi) { + p_fribidi = LoadLibrary("libfribidi-1"); + } +#endif + + if (!p_fribidi) { + return 1; + } + +#ifndef _WIN32 +#define LOAD_FUNCTION(func) \ + func = (t_##func)dlsym(p_fribidi, #func); \ + error = error || (func == NULL); +#else +#define LOAD_FUNCTION(func) \ + func = (t_##func)GetProcAddress(p_fribidi, #func); \ + error = error || (func == NULL); +#endif + + LOAD_FUNCTION(fribidi_get_bidi_types); + LOAD_FUNCTION(fribidi_get_bracket_types); + LOAD_FUNCTION(fribidi_get_par_embedding_levels_ex); +// LOAD_FUNCTION(fribidi_get_par_embedding_levels); + LOAD_FUNCTION(fribidi_unicode_to_charset); + LOAD_FUNCTION(fribidi_charset_to_unicode); + +#ifndef _WIN32 + if (dlerror() || error) { + dlclose(p_fribidi); + p_fribidi = NULL; + return 2; + } +#else + if (error) { + FreeLibrary(p_fribidi); + p_fribidi = NULL; + return 2; + } +#endif + + return 0; +} diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h new file mode 100644 index 00000000000..c79bb170a0c --- /dev/null +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -0,0 +1,104 @@ + +/* fribidi-types.h */ + +# if defined (_SVR4) || defined (SVR4) || defined (__OpenBSD__) || \ + defined (_sgi) || defined (__sun) || defined (sun) || \ + defined (__digital__) || defined (__HP_cc) +# include +# elif defined (_AIX) +# include +# else +# include +# endif + +typedef uint32_t FriBidiChar; +typedef int FriBidiStrIndex; + +typedef FriBidiChar FriBidiBracketType; + + + +/* fribidi-char-sets.h */ + +typedef enum +{ + _FRIBIDI_CHAR_SET_NOT_FOUND, + FRIBIDI_CHAR_SET_UTF8, + FRIBIDI_CHAR_SET_CAP_RTL, + FRIBIDI_CHAR_SET_ISO8859_6, + FRIBIDI_CHAR_SET_ISO8859_8, + FRIBIDI_CHAR_SET_CP1255, + FRIBIDI_CHAR_SET_CP1256, + _FRIBIDI_CHAR_SETS_NUM_PLUS_ONE +} +FriBidiCharSet; + + + +/* fribidi-bidi-types.h */ + +typedef signed char FriBidiLevel; + +#define FRIBIDI_TYPE_LTR_VAL 0x00000110L +#define FRIBIDI_TYPE_RTL_VAL 0x00000111L +#define FRIBIDI_TYPE_ON_VAL 0x00000040L + +typedef uint32_t FriBidiCharType; +#define FRIBIDI_TYPE_LTR FRIBIDI_TYPE_LTR_VAL + +typedef uint32_t FriBidiParType; +#define FRIBIDI_PAR_LTR FRIBIDI_TYPE_LTR_VAL +#define FRIBIDI_PAR_RTL FRIBIDI_TYPE_RTL_VAL +#define FRIBIDI_PAR_ON FRIBIDI_TYPE_ON_VAL + +#define FRIBIDI_LEVEL_IS_RTL(lev) ((lev) & 1) +#define FRIBIDI_DIR_TO_LEVEL(dir) ((FriBidiLevel) (FRIBIDI_IS_RTL(dir) ? 1 : 0)) +#define FRIBIDI_IS_RTL(p) ((p) & 0x00000001L) +#define FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS(p) ((p) & 0x00901000L) + + + +/* functions */ + +#ifdef FRIBIDI_SHIM_IMPLEMENTATION +#define FRIBIDI_ENTRY +#else +#define FRIBIDI_ENTRY extern +#endif + +#define FRIBIDI_FUNC(ret, name, ...) \ + typedef ret (*t_##name) (__VA_ARGS__); \ + FRIBIDI_ENTRY t_##name name; + +FRIBIDI_FUNC(void, fribidi_get_bidi_types, + const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *); + +FRIBIDI_FUNC(void, fribidi_get_bracket_types, + const FriBidiChar *, const FriBidiStrIndex, const FriBidiCharType *, + FriBidiBracketType *); + +FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex, + const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex, + FriBidiParType *, FriBidiLevel *); + +//FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, +// const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, +// FriBidiLevel *); + +FRIBIDI_FUNC(FriBidiStrIndex, fribidi_unicode_to_charset, + FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); + +FRIBIDI_FUNC(FriBidiStrIndex, fribidi_charset_to_unicode, + FriBidiCharSet, const char *, FriBidiStrIndex, FriBidiChar *); + +#undef FRIBIDI_FUNC + + + +/* shim */ + +FRIBIDI_ENTRY void *p_fribidi; + +FRIBIDI_ENTRY int load_fribidi(void); + +#undef FRIBIDI_ENTRY diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index 27e59b5fc20..c796f645e3a 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -30,15 +30,16 @@ #include #include -#include +#include "../fribidi-shim/fribidi.h" + #include #include #include "raqm.h" -#if FRIBIDI_MAJOR_VERSION >= 1 +//#if FRIBIDI_MAJOR_VERSION >= 1 #define USE_FRIBIDI_EX_API -#endif +//#endif /** * SECTION:raqm diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 2531d550480..fd63f4f1efb 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -296,21 +296,7 @@ def cmd_msbuild( cmd_nmake(target="clean"), cmd_nmake(target="fribidi"), ], - "headers": [r"lib\*.h"], - "libs": [r"*.lib"], - }, - "libraqm": { - "url": "https://github.com/HOST-Oman/libraqm/archive/v0.7.1.zip", - "filename": "libraqm-0.7.1.zip", - "dir": "libraqm-0.7.1", - "build": [ - cmd_copy(r"{winbuild_dir}\raqm.cmake", r"CMakeLists.txt"), - cmd_cmake(), - cmd_nmake(target="clean"), - cmd_nmake(target="libraqm"), - ], - "headers": [r"src\*.h"], - "bins": [r"libraqm.dll"], + "bins": [r"*.dll"], }, } @@ -511,8 +497,8 @@ def build_pillow(): verbose = True elif arg == "--no-imagequant": disabled += ["libimagequant"] - elif arg == "--no-raqm": - disabled += ["fribidi", "libraqm"] + elif arg == "--no-raqm" or arg == "--no-fribidi": + disabled += ["fribidi"] elif arg.startswith("--depends="): depends_dir = arg[10:] elif arg.startswith("--python="): diff --git a/winbuild/fribidi.cmake b/winbuild/fribidi.cmake index 47ab2c329e8..acb614bfa1b 100644 --- a/winbuild/fribidi.cmake +++ b/winbuild/fribidi.cmake @@ -93,10 +93,10 @@ fribidi_tab(brackets-type unidata/BidiBrackets.txt) file(GLOB FRIBIDI_SOURCES lib/*.c) file(GLOB FRIBIDI_HEADERS lib/*.h) -add_library(fribidi STATIC +add_library(fribidi SHARED ${FRIBIDI_SOURCES} ${FRIBIDI_HEADERS} ${FRIBIDI_SOURCES_GENERATED}) fribidi_definitions(fribidi) target_compile_definitions(fribidi - PUBLIC -DFRIBIDI_LIB_STATIC) + PUBLIC "-DFRIBIDI_ENTRY=__declspec(dllexport)") From 9e5fc136b90e86a4cfbd437455cbe60e4aeeba4c Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 11:23:45 +0000 Subject: [PATCH 03/21] add Raqm license and readme --- src/thirdparty/raqm/AUTHORS | 9 ++++ src/thirdparty/raqm/COPYING | 22 +++++++++ src/thirdparty/raqm/NEWS | 89 +++++++++++++++++++++++++++++++++++++ src/thirdparty/raqm/README | 85 +++++++++++++++++++++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 src/thirdparty/raqm/AUTHORS create mode 100644 src/thirdparty/raqm/COPYING create mode 100644 src/thirdparty/raqm/NEWS create mode 100644 src/thirdparty/raqm/README diff --git a/src/thirdparty/raqm/AUTHORS b/src/thirdparty/raqm/AUTHORS new file mode 100644 index 00000000000..bd5c3ac6b6a --- /dev/null +++ b/src/thirdparty/raqm/AUTHORS @@ -0,0 +1,9 @@ +Abderraouf Adjal +Ali Yousuf +Anood Almuharbi +Asma Albahanta +Fahad Alsaidi +Ibtisam Almabsali +Khaled Hosny +Mazoon Almaamari +Shamsa Alqassabi diff --git a/src/thirdparty/raqm/COPYING b/src/thirdparty/raqm/COPYING new file mode 100644 index 00000000000..196511ef688 --- /dev/null +++ b/src/thirdparty/raqm/COPYING @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright © 2015 Information Technology Authority (ITA) +Copyright © 2016 Khaled Hosny + +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. diff --git a/src/thirdparty/raqm/NEWS b/src/thirdparty/raqm/NEWS new file mode 100644 index 00000000000..29c9ae0e5a5 --- /dev/null +++ b/src/thirdparty/raqm/NEWS @@ -0,0 +1,89 @@ +Overview of changes leading to 0.7.1 +Sunday, November 22, 2020 +==================================== + +Require HarfBuzz >= 2.0.0 + +Build and documentation fixes. + +Overview of changes leading to 0.7.0 +Monday, May 27, 2019 +==================================== + +New API: + * raqm_version + * raqm_version_string + * raqm_version_atleast + * RAQM_VERSION_MAJOR + * RAQM_VERSION_MICRO + * RAQM_VERSION_MINOR + * RAQM_VERSION_STRING + * RAQM_VERSION_ATLEAST + +Overview of changes leading to 0.6.0 +Sunday, May 5, 2019 +==================================== + +Fix TTB direction regression from the previous release. + +Correctly detect script of Common and Inherite characters at start of text. + +Undef HAVE_CONFIG_H workaround, for older versions of Fribidi. + +Drop test suite dependency on GLib. + +Port test runner to Python instead of shell script. + +New API: +* raqm_set_invisible_glyph() + +Overview of changes leading to 0.5.0 +Saturday, February 24, 2018 +==================================== + +Use FriBiDi 1.x API when available. + +Overview of changes leading to 0.4.0 +Sunday, January 21, 2018 +==================================== + +Set begin-of-text and end-of-text HarfBuzz buffer flags. + +Dynamically allocate memory instead of using stack allocation for input text. + +Accept zero length text and do nothing instead of treating it as error. + +Overview of changes leading to 0.3.0 +Monday, August 21, 2017 +==================================== + +Fix stack corruption on MSVC. + +New API: +* raqm_set_freetype_load_flags + +Overview of changes leading to 0.2.0 +Wednesday, August 25, 2016 +==================================== + +Fix building with MSVC due to lacking C99 support. + +Make multiple fonts support actually work. Start and length now respect the +input encoding. + +New API: +* raqm_index_to_position +* raqm_position_to_index +* raqm_set_language + +Overview of changes leading to 0.1.1 +Sunday, May 1, 2016 +==================================== + +Fix make check on 32-bit systems. + +Overview of changes leading to 0.1.0 +Wednesday, January 20, 2016 +==================================== + +First release. diff --git a/src/thirdparty/raqm/README b/src/thirdparty/raqm/README new file mode 100644 index 00000000000..7940bf3b660 --- /dev/null +++ b/src/thirdparty/raqm/README @@ -0,0 +1,85 @@ +Raqm +==== + +[![Linux & macOS build](https://travis-ci.org/HOST-Oman/libraqm.svg?branch=master)](https://travis-ci.org/HOST-Oman/libraqm) +[![Windows build](https://img.shields.io/appveyor/ci/HOSTOman/libraqm/master.svg)](https://ci.appveyor.com/project/HOSTOman/libraqm) + +Raqm is a small library that encapsulates the logic for complex text layout and +provides a convenient API. + +It currently provides bidirectional text support (using [FriBiDi][1]), shaping +(using [HarfBuzz][2]), and proper script itemization. As a result, +Raqm can support most writing systems covered by Unicode. + +The documentation can be accessed on the web at: +> http://host-oman.github.io/libraqm/ + +Raqm (Arabic: رَقْم) is writing, also number or digit and the Arabic word for +digital (رَقَمِيّ) shares the same root, so it is a play on “digital writing”. + +Building +-------- + +Raqm depends on the following libraries: +* [FreeType][3] +* [HarfBuzz][2] +* [FriBiDi][1] + +To build the documentation you will also need: +* [GTK-Doc][4] + +To install dependencies on Fedora: + + sudo dnf install freetype-devel harfbuzz-devel fribidi-devel gtk-doc + +To install dependencies on Ubuntu: + + sudo apt-get install libfreetype6-dev libharfbuzz-dev libfribidi-dev \ + gtk-doc-tools + +On Mac OS X you can use Homebrew: + + brew install freetype harfbuzz fribidi gtk-doc + export XML_CATALOG_FILES="/usr/local/etc/xml/catalog" # for the docs + +Once you have the source code and the dependencies, you can proceed to build. +To do that, run the customary sequence of commands in the source code +directory: + + $ ./configure + $ make + $ make install + +To build the documentation, pass `--enable-gtk-doc` to the `configure` script. + +To run the tests: + + $ make check + +Contributing +------------ + +Once you have made a change that you are happy with, contribute it back, we’ll +be happy to integrate it! Just fork the repository and make a pull request. + +Projects using Raqm +------------------- + +1. [ImageMagick](https://github.com/ImageMagick/ImageMagick) +2. [LibGD](https://github.com/libgd/libgd) +3. [FontView](https://github.com/googlei18n/fontview) +4. [Pillow](https://github.com/python-pillow) +5. [mplcairo](https://github.com/anntzer/mplcairo) + +The following projects have patches to support complex text layout using Raqm: + +2. SDL_ttf: https://bugzilla.libsdl.org/show_bug.cgi?id=3211 +3. Pygame: https://bitbucket.org/pygame/pygame/pull-requests/52 +4. Blender: https://developer.blender.org/D1809 + + + +[1]: http://fribidi.org +[2]: http://harfbuzz.org +[3]: https://www.freetype.org +[4]: https://www.gtk.org/gtk-doc From 5cd688fc82e875de25979af800642f905cb92cb3 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 14:01:16 +0000 Subject: [PATCH 04/21] add option to statically link fribidi, version info --- setup.py | 94 ++++++++++++++++++++++----- src/PIL/features.py | 7 ++ src/_imagingft.c | 74 ++++++++++++++++----- src/thirdparty/fribidi-shim/fribidi.c | 6 +- src/thirdparty/fribidi-shim/fribidi.h | 5 ++ src/thirdparty/raqm/raqm.c | 8 ++- 6 files changed, 157 insertions(+), 37 deletions(-) diff --git a/setup.py b/setup.py index 3e0ec557676..0afc6330c6b 100755 --- a/setup.py +++ b/setup.py @@ -267,7 +267,7 @@ class feature: "jpeg", "tiff", "freetype", - "harfbuzz", + "raqm", "lcms", "webp", "webpmux", @@ -277,6 +277,7 @@ class feature: ] required = {"jpeg", "zlib"} + system = set() def __init__(self): for f in self.features: @@ -288,6 +289,9 @@ def require(self, feat): def want(self, feat): return getattr(self, feat) is None + def want_system(self, feat): + return feat in self.system + def __iter__(self): yield from self.features @@ -297,6 +301,10 @@ def __iter__(self): build_ext.user_options + [(f"disable-{x}", None, f"Disable support for {x}") for x in feature] + [(f"enable-{x}", None, f"Enable support for {x}") for x in feature] + + [ + (f"system-{x}", None, f"Use system version of {x}") + for x in ("raqm", "fribidi") + ] + [ ("disable-platform-guessing", None, "Disable platform guessing on Linux"), ("debug", None, "Debug logging"), @@ -311,6 +319,8 @@ def initialize_options(self): for x in self.feature: setattr(self, f"disable_{x}", None) setattr(self, f"enable_{x}", None) + for x in ("raqm", "fribidi"): + setattr(self, f"system_{x}", None) def finalize_options(self): build_ext.finalize_options(self) @@ -335,18 +345,40 @@ def finalize_options(self): raise ValueError( f"Conflicting options: --enable-{x} and --disable-{x}" ) + if x == "freetype": + _dbg("--disable-freetype implies --disable-raqm") + if getattr(self, "enable_raqm"): + raise ValueError( + "Conflicting options: --enable-raqm and --disable-freetype" + ) + setattr(self, "disable_raqm", True) if getattr(self, f"enable_{x}"): _dbg("Requiring %s", x) self.feature.required.add(x) + if x == "raqm": + _dbg("--enable-raqm implies --enable-freetype") + self.feature.required.add("freetype") + for x in ("raqm", "fribidi"): + if getattr(self, f"system_{x}"): + if getattr(self, f"disable_raqm"): + raise ValueError( + f"Conflicting options: --system-{x} and --disable-raqm" + ) + if x == "fribidi" and getattr(self, f"system_raqm"): + raise ValueError( + f"Conflicting options: --system-{x} and --system-raqm" + ) + _dbg("Using system version of %s", x) + self.feature.system.add(x) - def _update_extension(self, name, libraries, define_macros=None, include_dirs=None): + def _update_extension(self, name, libraries, define_macros=None, sources=None): for extension in self.extensions: if extension.name == name: extension.libraries += libraries if define_macros is not None: extension.define_macros += define_macros - if include_dirs is not None: - extension.include_dirs += include_dirs + if sources is not None: + extension.sources += sources break def _remove_extension(self, name): @@ -657,11 +689,27 @@ def build_extensions(self): if subdir: _add_directory(self.compiler.include_dirs, subdir, 0) - if feature.want("harfbuzz"): - _dbg("Looking for harfbuzz") - if _find_include_file(self, "hb-version.h"): - if _find_library_file(self, "harfbuzz"): - feature.harfbuzz = "harfbuzz" + if feature.want("raqm"): + if feature.want_system("raqm"): # want system Raqm + _dbg("Looking for Raqm") + if _find_include_file(self, "raqm.h"): + if _find_library_file(self, "raqm"): + feature.harfbuzz = "raqm" + elif _find_library_file(self, "libraqm"): + feature.harfbuzz = "libraqm" + else: # want to build Raqm + _dbg("Looking for HarfBuzz") + if _find_include_file(self, "hb.h"): + if _find_library_file(self, "harfbuzz"): + feature.harfbuzz = "harfbuzz" + if feature.harfbuzz: + if feature.want_system("fribidi"): # want system FriBiDi + _dbg("Looking for FriBiDi") + if _find_include_file(self, "fribidi.h"): + if _find_library_file(self, "fribidi"): + feature.harfbuzz = "fribidi" + else: # want to build FriBiDi shim + feature.raqm = True if feature.want("lcms"): _dbg("Looking for lcms") @@ -758,9 +806,25 @@ def build_extensions(self): # additional libraries if feature.freetype: + srcs = [] libs = ["freetype"] defs = [] - self._update_extension("PIL._imagingft", libs, defs) + if feature.raqm: + if feature.want_system("raqm"): # using system Raqm + defs.append(("HAVE_RAQM", None)) + defs.append(("HAVE_RAQM_SYSTEM", None)) + libs.append(feature.raqm) + else: # building Raqm + defs.append(("HAVE_RAQM", None)) + srcs.append("src/thirdparty/raqm/raqm.c") + libs.append(feature.harfbuzz) + if feature.want_system("fribidi"): # using system FriBiDi + defs.append(("HAVE_FRIBIDI_SYSTEM", None)) + libs.append(feature.fribidi) + else: # building our FriBiDi shim + srcs.append("src/thirdparty/fribidi-shim/fribidi.c") + self._update_extension("PIL._imagingft", libs, defs, srcs) + else: self._remove_extension("PIL._imagingft") @@ -814,6 +878,7 @@ def summary_report(self, feature): (feature.imagequant, "LIBIMAGEQUANT"), (feature.tiff, "LIBTIFF"), (feature.freetype, "FREETYPE2"), + (feature.raqm, "RAQM (Text shaping)"), # TODO!!! (feature.lcms, "LITTLECMS2"), (feature.webp, "WEBP"), (feature.webpmux, "WEBPMUX"), @@ -857,14 +922,7 @@ def debug_build(): files.append(os.path.join("src/libImaging", src_file + ".c")) ext_modules = [ Extension("PIL._imaging", files), - Extension( - "PIL._imagingft", - [ - "src/_imagingft.c", - "src/thirdparty/raqm/raqm.c", - "src/thirdparty/fribidi-shim/fribidi.c", - ], - ), + Extension("PIL._imagingft", ["src/_imagingft.c"]), Extension("PIL._imagingcms", ["src/_imagingcms.c"]), Extension("PIL._webp", ["src/_webp.c"]), Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), diff --git a/src/PIL/features.py b/src/PIL/features.py index da0ca557cf2..85459063bfa 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -118,6 +118,8 @@ def get_supported_codecs(): "webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None), "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None), "raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"), + "fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"), + "harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"), "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"), "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"), "xcb": ("PIL._imaging", "HAVE_XCB", None), @@ -274,6 +276,11 @@ def pilinfo(out=None, supported_formats=True): # this check is also in src/_imagingcms.c:setup_module() version_static = tuple(int(x) for x in v.split(".")) < (2, 7) t = "compiled for" if version_static else "loaded" + if name == "raqm": + for f in ("fribidi", "harfbuzz"): + v2 = version_feature(f) + if v2 is not None: + v += f", {f} {v2}" print("---", feature, "support ok,", t, v, file=out) else: print("---", feature, "support ok", file=out) diff --git a/src/_imagingft.c b/src/_imagingft.c index 4a4084e9fd9..fd5530642a0 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -52,8 +52,21 @@ } \ ; -#include "thirdparty/raqm/raqm.h" -#include "thirdparty/fribidi-shim/fribidi.h" +#ifdef HAVE_RAQM +# ifdef HAVE_RAQM_SYSTEM +# include +# else +# include "thirdparty/raqm/raqm.h" +# ifdef HAVE_FRIBIDI_SYSTEM +# include +# else +# include "thirdparty/fribidi-shim/fribidi.h" +# include +# endif +# endif +#endif + +static int have_raqm = 0; #define LAYOUT_FALLBACK 0 #define LAYOUT_RAQM 1 @@ -101,11 +114,6 @@ geterror(int code) { return NULL; } -static int -setraqm(void) { - return load_fribidi(); -} - static PyObject * getfont(PyObject *self_, PyObject *args, PyObject *kw) { /* create a font object from a file name and a size (in pixels) */ @@ -474,7 +482,7 @@ text_layout( int color) { size_t count; - if (p_fribidi && self->layout_engine == LAYOUT_RAQM) { + if (have_raqm && self->layout_engine == LAYOUT_RAQM) { count = text_layout_raqm( string, self, dir, features, lang, glyph_info, mask, color); } else { @@ -1357,15 +1365,51 @@ setup_module(PyObject *m) { v = PyUnicode_FromFormat("%d.%d.%d", major, minor, patch); PyDict_SetItemString(d, "freetype2_version", v); - setraqm(); - v = PyBool_FromLong(!!p_fribidi); - PyDict_SetItemString(d, "HAVE_RAQM", v); -// if (p_raqm.version_string) { - PyDict_SetItemString( - d, "raqm_version", PyUnicode_FromString(raqm_version_string())); -// }; +#ifdef HAVE_RAQM +#ifdef HAVE_FRIBIDI_SYSTEM + have_raqm = 1; +#else + load_fribidi(); + have_raqm = !!p_fribidi; +#endif +#else + have_raqm = 0; +#endif + /* if we have Raqm, we have all three (but possibly no version info) */ + v = PyBool_FromLong(have_raqm); + PyDict_SetItemString(d, "HAVE_RAQM", v); PyDict_SetItemString(d, "HAVE_FRIBIDI", v); + PyDict_SetItemString(d, "HAVE_HARFBUZZ", v); + if (have_raqm) { + const char *a, *b; +#ifdef RAQM_VERSION_MAJOR + v = PyUnicode_FromString(raqm_version_string()); +#else + v = Py_None; +#endif + PyDict_SetItemString(d, "raqm_version", v); + +#ifdef FRIBIDI_MAJOR_VERSION + a = strchr(fribidi_version_info, '1'); + b = strchr(fribidi_version_info, '\n'); + if (a && b) { + v = PyUnicode_FromStringAndSize(a, b - a); + } else { + v = Py_None; + } +#else + v = Py_None; +#endif + PyDict_SetItemString(d, "fribidi_version", v); + +#ifdef HB_VERSION_STRING + v = PyUnicode_FromString(hb_version_string()); +#else + v = Py_None; +#endif + PyDict_SetItemString(d, "harfbuzz_version", v); + } return 0; } diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index 64ff7e11544..77a55b502a0 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -51,13 +51,15 @@ int load_fribidi(void) { LOAD_FUNCTION(fribidi_charset_to_unicode); #ifndef _WIN32 - if (dlerror() || error) { + fribidi_version_info = *(const char**)dlsym(p_fribidi, "fribidi_version_info"); + if (dlerror() || error || (fribidi_version_info == NULL)) { dlclose(p_fribidi); p_fribidi = NULL; return 2; } #else - if (error) { + fribidi_version_info = *(const char**)GetProcAddress(p_fribidi, "fribidi_version_info"); + if (error || (fribidi_version_info == NULL)) { FreeLibrary(p_fribidi); p_fribidi = NULL; return 2; diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h index c79bb170a0c..b7c6064bce1 100644 --- a/src/thirdparty/fribidi-shim/fribidi.h +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -1,4 +1,6 @@ +#define FRIBIDI_MAJOR_VERSION 1 + /* fribidi-types.h */ # if defined (_SVR4) || defined (SVR4) || defined (__OpenBSD__) || \ @@ -93,6 +95,9 @@ FRIBIDI_FUNC(FriBidiStrIndex, fribidi_charset_to_unicode, #undef FRIBIDI_FUNC +/* constant, not a function */ +FRIBIDI_ENTRY const char *fribidi_version_info; + /* shim */ diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index c796f645e3a..5a0b2078eae 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -30,16 +30,20 @@ #include #include +#ifdef HAVE_FRIBIDI_SYSTEM +#include +#else #include "../fribidi-shim/fribidi.h" +#endif #include #include #include "raqm.h" -//#if FRIBIDI_MAJOR_VERSION >= 1 +#if FRIBIDI_MAJOR_VERSION >= 1 #define USE_FRIBIDI_EX_API -//#endif +#endif /** * SECTION:raqm From d4403bec46a22d0b8cb8a8fde816519effbc4f2a Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 14:02:21 +0000 Subject: [PATCH 05/21] GHA: fix windows build for dynamic fribidi --- .github/workflows/test-windows.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index db16751355e..c5aa133cb26 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -137,14 +137,11 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_harfbuzz.cmd" + # Raqm dependencies - name: Build dependencies / FriBidi if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_fribidi.cmd" - - name: Build dependencies / Raqm - if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_libraqm.cmd" - # trim ~150MB x 9 - name: Optimize build cache if: steps.build-cache.outputs.cache-hit != 'true' From 3386a9ce0272d92c1c1c20037c60022aa4e09ea4 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 15:18:43 +0000 Subject: [PATCH 06/21] replace tabs in thirdparty libs --- src/thirdparty/fribidi-shim/fribidi.h | 2 +- src/thirdparty/raqm/raqm-version.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h index b7c6064bce1..aa446fbef94 100644 --- a/src/thirdparty/fribidi-shim/fribidi.h +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -43,7 +43,7 @@ typedef signed char FriBidiLevel; #define FRIBIDI_TYPE_LTR_VAL 0x00000110L #define FRIBIDI_TYPE_RTL_VAL 0x00000111L -#define FRIBIDI_TYPE_ON_VAL 0x00000040L +#define FRIBIDI_TYPE_ON_VAL 0x00000040L typedef uint32_t FriBidiCharType; #define FRIBIDI_TYPE_LTR FRIBIDI_TYPE_LTR_VAL diff --git a/src/thirdparty/raqm/raqm-version.h b/src/thirdparty/raqm/raqm-version.h index 4fd5c684287..94b25ada7e8 100644 --- a/src/thirdparty/raqm/raqm-version.h +++ b/src/thirdparty/raqm/raqm-version.h @@ -38,7 +38,7 @@ #define RAQM_VERSION_STRING "0.7.1" #define RAQM_VERSION_ATLEAST(major,minor,micro) \ - ((major)*10000+(minor)*100+(micro) <= \ - RAQM_VERSION_MAJOR*10000+RAQM_VERSION_MINOR*100+RAQM_VERSION_MICRO) + ((major)*10000+(minor)*100+(micro) <= \ + RAQM_VERSION_MAJOR*10000+RAQM_VERSION_MINOR*100+RAQM_VERSION_MICRO) #endif /* _RAQM_VERSION_H_ */ From be0d0a3a4895aeef5504a78440cd08dbee16f99c Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 16:27:12 +0000 Subject: [PATCH 07/21] fix finding raqm deps --- setup.py | 38 ++++++++++++++++++++++----- src/_imagingft.c | 12 ++++++--- src/thirdparty/fribidi-shim/fribidi.c | 2 +- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index 0afc6330c6b..7275dc6ed0d 100755 --- a/setup.py +++ b/setup.py @@ -123,7 +123,7 @@ def get_version(): "codec_fd", ) -DEBUG = False +DEBUG = True class DependencyException(Exception): @@ -228,6 +228,19 @@ def _find_library_file(self, library): return ret +def _find_include_dir(self, dirname, include): + for directory in self.compiler.include_dirs: + _dbg("Checking for include file %s in %s", (include, directory)) + if os.path.isfile(os.path.join(directory, include)): + _dbg("Found %s in %s", (include, directory)) + return True + subdir = os.path.join(directory, dirname) + _dbg("Checking for include file %s in %s", (include, subdir)) + if os.path.isfile(os.path.join(subdir, include)): + _dbg("Found %s in %s", (include, subdir)) + return subdir + + def _cmd_exists(cmd): return any( os.access(os.path.join(path, cmd), os.X_OK) @@ -689,25 +702,36 @@ def build_extensions(self): if subdir: _add_directory(self.compiler.include_dirs, subdir, 0) - if feature.want("raqm"): + if feature.freetype and feature.want("raqm"): if feature.want_system("raqm"): # want system Raqm _dbg("Looking for Raqm") if _find_include_file(self, "raqm.h"): if _find_library_file(self, "raqm"): - feature.harfbuzz = "raqm" + feature.raqm = "raqm" elif _find_library_file(self, "libraqm"): - feature.harfbuzz = "libraqm" + feature.raqm = "libraqm" else: # want to build Raqm _dbg("Looking for HarfBuzz") - if _find_include_file(self, "hb.h"): + feature.harfbuzz = None + hb_dir = _find_include_dir(self, "harfbuzz", "hb.h") + if hb_dir: + if isinstance(hb_dir, str): + _add_directory(self.compiler.include_dirs, hb_dir, 0) if _find_library_file(self, "harfbuzz"): feature.harfbuzz = "harfbuzz" if feature.harfbuzz: if feature.want_system("fribidi"): # want system FriBiDi _dbg("Looking for FriBiDi") - if _find_include_file(self, "fribidi.h"): + feature.fribidi = None + fribidi_dir = _find_include_dir(self, "fribidi", "fribidi.h") + if fribidi_dir: + if isinstance(fribidi_dir, str): + _add_directory( + self.compiler.include_dirs, fribidi_dir, 0 + ) if _find_library_file(self, "fribidi"): - feature.harfbuzz = "fribidi" + feature.fribidi = "fribidi" + feature.raqm = True else: # want to build FriBiDi shim feature.raqm = True diff --git a/src/_imagingft.c b/src/_imagingft.c index fd5530642a0..b2cf76ce798 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -221,6 +221,8 @@ font_getchar(PyObject *string, int index, FT_ULong *char_out) { return 0; } +#ifdef HAVE_RAQM + static size_t text_layout_raqm( PyObject *string, @@ -386,6 +388,8 @@ text_layout_raqm( return count; } +#endif + static size_t text_layout_fallback( PyObject *string, @@ -481,11 +485,13 @@ text_layout( int mask, int color) { size_t count; - +#ifdef HAVE_RAQM if (have_raqm && self->layout_engine == LAYOUT_RAQM) { count = text_layout_raqm( string, self, dir, features, lang, glyph_info, mask, color); - } else { + } else +#endif + { count = text_layout_fallback( string, self, dir, features, lang, glyph_info, mask, color); } @@ -1366,7 +1372,7 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "freetype2_version", v); #ifdef HAVE_RAQM -#ifdef HAVE_FRIBIDI_SYSTEM +#if defined(HAVE_RAQM_SYSTEM) || defined(HAVE_FRIBIDI_SYSTEM) have_raqm = 1; #else load_fribidi(); diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index 77a55b502a0..c83159e2905 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -25,7 +25,7 @@ int load_fribidi(void) { p_fribidi = LoadLibrary("fribidi"); /* MSYS2 */ if (!p_fribidi) { - p_fribidi = LoadLibrary("libfribidi-1"); + p_fribidi = LoadLibrary("libfribidi-0"); } #endif From 834c2e5e5dea378caf5603f58f0dcd476112cf6f Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 16:29:43 +0000 Subject: [PATCH 08/21] lint --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 7275dc6ed0d..cb4ec2da8b1 100755 --- a/setup.py +++ b/setup.py @@ -373,11 +373,11 @@ def finalize_options(self): self.feature.required.add("freetype") for x in ("raqm", "fribidi"): if getattr(self, f"system_{x}"): - if getattr(self, f"disable_raqm"): + if getattr(self, "disable_raqm"): raise ValueError( f"Conflicting options: --system-{x} and --disable-raqm" ) - if x == "fribidi" and getattr(self, f"system_raqm"): + if x == "fribidi" and getattr(self, "system_raqm"): raise ValueError( f"Conflicting options: --system-{x} and --system-raqm" ) From c3fce854f2e227e37036b96980ae00934db483a2 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 16:32:37 +0000 Subject: [PATCH 09/21] avoid NULL in fribidi shim --- src/thirdparty/fribidi-shim/fribidi.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index c83159e2905..f23741ecd2f 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -13,7 +13,7 @@ int load_fribidi(void) { int error = 0; - p_fribidi = NULL; + p_fribidi = 0; /* Microsoft needs a totally different system */ #ifndef _WIN32 @@ -36,11 +36,11 @@ int load_fribidi(void) { #ifndef _WIN32 #define LOAD_FUNCTION(func) \ func = (t_##func)dlsym(p_fribidi, #func); \ - error = error || (func == NULL); + error = error || (func == 0); #else #define LOAD_FUNCTION(func) \ func = (t_##func)GetProcAddress(p_fribidi, #func); \ - error = error || (func == NULL); + error = error || (func == 0); #endif LOAD_FUNCTION(fribidi_get_bidi_types); @@ -52,16 +52,16 @@ int load_fribidi(void) { #ifndef _WIN32 fribidi_version_info = *(const char**)dlsym(p_fribidi, "fribidi_version_info"); - if (dlerror() || error || (fribidi_version_info == NULL)) { + if (dlerror() || error || (fribidi_version_info == 0)) { dlclose(p_fribidi); - p_fribidi = NULL; + p_fribidi = 0; return 2; } #else fribidi_version_info = *(const char**)GetProcAddress(p_fribidi, "fribidi_version_info"); - if (error || (fribidi_version_info == NULL)) { + if (error || (fribidi_version_info == 0)) { FreeLibrary(p_fribidi); - p_fribidi = NULL; + p_fribidi = 0; return 2; } #endif From f2b2d53ca82ea2ca882329d382d2812bf7818485 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 18:01:49 +0000 Subject: [PATCH 10/21] raqm: avoid declaring variables in for statement for C89 compatibility --- src/thirdparty/raqm/raqm.c | 127 +++++++++++++++++++++---------------- 1 file changed, 74 insertions(+), 53 deletions(-) diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index 5a0b2078eae..96523ffb949 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -220,6 +220,7 @@ static bool _raqm_init_text_info (raqm_t *rq) { hb_language_t default_lang; + size_t i; if (rq->text_info) return true; @@ -229,7 +230,7 @@ _raqm_init_text_info (raqm_t *rq) return false; default_lang = hb_language_get_default (); - for (size_t i = 0; i < rq->text_len; i++) + for (i = 0; i < rq->text_len; i++) { rq->text_info[i].ftface = NULL; rq->text_info[i].lang = default_lang; @@ -242,10 +243,12 @@ _raqm_init_text_info (raqm_t *rq) static void _raqm_free_text_info (raqm_t *rq) { + size_t i; + if (!rq->text_info) return; - for (size_t i = 0; i < rq->text_len; i++) + for (i = 0; i < rq->text_len; i++) { if (rq->text_info[i].ftface) FT_Done_Face (rq->text_info[i].ftface); @@ -551,6 +554,7 @@ raqm_set_language (raqm_t *rq, size_t len) { hb_language_t language; + size_t i; size_t end = start + len; if (!rq) @@ -572,7 +576,7 @@ raqm_set_language (raqm_t *rq, return false; language = hb_language_from_string (lang, -1); - for (size_t i = start; i < end; i++) + for (i = start; i < end; i++) { rq->text_info[i].lang = language; } @@ -646,6 +650,8 @@ _raqm_set_freetype_face (raqm_t *rq, size_t start, size_t end) { + size_t i; + if (!rq) return false; @@ -658,7 +664,7 @@ _raqm_set_freetype_face (raqm_t *rq, if (!rq->text_info) return false; - for (size_t i = start; i < end; i++) + for (i = start; i < end; i++) { if (rq->text_info[i].ftface) FT_Done_Face (rq->text_info[i].ftface); @@ -832,6 +838,8 @@ _raqm_shape (raqm_t *rq); bool raqm_layout (raqm_t *rq) { + size_t i; + if (!rq) return false; @@ -841,7 +849,7 @@ raqm_layout (raqm_t *rq) if (!rq->text_info) return false; - for (size_t i = 0; i < rq->text_len; i++) + for (i = 0; i < rq->text_len; i++) { if (!rq->text_info[i].ftface) return false; @@ -879,6 +887,9 @@ raqm_glyph_t * raqm_get_glyphs (raqm_t *rq, size_t *length) { + size_t i; + raqm_run_t *run; + size_t count = 0; if (!rq || !rq->runs || !length) @@ -888,7 +899,7 @@ raqm_get_glyphs (raqm_t *rq, return NULL; } - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) count += hb_buffer_get_length (run->buffer); *length = count; @@ -906,7 +917,7 @@ raqm_get_glyphs (raqm_t *rq, RAQM_TEST ("Glyph information:\n"); count = 0; - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -916,7 +927,7 @@ raqm_get_glyphs (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (size_t i = 0; i < len; i++) + for (i = 0; i < len; i++) { rq->glyphs[count + i].index = info[i].codepoint; rq->glyphs[count + i].cluster = info[i].cluster; @@ -939,18 +950,18 @@ raqm_get_glyphs (raqm_t *rq, { #ifdef RAQM_TESTING RAQM_TEST ("\nUTF-32 clusters:"); - for (size_t i = 0; i < count; i++) + for (i = 0; i < count; i++) RAQM_TEST (" %02d", rq->glyphs[i].cluster); RAQM_TEST ("\n"); #endif - for (size_t i = 0; i < count; i++) + for (i = 0; i < count; i++) rq->glyphs[i].cluster = _raqm_u32_to_u8_index (rq, rq->glyphs[i].cluster); #ifdef RAQM_TESTING RAQM_TEST ("UTF-8 clusters: "); - for (size_t i = 0; i < count; i++) + for (i = 0; i < count; i++) RAQM_TEST (" %02d", rq->glyphs[i].cluster); RAQM_TEST ("\n"); #endif @@ -983,9 +994,11 @@ typedef struct { static void _raqm_reverse_run (_raqm_bidi_run *run, const size_t len) { + size_t i; + assert (run); - for (size_t i = 0; i < len / 2; i++) + for (i = 0; i < len / 2; i++) { _raqm_bidi_run temp = run[i]; run[i] = run[len - 1 - i]; @@ -1002,6 +1015,7 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* output */ size_t *run_count) { + size_t i; FriBidiLevel level; FriBidiLevel last_level = -1; FriBidiLevel max_level = 0; @@ -1021,8 +1035,7 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* L1. Reset the embedding levels of some chars: 4. any sequence of white space characters at the end of the line. */ - for (int i = len - 1; - i >= 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); i--) + for (i = len; i-- > 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); ) { levels[i] = FRIBIDI_DIR_TO_LEVEL (base_dir); } @@ -1030,13 +1043,13 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* Find max_level of the line. We don't reuse the paragraph * max_level, both for a cleaner API, and that the line max_level * may be far less than paragraph max_level. */ - for (int i = len - 1; i >= 0; i--) + for (i = len; i-- > 0; ) { if (levels[i] > max_level) max_level = levels[i]; } - for (size_t i = 0; i < len; i++) + for (i = 0; i < len; i++) { if (levels[i] != last_level) count++; @@ -1064,14 +1077,16 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* L2. Reorder. */ for (level = max_level; level > 0; level--) { - for (int i = count - 1; i >= 0; i--) + for (i = count; i-- > 0; ) { if (runs[i].level >= level) { int end = i; - for (i--; (i >= 0 && runs[i].level >= level); i--) + for (; (i > 0 && runs[i - 1].level >= level); i--) ; - _raqm_reverse_run (runs + i + 1, end - i); + _raqm_reverse_run (runs + i, end - i + 1); + if (i-- == 0) + break; } } } @@ -1083,6 +1098,8 @@ _raqm_reorder_runs (const FriBidiCharType *types, static bool _raqm_itemize (raqm_t *rq) { + size_t i, j; + raqm_run_t *run; FriBidiParType par_type = FRIBIDI_PAR_ON; FriBidiCharType *types; #ifdef USE_FRIBIDI_EX_API @@ -1185,7 +1202,7 @@ _raqm_itemize (raqm_t *rq) RAQM_TEST ("Number of runs before script itemization: %zu\n\n", run_count); RAQM_TEST ("Fribidi Runs:\n"); - for (size_t i = 0; i < run_count; i++) + for (i = 0; i < run_count; i++) { RAQM_TEST ("run[%zu]:\t start: %zu\tlength: %zu\tlevel: %d\n", i, runs[i].pos, runs[i].len, runs[i].level); @@ -1194,7 +1211,7 @@ _raqm_itemize (raqm_t *rq) #endif last = NULL; - for (size_t i = 0; i < run_count; i++) + for (i = 0; i < run_count; i++) { raqm_run_t *run = calloc (1, sizeof (raqm_run_t)); if (!run) @@ -1216,7 +1233,7 @@ _raqm_itemize (raqm_t *rq) run->pos = runs[i].pos + runs[i].len - 1; run->script = rq->text_info[run->pos].script; run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); - for (int j = runs[i].len - 1; j >= 0; j--) + for (j = runs[i].len; j-- > 0; ) { _raqm_text_info info = rq->text_info[runs[i].pos + j]; if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) @@ -1247,7 +1264,7 @@ _raqm_itemize (raqm_t *rq) run->pos = runs[i].pos; run->script = rq->text_info[run->pos].script; run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); - for (size_t j = 0; j < runs[i].len; j++) + for (j = 0; j < runs[i].len; j++) { _raqm_text_info info = rq->text_info[runs[i].pos + j]; if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) @@ -1277,13 +1294,13 @@ _raqm_itemize (raqm_t *rq) #ifdef RAQM_TESTING run_count = 0; - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) run_count++; RAQM_TEST ("Number of runs after script itemization: %zu\n\n", run_count); run_count = 0; RAQM_TEST ("Final Runs:\n"); - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { SCRIPT_TO_STRING (run->script); RAQM_TEST ("run[%zu]:\t start: %d\tlength: %d\tdirection: %s\tscript: %s\tfont: %s\n", @@ -1448,18 +1465,19 @@ _get_pair_index (const FriBidiChar ch) static bool _raqm_resolve_scripts (raqm_t *rq) { - int last_script_index = -1; - int last_set_index = -1; + size_t i, j; + size_t next_script_index = 0; + size_t next_set_index = 0; hb_script_t last_script = HB_SCRIPT_INVALID; _raqm_stack_t *stack = NULL; hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); - for (size_t i = 0; i < rq->text_len; ++i) + for (i = 0; i < rq->text_len; ++i) rq->text_info[i].script = hb_unicode_script (unicode_funcs, rq->text[i]); #ifdef RAQM_TESTING RAQM_TEST ("Before script detection:\n"); - for (size_t i = 0; i < rq->text_len; ++i) + for (i = 0; i < rq->text_len; ++i) { SCRIPT_TO_STRING (rq->text_info[i].script); RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); @@ -1471,9 +1489,9 @@ _raqm_resolve_scripts (raqm_t *rq) if (!stack) return false; - for (int i = 0; i < (int) rq->text_len; i++) + for (i = 0; i < rq->text_len; i++) { - if (rq->text_info[i].script == HB_SCRIPT_COMMON && last_script_index != -1) + if (rq->text_info[i].script == HB_SCRIPT_COMMON && next_script_index != 0) { int pair_index = _get_pair_index (rq->text[i]); if (pair_index >= 0) @@ -1482,7 +1500,7 @@ _raqm_resolve_scripts (raqm_t *rq) { /* is a paired character */ rq->text_info[i].script = last_script; - last_set_index = i; + next_set_index = i + 1; _raqm_stack_push (stack, rq->text_info[i].script, pair_index); } else @@ -1499,34 +1517,34 @@ _raqm_resolve_scripts (raqm_t *rq) { rq->text_info[i].script = _raqm_stack_top (stack); last_script = rq->text_info[i].script; - last_set_index = i; + next_set_index = i + 1; } else { rq->text_info[i].script = last_script; - last_set_index = i; + next_set_index = i + 1; } } } else { rq->text_info[i].script = last_script; - last_set_index = i; + next_set_index = i + 1; } } else if (rq->text_info[i].script == HB_SCRIPT_INHERITED && - last_script_index != -1) + next_script_index != 0) { rq->text_info[i].script = last_script; - last_set_index = i; + next_set_index = i + 1; } else { - for (int j = last_set_index + 1; j < i; ++j) + for (j = next_set_index; j < i; ++j) rq->text_info[j].script = rq->text_info[i].script; last_script = rq->text_info[i].script; - last_script_index = i; - last_set_index = i; + next_script_index = i + 1; + next_set_index = i + 1; } } @@ -1534,7 +1552,7 @@ _raqm_resolve_scripts (raqm_t *rq) * take the script if the next character. * https://github.com/HOST-Oman/libraqm/issues/95 */ - for (int i = rq->text_len - 2; i >= 0; --i) + for (i = rq->text_len - 1; i-- > 0; ) { if (rq->text_info[i].script == HB_SCRIPT_INHERITED || rq->text_info[i].script == HB_SCRIPT_COMMON) @@ -1543,7 +1561,7 @@ _raqm_resolve_scripts (raqm_t *rq) #ifdef RAQM_TESTING RAQM_TEST ("After script detection:\n"); - for (size_t i = 0; i < rq->text_len; ++i) + for (i = 0; i < rq->text_len; ++i) { SCRIPT_TO_STRING (rq->text_info[i].script); RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); @@ -1559,6 +1577,7 @@ _raqm_resolve_scripts (raqm_t *rq) static bool _raqm_shape (raqm_t *rq) { + raqm_run_t *run; hb_buffer_flags_t hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT; #if defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) && \ @@ -1567,7 +1586,7 @@ _raqm_shape (raqm_t *rq) hb_buffer_flags |= HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES; #endif - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { run->buffer = hb_buffer_create (); @@ -1653,6 +1672,8 @@ raqm_index_to_position (raqm_t *rq, int *x, int *y) { + size_t i, j; + raqm_run_t *run; /* We don't currently support multiline, so y is always 0 */ *y = 0; *x = 0; @@ -1676,7 +1697,7 @@ raqm_index_to_position (raqm_t *rq, ++*index; } - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -1685,7 +1706,7 @@ raqm_index_to_position (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (size_t i = 0; i < len; i++) + for (i = 0; i < len; i++) { uint32_t curr_cluster = info[i].cluster; uint32_t next_cluster = curr_cluster; @@ -1693,13 +1714,12 @@ raqm_index_to_position (raqm_t *rq, if (run->direction == HB_DIRECTION_LTR) { - for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + for (j = i + 1; j < len && next_cluster == curr_cluster; j++) next_cluster = info[j].cluster; } else { - for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; - j--) + for (j = i; i != 0 && j-- > 0 && next_cluster == curr_cluster; ) next_cluster = info[j].cluster; } @@ -1745,6 +1765,8 @@ raqm_position_to_index (raqm_t *rq, int y, size_t *index) { + size_t i, j; + raqm_run_t *run; int delta_x = 0, current_x = 0; (void)y; @@ -1762,7 +1784,7 @@ raqm_position_to_index (raqm_t *rq, RAQM_TEST ("\n"); - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + for (run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -1771,7 +1793,7 @@ raqm_position_to_index (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (size_t i = 0; i < len; i++) + for (i = 0; i < len; i++) { delta_x = position[i].x_advance; if (x < (current_x + delta_x)) @@ -1789,11 +1811,10 @@ raqm_position_to_index (raqm_t *rq, uint32_t curr_cluster = info[i].cluster; uint32_t next_cluster = curr_cluster; if (run->direction == HB_DIRECTION_LTR) - for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + for (j = i + 1; j < len && next_cluster == curr_cluster; j++) next_cluster = info[j].cluster; else - for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; - j--) + for (j = i; i != 0 && j-- > 0 && next_cluster == curr_cluster; ) next_cluster = info[j].cluster; if (next_cluster == curr_cluster) From b4a57d6fc5c96749430dd244fa4ce4f7104ab311 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 18:05:50 +0000 Subject: [PATCH 11/21] support FriBiDi<1.0 --- src/thirdparty/fribidi-shim/fribidi.c | 46 ++++++++++++++++++++------- src/thirdparty/fribidi-shim/fribidi.h | 6 ++-- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index f23741ecd2f..20364ea2488 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -10,6 +10,20 @@ #include "fribidi.h" + +/* ..._ex adds bracket_types param, ignore and call legacy function */ +FriBidiLevel fribidi_get_par_embedding_levels_ex_compat( + const FriBidiCharType *bidi_types, + const FriBidiBracketType *bracket_types, + const FriBidiStrIndex len, + FriBidiParType *pbase_dir, + FriBidiLevel *embedding_levels) +{ + return fribidi_get_par_embedding_levels( + bidi_types, len, pbase_dir, embedding_levels); +} + + int load_fribidi(void) { int error = 0; @@ -17,11 +31,22 @@ int load_fribidi(void) { /* Microsoft needs a totally different system */ #ifndef _WIN32 - p_fribidi = dlopen("libfribidi.so.1", RTLD_LAZY); +#define LOAD_FUNCTION(func) \ + func = (t_##func)dlsym(p_fribidi, #func); \ + error = error || (func == 0); + + p_fribidi = dlopen("libfribidi.so", RTLD_LAZY); + if (!p_fribidi) { + p_fribidi = dlopen("libfribidi.so.0", RTLD_LAZY); + } if (!p_fribidi) { p_fribidi = dlopen("libfribidi.dylib", RTLD_LAZY); } #else +#define LOAD_FUNCTION(func) \ + func = (t_##func)GetProcAddress(p_fribidi, #func); \ + error = error || (func == 0); + p_fribidi = LoadLibrary("fribidi"); /* MSYS2 */ if (!p_fribidi) { @@ -33,20 +58,17 @@ int load_fribidi(void) { return 1; } -#ifndef _WIN32 -#define LOAD_FUNCTION(func) \ - func = (t_##func)dlsym(p_fribidi, #func); \ - error = error || (func == 0); -#else -#define LOAD_FUNCTION(func) \ - func = (t_##func)GetProcAddress(p_fribidi, #func); \ - error = error || (func == 0); -#endif + /* load ..._ex first to preserve error variable */ + LOAD_FUNCTION(fribidi_get_par_embedding_levels_ex); + if (error) { + /* using FriBiDi 0.x, emulate ..._ex function */ + fribidi_get_par_embedding_levels_ex = &fribidi_get_par_embedding_levels_ex_compat; + error = 0; + } LOAD_FUNCTION(fribidi_get_bidi_types); LOAD_FUNCTION(fribidi_get_bracket_types); - LOAD_FUNCTION(fribidi_get_par_embedding_levels_ex); -// LOAD_FUNCTION(fribidi_get_par_embedding_levels); + LOAD_FUNCTION(fribidi_get_par_embedding_levels); LOAD_FUNCTION(fribidi_unicode_to_charset); LOAD_FUNCTION(fribidi_charset_to_unicode); diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h index aa446fbef94..0f0cdac2105 100644 --- a/src/thirdparty/fribidi-shim/fribidi.h +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -83,9 +83,9 @@ FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex, const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex, FriBidiParType *, FriBidiLevel *); -//FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, -// const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, -// FriBidiLevel *); +FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, + const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, + FriBidiLevel *); FRIBIDI_FUNC(FriBidiStrIndex, fribidi_unicode_to_charset, FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); From 9c178435fba30f820a1dcd7845313609466c925a Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 19:10:37 +0000 Subject: [PATCH 12/21] raqm: fix FriBiDi<1 support --- src/_imagingft.c | 4 ++-- src/thirdparty/fribidi-shim/fribidi.c | 23 ++++++++++++++++------- src/thirdparty/fribidi-shim/fribidi.h | 26 ++++++++++++++------------ 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index b2cf76ce798..0995abab3c5 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1397,10 +1397,10 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "raqm_version", v); #ifdef FRIBIDI_MAJOR_VERSION - a = strchr(fribidi_version_info, '1'); + a = strchr(fribidi_version_info, ')'); b = strchr(fribidi_version_info, '\n'); if (a && b) { - v = PyUnicode_FromStringAndSize(a, b - a); + v = PyUnicode_FromStringAndSize(a + 2, b - a - 2); } else { v = Py_None; } diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index 20364ea2488..55e2a6ab397 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -11,7 +11,7 @@ #include "fribidi.h" -/* ..._ex adds bracket_types param, ignore and call legacy function */ +/* FriBiDi>=1.0.0 adds bracket_types param, ignore and call legacy function */ FriBidiLevel fribidi_get_par_embedding_levels_ex_compat( const FriBidiCharType *bidi_types, const FriBidiBracketType *bracket_types, @@ -23,6 +23,14 @@ FriBidiLevel fribidi_get_par_embedding_levels_ex_compat( bidi_types, len, pbase_dir, embedding_levels); } +/* FriBiDi>=1.0.0 gets bracket types here, ignore */ +void fribidi_get_bracket_types_compat( + const FriBidiChar *str, + const FriBidiStrIndex len, + const FriBidiCharType *types, + FriBidiBracketType *btypes) +{ /* no-op*/ } + int load_fribidi(void) { int error = 0; @@ -58,19 +66,20 @@ int load_fribidi(void) { return 1; } - /* load ..._ex first to preserve error variable */ + /* load FriBiDi>=1.0.0 functions first, use error to detect version */ LOAD_FUNCTION(fribidi_get_par_embedding_levels_ex); + LOAD_FUNCTION(fribidi_get_bracket_types); if (error) { - /* using FriBiDi 0.x, emulate ..._ex function */ - fribidi_get_par_embedding_levels_ex = &fribidi_get_par_embedding_levels_ex_compat; + /* using FriBiDi<1.0.0, ignore new parameters */ error = 0; + fribidi_get_par_embedding_levels_ex = &fribidi_get_par_embedding_levels_ex_compat; + fribidi_get_bracket_types = &fribidi_get_bracket_types_compat; } - LOAD_FUNCTION(fribidi_get_bidi_types); - LOAD_FUNCTION(fribidi_get_bracket_types); - LOAD_FUNCTION(fribidi_get_par_embedding_levels); LOAD_FUNCTION(fribidi_unicode_to_charset); LOAD_FUNCTION(fribidi_charset_to_unicode); + LOAD_FUNCTION(fribidi_get_bidi_types); + LOAD_FUNCTION(fribidi_get_par_embedding_levels); #ifndef _WIN32 fribidi_version_info = *(const char**)dlsym(p_fribidi, "fribidi_version_info"); diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h index 0f0cdac2105..7712a5b2297 100644 --- a/src/thirdparty/fribidi-shim/fribidi.h +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -72,26 +72,28 @@ typedef uint32_t FriBidiParType; typedef ret (*t_##name) (__VA_ARGS__); \ FRIBIDI_ENTRY t_##name name; -FRIBIDI_FUNC(void, fribidi_get_bidi_types, - const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *); +FRIBIDI_FUNC(FriBidiStrIndex, fribidi_unicode_to_charset, + FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); -FRIBIDI_FUNC(void, fribidi_get_bracket_types, - const FriBidiChar *, const FriBidiStrIndex, const FriBidiCharType *, - FriBidiBracketType *); +FRIBIDI_FUNC(FriBidiStrIndex, fribidi_charset_to_unicode, + FriBidiCharSet, const char *, FriBidiStrIndex, FriBidiChar *); -FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex, - const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex, - FriBidiParType *, FriBidiLevel *); +FRIBIDI_FUNC(void, fribidi_get_bidi_types, + const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *); FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, FriBidiLevel *); -FRIBIDI_FUNC(FriBidiStrIndex, fribidi_unicode_to_charset, - FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); +/* FriBiDi>=1.0.0 */ +FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex, + const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex, + FriBidiParType *, FriBidiLevel *); -FRIBIDI_FUNC(FriBidiStrIndex, fribidi_charset_to_unicode, - FriBidiCharSet, const char *, FriBidiStrIndex, FriBidiChar *); +/* FriBiDi>=1.0.0 */ +FRIBIDI_FUNC(void, fribidi_get_bracket_types, + const FriBidiChar *, const FriBidiStrIndex, const FriBidiCharType *, + FriBidiBracketType *); #undef FRIBIDI_FUNC From db0dad909e53ac2f25e4badab4d2aad464c49a68 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 25 Nov 2020 19:33:33 +0000 Subject: [PATCH 13/21] test --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index cb4ec2da8b1..031cf5e4ea3 100755 --- a/setup.py +++ b/setup.py @@ -29,6 +29,8 @@ def get_version(): NAME = "Pillow" PILLOW_VERSION = get_version() FREETYPE_ROOT = None +HARFBUZZ_ROOT = None +FRIBIDI_ROOT = None IMAGEQUANT_ROOT = None JPEG2K_ROOT = None JPEG_ROOT = None @@ -417,6 +419,8 @@ def build_extensions(self): TIFF_ROOT=("libtiff-5", "libtiff-4"), ZLIB_ROOT="zlib", FREETYPE_ROOT="freetype2", + HARFBUZZ_ROOT="harfbuzz", + FRIBIDI_ROOT="fribidi", LCMS_ROOT="lcms2", IMAGEQUANT_ROOT="libimagequant", ).items(): From 8c02e3803b995fe0e0d8db2ea4a59c394130d611 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 2 Jan 2021 12:37:21 +0100 Subject: [PATCH 14/21] Revert "raqm: avoid declaring variables in for statement for C89 compatibility" This reverts commit b3cfe73854e74bc25a88f53b177713bfb63812e4. --- src/thirdparty/raqm/raqm.c | 127 ++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 74 deletions(-) diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index 96523ffb949..5a0b2078eae 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -220,7 +220,6 @@ static bool _raqm_init_text_info (raqm_t *rq) { hb_language_t default_lang; - size_t i; if (rq->text_info) return true; @@ -230,7 +229,7 @@ _raqm_init_text_info (raqm_t *rq) return false; default_lang = hb_language_get_default (); - for (i = 0; i < rq->text_len; i++) + for (size_t i = 0; i < rq->text_len; i++) { rq->text_info[i].ftface = NULL; rq->text_info[i].lang = default_lang; @@ -243,12 +242,10 @@ _raqm_init_text_info (raqm_t *rq) static void _raqm_free_text_info (raqm_t *rq) { - size_t i; - if (!rq->text_info) return; - for (i = 0; i < rq->text_len; i++) + for (size_t i = 0; i < rq->text_len; i++) { if (rq->text_info[i].ftface) FT_Done_Face (rq->text_info[i].ftface); @@ -554,7 +551,6 @@ raqm_set_language (raqm_t *rq, size_t len) { hb_language_t language; - size_t i; size_t end = start + len; if (!rq) @@ -576,7 +572,7 @@ raqm_set_language (raqm_t *rq, return false; language = hb_language_from_string (lang, -1); - for (i = start; i < end; i++) + for (size_t i = start; i < end; i++) { rq->text_info[i].lang = language; } @@ -650,8 +646,6 @@ _raqm_set_freetype_face (raqm_t *rq, size_t start, size_t end) { - size_t i; - if (!rq) return false; @@ -664,7 +658,7 @@ _raqm_set_freetype_face (raqm_t *rq, if (!rq->text_info) return false; - for (i = start; i < end; i++) + for (size_t i = start; i < end; i++) { if (rq->text_info[i].ftface) FT_Done_Face (rq->text_info[i].ftface); @@ -838,8 +832,6 @@ _raqm_shape (raqm_t *rq); bool raqm_layout (raqm_t *rq) { - size_t i; - if (!rq) return false; @@ -849,7 +841,7 @@ raqm_layout (raqm_t *rq) if (!rq->text_info) return false; - for (i = 0; i < rq->text_len; i++) + for (size_t i = 0; i < rq->text_len; i++) { if (!rq->text_info[i].ftface) return false; @@ -887,9 +879,6 @@ raqm_glyph_t * raqm_get_glyphs (raqm_t *rq, size_t *length) { - size_t i; - raqm_run_t *run; - size_t count = 0; if (!rq || !rq->runs || !length) @@ -899,7 +888,7 @@ raqm_get_glyphs (raqm_t *rq, return NULL; } - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) count += hb_buffer_get_length (run->buffer); *length = count; @@ -917,7 +906,7 @@ raqm_get_glyphs (raqm_t *rq, RAQM_TEST ("Glyph information:\n"); count = 0; - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -927,7 +916,7 @@ raqm_get_glyphs (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { rq->glyphs[count + i].index = info[i].codepoint; rq->glyphs[count + i].cluster = info[i].cluster; @@ -950,18 +939,18 @@ raqm_get_glyphs (raqm_t *rq, { #ifdef RAQM_TESTING RAQM_TEST ("\nUTF-32 clusters:"); - for (i = 0; i < count; i++) + for (size_t i = 0; i < count; i++) RAQM_TEST (" %02d", rq->glyphs[i].cluster); RAQM_TEST ("\n"); #endif - for (i = 0; i < count; i++) + for (size_t i = 0; i < count; i++) rq->glyphs[i].cluster = _raqm_u32_to_u8_index (rq, rq->glyphs[i].cluster); #ifdef RAQM_TESTING RAQM_TEST ("UTF-8 clusters: "); - for (i = 0; i < count; i++) + for (size_t i = 0; i < count; i++) RAQM_TEST (" %02d", rq->glyphs[i].cluster); RAQM_TEST ("\n"); #endif @@ -994,11 +983,9 @@ typedef struct { static void _raqm_reverse_run (_raqm_bidi_run *run, const size_t len) { - size_t i; - assert (run); - for (i = 0; i < len / 2; i++) + for (size_t i = 0; i < len / 2; i++) { _raqm_bidi_run temp = run[i]; run[i] = run[len - 1 - i]; @@ -1015,7 +1002,6 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* output */ size_t *run_count) { - size_t i; FriBidiLevel level; FriBidiLevel last_level = -1; FriBidiLevel max_level = 0; @@ -1035,7 +1021,8 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* L1. Reset the embedding levels of some chars: 4. any sequence of white space characters at the end of the line. */ - for (i = len; i-- > 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); ) + for (int i = len - 1; + i >= 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); i--) { levels[i] = FRIBIDI_DIR_TO_LEVEL (base_dir); } @@ -1043,13 +1030,13 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* Find max_level of the line. We don't reuse the paragraph * max_level, both for a cleaner API, and that the line max_level * may be far less than paragraph max_level. */ - for (i = len; i-- > 0; ) + for (int i = len - 1; i >= 0; i--) { if (levels[i] > max_level) max_level = levels[i]; } - for (i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { if (levels[i] != last_level) count++; @@ -1077,16 +1064,14 @@ _raqm_reorder_runs (const FriBidiCharType *types, /* L2. Reorder. */ for (level = max_level; level > 0; level--) { - for (i = count; i-- > 0; ) + for (int i = count - 1; i >= 0; i--) { if (runs[i].level >= level) { int end = i; - for (; (i > 0 && runs[i - 1].level >= level); i--) + for (i--; (i >= 0 && runs[i].level >= level); i--) ; - _raqm_reverse_run (runs + i, end - i + 1); - if (i-- == 0) - break; + _raqm_reverse_run (runs + i + 1, end - i); } } } @@ -1098,8 +1083,6 @@ _raqm_reorder_runs (const FriBidiCharType *types, static bool _raqm_itemize (raqm_t *rq) { - size_t i, j; - raqm_run_t *run; FriBidiParType par_type = FRIBIDI_PAR_ON; FriBidiCharType *types; #ifdef USE_FRIBIDI_EX_API @@ -1202,7 +1185,7 @@ _raqm_itemize (raqm_t *rq) RAQM_TEST ("Number of runs before script itemization: %zu\n\n", run_count); RAQM_TEST ("Fribidi Runs:\n"); - for (i = 0; i < run_count; i++) + for (size_t i = 0; i < run_count; i++) { RAQM_TEST ("run[%zu]:\t start: %zu\tlength: %zu\tlevel: %d\n", i, runs[i].pos, runs[i].len, runs[i].level); @@ -1211,7 +1194,7 @@ _raqm_itemize (raqm_t *rq) #endif last = NULL; - for (i = 0; i < run_count; i++) + for (size_t i = 0; i < run_count; i++) { raqm_run_t *run = calloc (1, sizeof (raqm_run_t)); if (!run) @@ -1233,7 +1216,7 @@ _raqm_itemize (raqm_t *rq) run->pos = runs[i].pos + runs[i].len - 1; run->script = rq->text_info[run->pos].script; run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); - for (j = runs[i].len; j-- > 0; ) + for (int j = runs[i].len - 1; j >= 0; j--) { _raqm_text_info info = rq->text_info[runs[i].pos + j]; if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) @@ -1264,7 +1247,7 @@ _raqm_itemize (raqm_t *rq) run->pos = runs[i].pos; run->script = rq->text_info[run->pos].script; run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); - for (j = 0; j < runs[i].len; j++) + for (size_t j = 0; j < runs[i].len; j++) { _raqm_text_info info = rq->text_info[runs[i].pos + j]; if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) @@ -1294,13 +1277,13 @@ _raqm_itemize (raqm_t *rq) #ifdef RAQM_TESTING run_count = 0; - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) run_count++; RAQM_TEST ("Number of runs after script itemization: %zu\n\n", run_count); run_count = 0; RAQM_TEST ("Final Runs:\n"); - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { SCRIPT_TO_STRING (run->script); RAQM_TEST ("run[%zu]:\t start: %d\tlength: %d\tdirection: %s\tscript: %s\tfont: %s\n", @@ -1465,19 +1448,18 @@ _get_pair_index (const FriBidiChar ch) static bool _raqm_resolve_scripts (raqm_t *rq) { - size_t i, j; - size_t next_script_index = 0; - size_t next_set_index = 0; + int last_script_index = -1; + int last_set_index = -1; hb_script_t last_script = HB_SCRIPT_INVALID; _raqm_stack_t *stack = NULL; hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); - for (i = 0; i < rq->text_len; ++i) + for (size_t i = 0; i < rq->text_len; ++i) rq->text_info[i].script = hb_unicode_script (unicode_funcs, rq->text[i]); #ifdef RAQM_TESTING RAQM_TEST ("Before script detection:\n"); - for (i = 0; i < rq->text_len; ++i) + for (size_t i = 0; i < rq->text_len; ++i) { SCRIPT_TO_STRING (rq->text_info[i].script); RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); @@ -1489,9 +1471,9 @@ _raqm_resolve_scripts (raqm_t *rq) if (!stack) return false; - for (i = 0; i < rq->text_len; i++) + for (int i = 0; i < (int) rq->text_len; i++) { - if (rq->text_info[i].script == HB_SCRIPT_COMMON && next_script_index != 0) + if (rq->text_info[i].script == HB_SCRIPT_COMMON && last_script_index != -1) { int pair_index = _get_pair_index (rq->text[i]); if (pair_index >= 0) @@ -1500,7 +1482,7 @@ _raqm_resolve_scripts (raqm_t *rq) { /* is a paired character */ rq->text_info[i].script = last_script; - next_set_index = i + 1; + last_set_index = i; _raqm_stack_push (stack, rq->text_info[i].script, pair_index); } else @@ -1517,34 +1499,34 @@ _raqm_resolve_scripts (raqm_t *rq) { rq->text_info[i].script = _raqm_stack_top (stack); last_script = rq->text_info[i].script; - next_set_index = i + 1; + last_set_index = i; } else { rq->text_info[i].script = last_script; - next_set_index = i + 1; + last_set_index = i; } } } else { rq->text_info[i].script = last_script; - next_set_index = i + 1; + last_set_index = i; } } else if (rq->text_info[i].script == HB_SCRIPT_INHERITED && - next_script_index != 0) + last_script_index != -1) { rq->text_info[i].script = last_script; - next_set_index = i + 1; + last_set_index = i; } else { - for (j = next_set_index; j < i; ++j) + for (int j = last_set_index + 1; j < i; ++j) rq->text_info[j].script = rq->text_info[i].script; last_script = rq->text_info[i].script; - next_script_index = i + 1; - next_set_index = i + 1; + last_script_index = i; + last_set_index = i; } } @@ -1552,7 +1534,7 @@ _raqm_resolve_scripts (raqm_t *rq) * take the script if the next character. * https://github.com/HOST-Oman/libraqm/issues/95 */ - for (i = rq->text_len - 1; i-- > 0; ) + for (int i = rq->text_len - 2; i >= 0; --i) { if (rq->text_info[i].script == HB_SCRIPT_INHERITED || rq->text_info[i].script == HB_SCRIPT_COMMON) @@ -1561,7 +1543,7 @@ _raqm_resolve_scripts (raqm_t *rq) #ifdef RAQM_TESTING RAQM_TEST ("After script detection:\n"); - for (i = 0; i < rq->text_len; ++i) + for (size_t i = 0; i < rq->text_len; ++i) { SCRIPT_TO_STRING (rq->text_info[i].script); RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); @@ -1577,7 +1559,6 @@ _raqm_resolve_scripts (raqm_t *rq) static bool _raqm_shape (raqm_t *rq) { - raqm_run_t *run; hb_buffer_flags_t hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT; #if defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) && \ @@ -1586,7 +1567,7 @@ _raqm_shape (raqm_t *rq) hb_buffer_flags |= HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES; #endif - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { run->buffer = hb_buffer_create (); @@ -1672,8 +1653,6 @@ raqm_index_to_position (raqm_t *rq, int *x, int *y) { - size_t i, j; - raqm_run_t *run; /* We don't currently support multiline, so y is always 0 */ *y = 0; *x = 0; @@ -1697,7 +1676,7 @@ raqm_index_to_position (raqm_t *rq, ++*index; } - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -1706,7 +1685,7 @@ raqm_index_to_position (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { uint32_t curr_cluster = info[i].cluster; uint32_t next_cluster = curr_cluster; @@ -1714,12 +1693,13 @@ raqm_index_to_position (raqm_t *rq, if (run->direction == HB_DIRECTION_LTR) { - for (j = i + 1; j < len && next_cluster == curr_cluster; j++) + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) next_cluster = info[j].cluster; } else { - for (j = i; i != 0 && j-- > 0 && next_cluster == curr_cluster; ) + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) next_cluster = info[j].cluster; } @@ -1765,8 +1745,6 @@ raqm_position_to_index (raqm_t *rq, int y, size_t *index) { - size_t i, j; - raqm_run_t *run; int delta_x = 0, current_x = 0; (void)y; @@ -1784,7 +1762,7 @@ raqm_position_to_index (raqm_t *rq, RAQM_TEST ("\n"); - for (run = rq->runs; run != NULL; run = run->next) + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { size_t len; hb_glyph_info_t *info; @@ -1793,7 +1771,7 @@ raqm_position_to_index (raqm_t *rq, info = hb_buffer_get_glyph_infos (run->buffer, NULL); position = hb_buffer_get_glyph_positions (run->buffer, NULL); - for (i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { delta_x = position[i].x_advance; if (x < (current_x + delta_x)) @@ -1811,10 +1789,11 @@ raqm_position_to_index (raqm_t *rq, uint32_t curr_cluster = info[i].cluster; uint32_t next_cluster = curr_cluster; if (run->direction == HB_DIRECTION_LTR) - for (j = i + 1; j < len && next_cluster == curr_cluster; j++) + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) next_cluster = info[j].cluster; else - for (j = i; i != 0 && j-- > 0 && next_cluster == curr_cluster; ) + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) next_cluster = info[j].cluster; if (next_cluster == curr_cluster) From 43bde01623d6db01435b8a821160ac48d9b722b0 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 2 Jan 2021 12:47:08 +0100 Subject: [PATCH 15/21] disable Raqm/FriBiDi vendoring by default, except in Windows tests --- setup.py | 22 +++++++++++----------- winbuild/build_prepare.py | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index 031cf5e4ea3..65fc7c47f6d 100755 --- a/setup.py +++ b/setup.py @@ -125,7 +125,7 @@ def get_version(): "codec_fd", ) -DEBUG = True +DEBUG = False class DependencyException(Exception): @@ -292,7 +292,7 @@ class feature: ] required = {"jpeg", "zlib"} - system = set() + vendor = set() def __init__(self): for f in self.features: @@ -305,7 +305,7 @@ def want(self, feat): return getattr(self, feat) is None def want_system(self, feat): - return feat in self.system + return feat not in self.vendor def __iter__(self): yield from self.features @@ -317,7 +317,7 @@ def __iter__(self): + [(f"disable-{x}", None, f"Disable support for {x}") for x in feature] + [(f"enable-{x}", None, f"Enable support for {x}") for x in feature] + [ - (f"system-{x}", None, f"Use system version of {x}") + (f"_vendor-{x}", None, f"Use vendored version of {x}") for x in ("raqm", "fribidi") ] + [ @@ -335,7 +335,7 @@ def initialize_options(self): setattr(self, f"disable_{x}", None) setattr(self, f"enable_{x}", None) for x in ("raqm", "fribidi"): - setattr(self, f"system_{x}", None) + setattr(self, f"_vendor_{x}", None) def finalize_options(self): build_ext.finalize_options(self) @@ -374,17 +374,17 @@ def finalize_options(self): _dbg("--enable-raqm implies --enable-freetype") self.feature.required.add("freetype") for x in ("raqm", "fribidi"): - if getattr(self, f"system_{x}"): + if getattr(self, f"_vendor_{x}"): if getattr(self, "disable_raqm"): raise ValueError( - f"Conflicting options: --system-{x} and --disable-raqm" + f"Conflicting options: --_vendor-{x} and --disable-raqm" ) - if x == "fribidi" and getattr(self, "system_raqm"): + if x == "fribidi" and not getattr(self, "_vendor_raqm"): raise ValueError( - f"Conflicting options: --system-{x} and --system-raqm" + f"Conflicting options: --_vendor-{x} and not --_vendor-raqm" ) - _dbg("Using system version of %s", x) - self.feature.system.add(x) + _dbg("Using vendored version of %s", x) + self.feature.vendor.add(x) def _update_extension(self, name, libraries, define_macros=None, sources=None): for extension in self.extensions: diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index fd63f4f1efb..100f07e9018 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -472,7 +472,7 @@ def build_pillow(): cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow cmd_set("MSSdk", "1"), # for PyPy3.6 cmd_set("py_vcruntime_redist", "true"), # use /MD, not /MT - r'"{python_dir}\{python_exe}" setup.py build_ext %*', + r'"{python_dir}\{python_exe}" setup.py build_ext --_vendor-raqm --_vendor-fribidi %*', ] write_script("build_pillow.cmd", lines) From 0488a2761ac3ccc6cc65f910a8254b0e0677b0c9 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 2 Jan 2021 12:51:45 +0100 Subject: [PATCH 16/21] can't use underscore prefix for distutils options --- setup.py | 12 ++++++------ winbuild/build_prepare.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 65fc7c47f6d..fbf869ffbf4 100755 --- a/setup.py +++ b/setup.py @@ -317,7 +317,7 @@ def __iter__(self): + [(f"disable-{x}", None, f"Disable support for {x}") for x in feature] + [(f"enable-{x}", None, f"Enable support for {x}") for x in feature] + [ - (f"_vendor-{x}", None, f"Use vendored version of {x}") + (f"vendor-{x}", None, f"Use vendored version of {x}") for x in ("raqm", "fribidi") ] + [ @@ -335,7 +335,7 @@ def initialize_options(self): setattr(self, f"disable_{x}", None) setattr(self, f"enable_{x}", None) for x in ("raqm", "fribidi"): - setattr(self, f"_vendor_{x}", None) + setattr(self, f"vendor_{x}", None) def finalize_options(self): build_ext.finalize_options(self) @@ -374,14 +374,14 @@ def finalize_options(self): _dbg("--enable-raqm implies --enable-freetype") self.feature.required.add("freetype") for x in ("raqm", "fribidi"): - if getattr(self, f"_vendor_{x}"): + if getattr(self, f"vendor_{x}"): if getattr(self, "disable_raqm"): raise ValueError( - f"Conflicting options: --_vendor-{x} and --disable-raqm" + f"Conflicting options: --vendor-{x} and --disable-raqm" ) - if x == "fribidi" and not getattr(self, "_vendor_raqm"): + if x == "fribidi" and not getattr(self, "vendor_raqm"): raise ValueError( - f"Conflicting options: --_vendor-{x} and not --_vendor-raqm" + f"Conflicting options: --vendor-{x} and not --vendor-raqm" ) _dbg("Using vendored version of %s", x) self.feature.vendor.add(x) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 100f07e9018..dc372f36b96 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -472,7 +472,7 @@ def build_pillow(): cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow cmd_set("MSSdk", "1"), # for PyPy3.6 cmd_set("py_vcruntime_redist", "true"), # use /MD, not /MT - r'"{python_dir}\{python_exe}" setup.py build_ext --_vendor-raqm --_vendor-fribidi %*', + r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*', ] write_script("build_pillow.cmd", lines) From aae94110d76acedf7d4fb12bb45cc77df06027e2 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 2 Jan 2021 13:08:38 +0100 Subject: [PATCH 17/21] lint --- winbuild/build_prepare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index dc372f36b96..3b1a15eac3b 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -472,7 +472,7 @@ def build_pillow(): cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow cmd_set("MSSdk", "1"), # for PyPy3.6 cmd_set("py_vcruntime_redist", "true"), # use /MD, not /MT - r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*', + r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*', # noqa: E501 ] write_script("build_pillow.cmd", lines) From 70fb148fc45fe72302445450190907ee1d161539 Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 27 Feb 2021 15:14:00 +0100 Subject: [PATCH 18/21] fix merge --- winbuild/fribidi.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winbuild/fribidi.cmake b/winbuild/fribidi.cmake index acb614bfa1b..27b8d17a8ed 100644 --- a/winbuild/fribidi.cmake +++ b/winbuild/fribidi.cmake @@ -99,4 +99,4 @@ add_library(fribidi SHARED ${FRIBIDI_SOURCES_GENERATED}) fribidi_definitions(fribidi) target_compile_definitions(fribidi - PUBLIC "-DFRIBIDI_ENTRY=__declspec(dllexport)") + PUBLIC "-DFRIBIDI_BUILD") From e4cc42265dc4e00bccdf960f13fbbd6dfcb469dc Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 27 Feb 2021 16:52:46 +0100 Subject: [PATCH 19/21] add Raqm build configuration info to build summary --- setup.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/setup.py b/setup.py index cc902c152a4..6c4840c75d8 100755 --- a/setup.py +++ b/setup.py @@ -304,8 +304,8 @@ def require(self, feat): def want(self, feat): return getattr(self, feat) is None - def want_system(self, feat): - return feat not in self.vendor + def want_vendor(self, feat): + return feat in self.vendor def __iter__(self): yield from self.features @@ -710,14 +710,14 @@ def build_extensions(self): _add_directory(self.compiler.include_dirs, subdir, 0) if feature.freetype and feature.want("raqm"): - if feature.want_system("raqm"): # want system Raqm + if not feature.want_vendor("raqm"): # want system Raqm _dbg("Looking for Raqm") if _find_include_file(self, "raqm.h"): if _find_library_file(self, "raqm"): feature.raqm = "raqm" elif _find_library_file(self, "libraqm"): feature.raqm = "libraqm" - else: # want to build Raqm + else: # want to build Raqm from src/thirdparty _dbg("Looking for HarfBuzz") feature.harfbuzz = None hb_dir = _find_include_dir(self, "harfbuzz", "hb.h") @@ -727,7 +727,7 @@ def build_extensions(self): if _find_library_file(self, "harfbuzz"): feature.harfbuzz = "harfbuzz" if feature.harfbuzz: - if feature.want_system("fribidi"): # want system FriBiDi + if not feature.want_vendor("fribidi"): # want system FriBiDi _dbg("Looking for FriBiDi") feature.fribidi = None fribidi_dir = _find_include_dir(self, "fribidi", "fribidi.h") @@ -739,7 +739,7 @@ def build_extensions(self): if _find_library_file(self, "fribidi"): feature.fribidi = "fribidi" feature.raqm = True - else: # want to build FriBiDi shim + else: # want to build FriBiDi shim from src/thirdparty feature.raqm = True if feature.want("lcms"): @@ -841,18 +841,18 @@ def build_extensions(self): libs = ["freetype"] defs = [] if feature.raqm: - if feature.want_system("raqm"): # using system Raqm + if not feature.want_vendor("raqm"): # using system Raqm defs.append(("HAVE_RAQM", None)) defs.append(("HAVE_RAQM_SYSTEM", None)) libs.append(feature.raqm) - else: # building Raqm + else: # building Raqm from src/thirdparty defs.append(("HAVE_RAQM", None)) srcs.append("src/thirdparty/raqm/raqm.c") libs.append(feature.harfbuzz) - if feature.want_system("fribidi"): # using system FriBiDi + if not feature.want_vendor("fribidi"): # using system FriBiDi defs.append(("HAVE_FRIBIDI_SYSTEM", None)) libs.append(feature.fribidi) - else: # building our FriBiDi shim + else: # building FriBiDi shim from src/thirdparty srcs.append("src/thirdparty/fribidi-shim/fribidi.c") self._update_extension("PIL._imagingft", libs, defs, srcs) @@ -902,6 +902,12 @@ def summary_report(self, feature): print(f" [{v.strip()}") print("-" * 68) + raqm_extra_info = "" + if feature.want_vendor("raqm"): + raqm_extra_info += "bundled" + if feature.want_vendor("fribidi"): + raqm_extra_info += ", FriBiDi shim" + options = [ (feature.jpeg, "JPEG"), (feature.jpeg2000, "OPENJPEG (JPEG2000)", feature.openjpeg_version), @@ -909,7 +915,7 @@ def summary_report(self, feature): (feature.imagequant, "LIBIMAGEQUANT"), (feature.tiff, "LIBTIFF"), (feature.freetype, "FREETYPE2"), - (feature.raqm, "RAQM (Text shaping)"), # TODO!!! + (feature.raqm, "RAQM (Text shaping)", raqm_extra_info), (feature.lcms, "LITTLECMS2"), (feature.webp, "WEBP"), (feature.webpmux, "WEBPMUX"), @@ -919,10 +925,10 @@ def summary_report(self, feature): all = 1 for option in options: if option[0]: - version = "" + extra_info = "" if len(option) >= 3 and option[2]: - version = f" ({option[2]})" - print(f"--- {option[1]} support available{version}") + extra_info = f" ({option[2]})" + print(f"--- {option[1]} support available{extra_info}") else: print(f"*** {option[1]} support not available") all = 0 From 5e61c1842fcee02562cbb46dd44e8bdd39e3903f Mon Sep 17 00:00:00 2001 From: nulano Date: Thu, 25 Mar 2021 00:04:41 +0100 Subject: [PATCH 20/21] fix support for old versions of Raqm --- src/_imagingft.c | 61 ++++++++++++++++-------------------------------- 1 file changed, 20 insertions(+), 41 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 0995abab3c5..33029447988 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -236,7 +236,6 @@ text_layout_raqm( size_t i = 0, count = 0, start = 0; raqm_t *rq; raqm_glyph_t *glyphs = NULL; -// raqm_glyph_t_01 *glyphs_01 = NULL; raqm_direction_t direction; rq = raqm_create(); @@ -278,12 +277,12 @@ text_layout_raqm( direction = RAQM_DIRECTION_LTR; } else if (strcmp(dir, "ttb") == 0) { direction = RAQM_DIRECTION_TTB; -// if (p_raqm.version_atleast == NULL || !(*p_raqm.version_atleast)(0, 7, 0)) { -// PyErr_SetString( -// PyExc_ValueError, -// "libraqm 0.7 or greater required for 'ttb' direction"); -// goto failed; -// } +#if !defined(RAQM_VERSION_ATLEAST) || !RAQM_VERSION_ATLEAST(0, 7, 0) + PyErr_SetString( + PyExc_ValueError, + "libraqm 0.7 or greater required for 'ttb' direction"); + goto failed; +#endif } else { PyErr_SetString( PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"); @@ -340,21 +339,12 @@ text_layout_raqm( goto failed; } -// if (p_raqm.version == 1) { -// glyphs_01 = raqm_get_glyphs_01(rq, &count); -// if (glyphs_01 == NULL) { -// PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); -// count = 0; -// goto failed; -// } -// } else { /* version == 2 */ - glyphs = raqm_get_glyphs(rq, &count); - if (glyphs == NULL) { - PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); - count = 0; - goto failed; - } -// } + glyphs = raqm_get_glyphs(rq, &count); + if (glyphs == NULL) { + PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); + count = 0; + goto failed; + } (*glyph_info) = PyMem_New(GlyphInfo, count); if ((*glyph_info) == NULL) { @@ -363,25 +353,14 @@ text_layout_raqm( goto failed; } -// if (p_raqm.version == 1) { -// for (i = 0; i < count; i++) { -// (*glyph_info)[i].index = glyphs_01[i].index; -// (*glyph_info)[i].x_offset = glyphs_01[i].x_offset; -// (*glyph_info)[i].x_advance = glyphs_01[i].x_advance; -// (*glyph_info)[i].y_offset = glyphs_01[i].y_offset; -// (*glyph_info)[i].y_advance = glyphs_01[i].y_advance; -// (*glyph_info)[i].cluster = glyphs_01[i].cluster; -// } -// } else { - for (i = 0; i < count; i++) { - (*glyph_info)[i].index = glyphs[i].index; - (*glyph_info)[i].x_offset = glyphs[i].x_offset; - (*glyph_info)[i].x_advance = glyphs[i].x_advance; - (*glyph_info)[i].y_offset = glyphs[i].y_offset; - (*glyph_info)[i].y_advance = glyphs[i].y_advance; - (*glyph_info)[i].cluster = glyphs[i].cluster; - } -// } + for (i = 0; i < count; i++) { + (*glyph_info)[i].index = glyphs[i].index; + (*glyph_info)[i].x_offset = glyphs[i].x_offset; + (*glyph_info)[i].x_advance = glyphs[i].x_advance; + (*glyph_info)[i].y_offset = glyphs[i].y_offset; + (*glyph_info)[i].y_advance = glyphs[i].y_advance; + (*glyph_info)[i].cluster = glyphs[i].cluster; + } failed: raqm_destroy(rq); From c718cc6c94cacfbfcd7e84cff9f012df1b84c718 Mon Sep 17 00:00:00 2001 From: nulano Date: Thu, 25 Mar 2021 00:25:38 +0100 Subject: [PATCH 21/21] avoid unused variable warnings --- src/_imagingft.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 33029447988..73f0f6362f9 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1367,7 +1367,6 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "HAVE_FRIBIDI", v); PyDict_SetItemString(d, "HAVE_HARFBUZZ", v); if (have_raqm) { - const char *a, *b; #ifdef RAQM_VERSION_MAJOR v = PyUnicode_FromString(raqm_version_string()); #else @@ -1376,12 +1375,14 @@ setup_module(PyObject *m) { PyDict_SetItemString(d, "raqm_version", v); #ifdef FRIBIDI_MAJOR_VERSION - a = strchr(fribidi_version_info, ')'); - b = strchr(fribidi_version_info, '\n'); - if (a && b) { - v = PyUnicode_FromStringAndSize(a + 2, b - a - 2); - } else { - v = Py_None; + { + const char *a = strchr(fribidi_version_info, ')'); + const char *b = strchr(fribidi_version_info, '\n'); + if (a && b && a + 2 < b) { + v = PyUnicode_FromStringAndSize(a + 2, b - (a + 2)); + } else { + v = Py_None; + } } #else v = Py_None;