From 7b4ca8679c8f4a89540f989d5157a789d1c6a422 Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Sat, 29 Aug 2026 11:52:25 +0300 Subject: [PATCH 1/7] Build Raqm as a wheel dependencies Raqm was built in a special way because FriBiDi (a Raqm dependency) LGPL license. Raqm has since gained support for using Apache-2.0-licensed SheenBidi. Raqm is configured with -Dsheenbidi=true. A winbuild/raqm.cmake is added for Windows builds (Raqm upstream has no CMake support). The extension still compiles the bundled Raqm copy, so nothing links these libraries at the moment. --- .github/dependencies.json | 2 + .github/renovate.json | 18 ++ .github/workflows/wheels-dependencies.sh | 28 ++++ wheels/dependency_licenses/SHEENBIDI.txt | 205 +++++++++++++++++++++++ winbuild/build_prepare.py | 22 +++ winbuild/raqm.cmake | 17 ++ 6 files changed, 292 insertions(+) create mode 100644 wheels/dependency_licenses/SHEENBIDI.txt create mode 100644 winbuild/raqm.cmake diff --git a/.github/dependencies.json b/.github/dependencies.json index 1d0401c31ff..0f05ff1f1ec 100644 --- a/.github/dependencies.json +++ b/.github/dependencies.json @@ -12,6 +12,8 @@ "libwebp": "1.6.0", "libxcb": "1.17.0", "openjpeg": "2.5.4", + "raqm": "0.11.0", + "sheenbidi": "3.0.0", "tiff": "4.7.2", "xz": "5.8.3", "zlib-ng": "2.3.3", diff --git a/.github/renovate.json b/.github/renovate.json index f26109f29ec..4030ff38a80 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -129,6 +129,24 @@ "datasourceTemplate": "github-releases", "extractVersionTemplate": "^v(?.+)$" }, + { + "customType": "regex", + "managerFilePatterns": ["/^\\.github/dependencies\\.json$/"], + "matchStrings": ["\"raqm\":\\s*\"(?\\d+[^\"]*)\""], + "depNameTemplate": "raqm", + "packageNameTemplate": "HOST-Oman/libraqm", + "datasourceTemplate": "github-releases", + "extractVersionTemplate": "^v(?.+)$" + }, + { + "customType": "regex", + "managerFilePatterns": ["/^\\.github/dependencies\\.json$/"], + "matchStrings": ["\"sheenbidi\":\\s*\"(?\\d+[^\"]*)\""], + "depNameTemplate": "sheenbidi", + "packageNameTemplate": "Tehreer/SheenBidi", + "datasourceTemplate": "github-releases", + "extractVersionTemplate": "^v(?.+)$" + }, { "customType": "regex", "managerFilePatterns": ["/^\\.github/dependencies\\.json$/"], diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index b3a5782091b..de10ceab706 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -107,6 +107,8 @@ BZIP2_VERSION=$(_get_ver bzip2) LIBXCB_VERSION=$(_get_ver libxcb) BROTLI_VERSION=$(_get_ver brotli) LIBAVIF_VERSION=$(_get_ver libavif) +RAQM_VERSION=$(_get_ver raqm) +SHEENBIDI_VERSION=$(_get_ver sheenbidi) function build_pkg_config { if [ -e pkg-config-stamp ]; then return; fi @@ -171,6 +173,30 @@ function build_harfbuzz { touch harfbuzz-stamp } +function build_sheenbidi { + if [ -e sheenbidi-stamp ]; then return; fi + python3 -m pip install meson ninja + + local out_dir=$(fetch_unpack https://github.com/Tehreer/SheenBidi/archive/v$SHEENBIDI_VERSION.tar.gz SheenBidi-$SHEENBIDI_VERSION.tar.gz) + (cd $out_dir \ + && meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize $HOST_MESON_FLAGS) + (cd $out_dir/build \ + && meson install) + touch sheenbidi-stamp +} + +function build_raqm { + if [ -e raqm-stamp ]; then return; fi + python3 -m pip install meson ninja + + local out_dir=$(fetch_unpack https://github.com/HOST-Oman/libraqm/releases/download/v$RAQM_VERSION/raqm-$RAQM_VERSION.tar.xz raqm-$RAQM_VERSION.tar.xz) + (cd $out_dir \ + && meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dsheenbidi=true -Ddocs=false -Dtests=false $HOST_MESON_FLAGS) + (cd $out_dir/build \ + && meson install) + touch raqm-stamp +} + function build_libavif { if [ -e libavif-stamp ]; then return; fi @@ -315,6 +341,8 @@ function build { # On iOS, there's no vendor-provided raqm, and we can't ship it due to # licensing, so there's no point building harfbuzz. build_harfbuzz + build_sheenbidi + build_raqm fi } diff --git a/wheels/dependency_licenses/SHEENBIDI.txt b/wheels/dependency_licenses/SHEENBIDI.txt new file mode 100644 index 00000000000..84d0dba9088 --- /dev/null +++ b/wheels/dependency_licenses/SHEENBIDI.txt @@ -0,0 +1,205 @@ +SheenBidi is licensed under the Apache License, Version 2.0. + +Copyright (C) 2014-2025 Muhammad Tayyab Akram + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 2e8b3b82643..13e0646aec4 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -364,6 +364,28 @@ def cmd_msbuild( ], "bins": [r"*.dll"], }, + "sheenbidi": { + "url": f"https://github.com/Tehreer/SheenBidi/archive/v{V['sheenbidi']}.tar.gz", + "filename": f"SheenBidi-{V['sheenbidi']}.tar.gz", + "license": "LICENSE", + "build": [ + *cmds_cmake("SheenBidi"), + cmd_mkdir(r"{inc_dir}\SheenBidi"), + cmd_xcopy(r"Headers\SheenBidi", r"{inc_dir}\SheenBidi"), + ], + "libs": [r"*.lib"], + }, + "raqm": { + "url": f"https://github.com/HOST-Oman/libraqm/releases/download/v{V['raqm']}/FILENAME", + "filename": f"raqm-{V['raqm']}.tar.xz", + "license": "COPYING", + "build": [ + cmd_copy(r"{winbuild_dir}\raqm.cmake", r"CMakeLists.txt"), + *cmds_cmake("raqm", f"-DRAQM_VERSION={V['raqm']}"), + ], + "headers": [r"src\raqm.h", r"src\raqm-version.h"], + "libs": [r"*.lib"], + }, "libavif": { "url": f"https://github.com/AOMediaCodec/libavif/archive/v{V['libavif']}.tar.gz", "filename": f"libavif-{V['libavif']}.tar.gz", diff --git a/winbuild/raqm.cmake b/winbuild/raqm.cmake new file mode 100644 index 00000000000..a4230668bde --- /dev/null +++ b/winbuild/raqm.cmake @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.12) + +project(raqm C) + +add_definitions(-D_CRT_SECURE_NO_WARNINGS) + +set(RAQM_VERSION "" CACHE STRING "Raqm version, as x.y.z") +string(REPLACE "." ";" RAQM_VERSION_PARTS ${RAQM_VERSION}) +list(GET RAQM_VERSION_PARTS 0 RAQM_VERSION_MAJOR) +list(GET RAQM_VERSION_PARTS 1 RAQM_VERSION_MINOR) +list(GET RAQM_VERSION_PARTS 2 RAQM_VERSION_MICRO) +message("Building Raqm version ${RAQM_VERSION}") +configure_file(src/raqm-version.h.in ${CMAKE_CURRENT_SOURCE_DIR}/src/raqm-version.h @ONLY) + +# FreeType, HarfBuzz and SheenBidi headers come from INCLUDE, set by build_env.cmd. +add_library(raqm STATIC src/raqm.c) +target_compile_definitions(raqm PRIVATE RAQM_SHEENBIDI RAQM_SHEENBIDI_GT_2_9) From 4808991a17c44086adb1effd63b7845aa6182d1c Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Sat, 29 Aug 2026 11:59:57 +0300 Subject: [PATCH 2/7] Link Raqm instead of bundling it Use the dependency builds introduced in the previous commit. The whole FriBiDi shim setup is not longer needed since Rqam is built against SheenBidi. This simplifies the setup greatly and drops the need for bundling Raqm at all. Raqm availability is now decided at compile time. The "fribidi" feature is meaningless now, but instead of introducing a "sheenbidi" feature (which we can not reliably detect when dynamic linking Raqm), it is now deprecated and made an alias of "raqm" feature. The same goes for "harfbuzz" feature. --- .github/generate-sbom.py | 191 +- .github/workflows/test-windows.yml | 10 +- .github/workflows/wheels-dependencies.sh | 3 +- .github/workflows/wheels-test.sh | 26 +- Tests/test_features.py | 16 + checks/check_wheel.py | 7 +- docs/installation/building-from-source.rst | 6 - docs/reference/features.rst | 2 + pyproject.toml | 8 +- setup.py | 109 +- src/PIL/features.py | 64 +- src/_imagingft.c | 46 - src/thirdparty/fribidi-shim/fribidi.c | 109 - src/thirdparty/fribidi-shim/fribidi.h | 118 - src/thirdparty/raqm/AUTHORS | 9 - src/thirdparty/raqm/COPYING | 22 - src/thirdparty/raqm/NEWS | 204 -- src/thirdparty/raqm/README.md | 78 - src/thirdparty/raqm/grapheme-data.h | 398 --- src/thirdparty/raqm/raqm-version.h | 44 - src/thirdparty/raqm/raqm.c | 2950 -------------------- src/thirdparty/raqm/raqm.h | 227 -- winbuild/README.md | 4 +- winbuild/build.rst | 12 +- 24 files changed, 149 insertions(+), 4514 deletions(-) delete mode 100644 src/thirdparty/fribidi-shim/fribidi.c delete mode 100644 src/thirdparty/fribidi-shim/fribidi.h delete mode 100644 src/thirdparty/raqm/AUTHORS delete mode 100644 src/thirdparty/raqm/COPYING delete mode 100644 src/thirdparty/raqm/NEWS delete mode 100644 src/thirdparty/raqm/README.md delete mode 100644 src/thirdparty/raqm/grapheme-data.h delete mode 100644 src/thirdparty/raqm/raqm-version.h delete mode 100644 src/thirdparty/raqm/raqm.c delete mode 100644 src/thirdparty/raqm/raqm.h diff --git a/.github/generate-sbom.py b/.github/generate-sbom.py index 347edf51462..837b2c50c87 100755 --- a/.github/generate-sbom.py +++ b/.github/generate-sbom.py @@ -11,12 +11,9 @@ from __future__ import annotations import argparse -import base64 import datetime as dt -import difflib import hashlib import json -import urllib.request import uuid from pathlib import Path @@ -35,28 +32,6 @@ def sha256_file(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() -def upstream_diff_b64( - upstream_url: str, - upstream_display: bytes, - local_path: Path, - local_display: bytes, -) -> str: - """ - Fetch an upstream file and return a base64-encoded unified diff vs the local copy. - """ - with urllib.request.urlopen(upstream_url) as resp: - upstream_text = resp.read() - local_text = local_path.read_bytes() - diff_lines = difflib.diff_bytes( - difflib.unified_diff, - upstream_text.splitlines(keepends=True), - local_text.splitlines(keepends=True), - fromfile=b"a/" + upstream_display, - tofile=b"b/" + local_display, - ) - return base64.b64encode(b"".join(diff_lines)).decode() - - def generate(version: str) -> dict: serial = str(uuid.uuid4()) now = dt.datetime.now(dt.UTC).strftime("%Y-%m-%dT%H:%M:%SZ") @@ -115,28 +90,6 @@ def generate(version: str) -> dict: ] vendored_components = [ - { - "bom-ref": f"{purl}#thirdparty/fribidi-shim", - "type": "library", - "name": "fribidi-shim", - "version": "1.x", - "description": "FriBiDi runtime-loading shim " - "(vendored in src/thirdparty/fribidi-shim/); " - "loads libfribidi dynamically", - "licenses": [{"license": {"id": "LGPL-2.1-or-later"}}], - "hashes": [ - { - "alg": "SHA-256", - "content": sha256_file(thirdparty / "fribidi-shim" / "fribidi.c"), - } - ], - "pedigree": { - "notes": "Pillow-authored shim; not taken from an upstream project." - }, - "externalReferences": [ - {"type": "website", "url": "https://github.com/fribidi/fribidi"}, - ], - }, { "bom-ref": "pkg:github/python/pythoncapi-compat", "type": "library", @@ -160,90 +113,6 @@ def generate(version: str) -> dict: }, ], }, - { - "bom-ref": f"{purl}#thirdparty/raqm", - "type": "library", - "name": "raqm", - "version": "0.11.0", - "description": "Complex text layout library " - "(vendored in src/thirdparty/raqm/)", - "licenses": [{"license": {"id": "MIT"}}], - "hashes": [ - { - "alg": "SHA-256", - "content": sha256_file(thirdparty / "raqm" / "raqm.c"), - } - ], - "pedigree": { - "ancestors": [ - { - "bom-ref": "pkg:github/HOST-Oman/libraqm@0.11.0#upstream", - "type": "library", - "name": "raqm", - "version": "0.11.0", - "purl": "pkg:github/HOST-Oman/libraqm@0.11.0", - "externalReferences": [ - { - "type": "distribution", - "url": "https://github.com/HOST-Oman/libraqm/releases/tag/v0.11.0", - } - ], - } - ], - "patches": [ - { - "type": "unofficial", - "diff": { - "text": { - # raqm-version.h.in → raqm-version.h: - # template @RAQM_VERSION_*@ placeholders replaced - # with literal 0.11.0 values; filename changed to - # drop the .in suffix. - "content": upstream_diff_b64( - "https://raw.githubusercontent.com/HOST-Oman/libraqm/v0.11.0/src/raqm-version.h.in", - b"src/raqm-version.h.in", - thirdparty / "raqm" / "raqm-version.h", - b"src/raqm-version.h", - ), - "encoding": "base64", - } - }, - }, - { - "type": "unofficial", - "diff": { - "text": { - # raqm.c: wrap the include in an - # #ifdef HAVE_FRIBIDI_SYSTEM guard so that when - # building without a system FriBiDi Pillow's own - # fribidi-shim is used instead. - "content": upstream_diff_b64( - "https://raw.githubusercontent.com/HOST-Oman/libraqm/v0.11.0/src/raqm.c", - b"src/raqm.c", - thirdparty / "raqm" / "raqm.c", - b"src/raqm.c", - ), - "encoding": "base64", - } - }, - }, - ], - "notes": ( - "Vendored from upstream HOST-Oman/libraqm v0.11.0 with two " - "Pillow-specific modifications: (1) raqm-version.h.in was " - "pre-processed into raqm-version.h with version placeholders " - "replaced by literal values; (2) raqm.c wraps the " - "include in an #ifdef HAVE_FRIBIDI_SYSTEM guard so Pillow's " - "bundled fribidi-shim is used when a system FriBiDi is absent." - ), - }, - "externalReferences": [ - { - "type": "vcs", - "url": "https://github.com/python-pillow/Pillow/tree/main/src/thirdparty/raqm", - }, - ], - }, ] native_deps = [ @@ -264,23 +133,6 @@ def generate(version: str) -> dict: }, ], }, - { - "bom-ref": "pkg:generic/fribidi", - "type": "library", - "name": "FriBiDi", - "version": versions["fribidi"], - "scope": "optional", - "description": "Unicode bidi algorithm library (optional, " - "loaded at runtime by fribidi-shim).", - "licenses": [{"license": {"id": "LGPL-2.1-or-later"}}], - "externalReferences": [ - {"type": "website", "url": "https://github.com/fribidi/fribidi"}, - { - "type": "distribution", - "url": "https://github.com/fribidi/fribidi/releases", - }, - ], - }, { "bom-ref": "pkg:generic/harfbuzz", "type": "library", @@ -434,6 +286,39 @@ def generate(version: str) -> dict: }, ], }, + { + "bom-ref": "pkg:generic/raqm", + "type": "library", + "name": "Raqm", + "version": versions["raqm"], + "scope": "optional", + "description": "Complex text layout (optional, used by PIL._imagingft).", + "licenses": [{"license": {"id": "MIT"}}], + "externalReferences": [ + {"type": "website", "url": "https://github.com/HOST-Oman/libraqm"}, + { + "type": "distribution", + "url": "https://github.com/HOST-Oman/libraqm/releases", + }, + ], + }, + { + "bom-ref": "pkg:generic/sheenbidi", + "type": "library", + "name": "SheenBidi", + "version": versions["sheenbidi"], + "scope": "optional", + "description": "Unicode bidirectional algorithm (optional, required " + "by libraqm for bidirectional text).", + "licenses": [{"license": {"id": "Apache-2.0"}}], + "externalReferences": [ + {"type": "website", "url": "https://github.com/Tehreer/SheenBidi"}, + { + "type": "distribution", + "url": "https://github.com/Tehreer/SheenBidi/releases", + }, + ], + }, { "bom-ref": "pkg:pypi/pybind11", "type": "library", @@ -492,10 +377,7 @@ def generate(version: str) -> dict: "ref": f"{purl}#c-ext/PIL._imagingft", "dependsOn": [ "pkg:generic/freetype2", - "pkg:generic/fribidi", - "pkg:generic/harfbuzz", - f"{purl}#thirdparty/fribidi-shim", - f"{purl}#thirdparty/raqm", + "pkg:generic/raqm", ], }, { @@ -503,10 +385,11 @@ def generate(version: str) -> dict: "dependsOn": ["pkg:generic/libwebp"], }, { - "ref": f"{purl}#thirdparty/raqm", + "ref": "pkg:generic/raqm", "dependsOn": [ + "pkg:generic/freetype2", "pkg:generic/harfbuzz", - f"{purl}#thirdparty/fribidi-shim", + "pkg:generic/sheenbidi", ], }, ] diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index e00bf9e233c..015a5a53295 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -176,9 +176,13 @@ jobs: run: "& winbuild\\build\\build_dep_harfbuzz.cmd" # Raqm dependencies - - name: Build dependencies / FriBidi + - name: Build dependencies / SheenBidi if: steps.build-cache.outputs.cache-hit != 'true' - run: "& winbuild\\build\\build_dep_fribidi.cmd" + run: "& winbuild\\build\\build_dep_sheenbidi.cmd" + + - name: Build dependencies / Raqm + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_raqm.cmd" # trim ~150MB for each job - name: Optimize build cache @@ -189,7 +193,7 @@ jobs: - name: Build Pillow run: | - $FLAGS="-C raqm=vendor -C fribidi=vendor" + $FLAGS="-C raqm=enable" cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS .[tests]" & $env:pythonLocation\python.exe selftest.py --installed shell: pwsh diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index de10ceab706..778d705c95a 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -338,8 +338,7 @@ function build { fi if [[ -z "$IOS_SDK" ]]; then - # On iOS, there's no vendor-provided raqm, and we can't ship it due to - # licensing, so there's no point building harfbuzz. + # TODO: raqm is not built for iOS build_harfbuzz build_sheenbidi build_raqm diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index d73b6be5847..0c6040174c4 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -1,28 +1,10 @@ #!/bin/bash set -e -# Ensure fribidi is installed by the system. -if [[ "$OSTYPE" == "darwin"* ]]; then - # If Homebrew is on the path during the build, it may leak into the wheels. - # However, we *do* need Homebrew to provide a copy of fribidi for - # testing purposes so that we can verify the fribidi shim works as expected. - if [[ "$(uname -m)" == "x86_64" ]]; then - HOMEBREW_PREFIX=/usr/local - else - HOMEBREW_PREFIX=/opt/homebrew - fi - $HOMEBREW_PREFIX/bin/brew install fribidi - - # Add the lib folder for fribidi so that the vendored library can be found. - # Don't use $HOMEWBREW_PREFIX/lib directly - use the lib folder where the - # installed copy of fribidi is cellared. This ensures we don't pick up the - # Homebrew version of any other library that we're dependent on (most notably, - # freetype). - export DYLD_LIBRARY_PATH=$(dirname $(realpath $HOMEBREW_PREFIX/lib/libfribidi.dylib)) -elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then - apk add curl fribidi -else - yum install -y fribidi +if [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then + apk add curl +elif [ "${AUDITWHEEL_POLICY::9}" == "manylinux" ]; then + ldconfig fi if [ ! -d "test-images-main" ]; then diff --git a/Tests/test_features.py b/Tests/test_features.py index 93d803fc133..ed6fd2234a7 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -21,6 +21,8 @@ def test_check() -> None: for codec in features.codecs: assert features.check_codec(codec) == features.check(codec) for feature in features.features: + if feature in features.deprecated_features: + continue assert features.check_feature(feature) == features.check(feature) @@ -47,9 +49,23 @@ def test(name: str, function: Callable[[str], str | None]) -> None: for codec in features.codecs: test(codec, features.version_codec) for feature in features.features: + if feature in features.deprecated_features: + continue test(feature, features.version_feature) +@pytest.mark.parametrize("feature", features.deprecated_features) +def test_deprecated_feature(feature: str) -> None: + match = f'features.check\\("{feature}"\\)' + with pytest.warns(DeprecationWarning, match=match): + assert features.check_feature(feature) == features.check_feature("raqm") + + with pytest.warns(DeprecationWarning, match=match): + assert features.version_feature(feature) is None + + assert feature not in features.get_supported_features() + + @skip_unless_feature("libjpeg_turbo") def test_libjpeg_turbo_version() -> None: version = features.version("libjpeg_turbo") diff --git a/checks/check_wheel.py b/checks/check_wheel.py index f716c8498bb..ed714d682a5 100644 --- a/checks/check_wheel.py +++ b/checks/check_wheel.py @@ -38,8 +38,6 @@ def test_wheel_codecs() -> None: def test_wheel_features() -> None: expected_features = { "raqm", - "fribidi", - "harfbuzz", "libjpeg_turbo", "zlib_ng", "xcb", @@ -48,8 +46,7 @@ def test_wheel_features() -> None: if sys.platform == "win32": expected_features.remove("xcb") elif sys.platform == "ios": - # Can't distribute raqm due to licensing, and there's no system version; - # fribidi and harfbuzz won't be available if raqm isn't available. - expected_features -= {"raqm", "fribidi", "harfbuzz"} + # TODO: raqm is not built for iOS + expected_features.remove("raqm") assert set(features.get_supported_features()) == expected_features diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index a0923066afd..93dd6121646 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -295,12 +295,6 @@ Build options an exception if the libraries are not found. Tcl and Tk must be used together. -* Config settings: ``-C raqm=vendor``, ``-C fribidi=vendor``. - These flags are used to compile a modified version of libraqm and - a shim that dynamically loads libfribidi at runtime. These are - used to compile the standard Pillow wheels. Compiling libraqm requires - a C99-compliant compiler. - * Config setting: ``-C platform-guessing=disable``. Skips all of the platform dependent guessing of include and library directories for automated build systems that configure the proper paths in the diff --git a/docs/reference/features.rst b/docs/reference/features.rst index 45067ba3587..08b659ed5e3 100644 --- a/docs/reference/features.rst +++ b/docs/reference/features.rst @@ -61,6 +61,8 @@ Support for the following features can be checked: * ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available. * ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library. +``fribidi`` and ``harfbuzz`` are deprecated aliases of ``raqm``. Raqm selects its own bidirectional text and shaping libraries when it is built, so Pillow cannot reliably report which ones are in use. + .. autofunction:: PIL.features.check_feature .. autofunction:: PIL.features.version_feature .. autofunction:: PIL.features.get_supported_features diff --git a/pyproject.toml b/pyproject.toml index d85a5adb716..7eed34ff847 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ dynamic.version = { attr = "PIL.__version__" } [tool.cibuildwheel] build-verbosity = 1 -config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable" +config-settings = "raqm=enable imagequant=disable" before-all = ".github/workflows/wheels-dependencies.sh" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-requires = [ @@ -107,13 +107,13 @@ test-requires = [ ] test-extras = "tests" # Disable platform guessing on macOS to avoid picking up Homebrew etc. -macos.config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable platform-guessing=disable" +macos.config-settings = "raqm=enable imagequant=disable platform-guessing=disable" # Isolate macOS build environment from Homebrew etc. macos.environment.PATH = """\ $(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin\ """ -# Disable platform guessing on iOS, and disable raqm (since there won't be a -# vendor version, and we can't distribute it due to licensing) +# Disable platform guessing on iOS +# TODO: raqm is not built for iOS ios.config-settings = "raqm=disable imagequant=disable platform-guessing=disable" # iOS needs to be given a specific pytest invocation and list of test sources. ios.test-command = [ diff --git a/setup.py b/setup.py index c49dbe96575..be0fb155d64 100644 --- a/setup.py +++ b/setup.py @@ -46,8 +46,6 @@ def get_version() -> str: PILLOW_VERSION = get_version() AVIF_ROOT = None FREETYPE_ROOT = None -HARFBUZZ_ROOT = None -FRIBIDI_ROOT = None IMAGEQUANT_ROOT = None JPEG2K_ROOT = None JPEG_ROOT = None @@ -254,20 +252,6 @@ def _find_library_file(self: pil_build_ext, library: str) -> str | None: return ret -def _find_include_dir(self: pil_build_ext, dirname: str, include: str) -> bool | str: - 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 - return False - - def _cmd_exists(cmd: str) -> bool: if "PATH" not in os.environ: return False @@ -324,7 +308,6 @@ class ext_feature: ] required = {"jpeg", "zlib"} - vendor: set[str] = set() def __init__(self) -> None: self._settings: dict[str, str | bool | None] = {} @@ -343,9 +326,6 @@ def set(self, feat: str, value: str | bool | None) -> None: def want(self, feat: str) -> bool: return self._settings[feat] is None - def want_vendor(self, feat: str) -> bool: - return feat in self.vendor - def __iter__(self) -> Iterator[str]: yield from self.features @@ -355,10 +335,7 @@ def __iter__(self) -> Iterator[str]: 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"vendor-{x}", None, f"Use vendored version of {x}") - for x in ("raqm", "fribidi") - ] + + [(f"vendor-{x}", None, "Deprecated, ignored") for x in ("raqm", "fribidi")] + [ ("disable-platform-guessing", None, "Disable platform guessing"), ("debug", None, "Debug logging"), @@ -425,16 +402,11 @@ def finalize_options(self) -> None: self.feature.required.add("freetype") for x in ("raqm", "fribidi"): if getattr(self, f"vendor_{x}"): - if getattr(self, "disable_raqm"): - msg = f"Conflicting options: '-C {x}=vendor' and '-C raqm=disable'" - raise ValueError(msg) - if x == "fribidi" and not getattr(self, "vendor_raqm"): - msg = ( - f"Conflicting options: '-C {x}=vendor' and not '-C raqm=vendor'" - ) - raise ValueError(msg) - _dbg("Using vendored version of %s", x) - self.feature.vendor.add(x) + warnings.warn( + f"'-C {x}=vendor' is deprecated and ignored. " + "Raqm is no longer bundled.", + DeprecationWarning, + ) def _update_extension( self, @@ -512,8 +484,6 @@ def build_extensions(self) -> None: "TIFF_ROOT": ("libtiff-5", "libtiff-4"), "ZLIB_ROOT": "zlib", "FREETYPE_ROOT": "freetype2", - "HARFBUZZ_ROOT": "harfbuzz", - "FRIBIDI_ROOT": "fribidi", "RAQM_ROOT": "raqm", "WEBP_ROOT": "libwebp", "LCMS_ROOT": "lcms2", @@ -815,37 +785,12 @@ def build_extensions(self) -> None: _add_directory(self.compiler.include_dirs, subdir, 0) if feature.get("freetype") and feature.want("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.set("raqm", "raqm") - elif _find_library_file(self, "libraqm"): - feature.set("raqm", "libraqm") - else: # want to build Raqm from src/thirdparty - _dbg("Looking for HarfBuzz") - feature.set("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.set("harfbuzz", "harfbuzz") - if feature.get("harfbuzz"): - if not feature.want_vendor("fribidi"): # want system FriBiDi - _dbg("Looking for FriBiDi") - feature.set("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.set("fribidi", "fribidi") - feature.set("raqm", True) - else: # want to build FriBiDi shim from src/thirdparty - feature.set("raqm", True) + _dbg("Looking for Raqm") + if _find_include_file(self, "raqm.h"): + if _find_library_file(self, "raqm"): + feature.set("raqm", "raqm") + elif _find_library_file(self, "libraqm"): + feature.set("raqm", "libraqm") if feature.want("lcms"): _dbg("Looking for lcms") @@ -943,29 +888,21 @@ def build_extensions(self) -> None: # additional libraries if feature.get("freetype"): - srcs = [] libs = ["freetype"] defs = [] if feature.get("raqm"): - if not feature.want_vendor("raqm"): # using system Raqm - defs.append(("HAVE_RAQM", None)) - defs.append(("HAVE_RAQM_SYSTEM", None)) - libs.append(feature.get("raqm")) - else: # building Raqm from src/thirdparty - defs.append(("HAVE_RAQM", None)) - srcs.append("src/thirdparty/raqm/raqm.c") - libs.append(feature.get("harfbuzz")) - if not feature.want_vendor("fribidi"): # using system FriBiDi - defs.append(("HAVE_FRIBIDI_SYSTEM", None)) - libs.append(feature.get("fribidi")) - else: # building FriBiDi shim from src/thirdparty - srcs.append("src/thirdparty/fribidi-shim/fribidi.c") + defs.append(("HAVE_RAQM", None)) + libs.append(feature.get("raqm")) + + if feature.get("raqm") and sys.platform == "win32": + # Raqm is linked statically on Windows, so link what it needs. + libs.extend(["harfbuzz", "SheenBidi"]) if sys.platform == "ios": # Ensure transitive dependencies are linked. libs.extend(["z", "bz2", "brotlicommon", "brotlidec", "png"]) - self._update_extension("PIL._imagingft", libs, defs, srcs) + self._update_extension("PIL._imagingft", libs, defs) else: self._remove_extension("PIL._imagingft") @@ -1017,12 +954,6 @@ def summary_report(self, feature: ext_feature) -> None: 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.get("jpeg"), "JPEG"), ( @@ -1034,7 +965,7 @@ def summary_report(self, feature: ext_feature) -> None: (feature.get("imagequant"), "LIBIMAGEQUANT"), (feature.get("tiff"), "LIBTIFF"), (feature.get("freetype"), "FREETYPE2"), - (feature.get("raqm"), "RAQM (Text shaping)", raqm_extra_info), + (feature.get("raqm"), "RAQM (Text shaping)"), (feature.get("lcms"), "LITTLECMS2"), (feature.get("webp"), "WEBP"), (feature.get("xcb"), "XCB (X protocol)"), diff --git a/src/PIL/features.py b/src/PIL/features.py index a7cf7318eaa..92dec76f119 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -10,9 +10,11 @@ import PIL from . import Image +from ._deprecate import deprecate TYPE_CHECKING = False if TYPE_CHECKING: + from types import FrameType from typing import IO modules = { "pil": ("PIL._imaging", "PILLOW_VERSION"), @@ -125,8 +127,8 @@ def get_supported_codecs() -> list[str]: features: dict[str, tuple[str, str, str | None]] = { "raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"), - "fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"), - "harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"), + "fribidi": ("PIL._imagingft", "HAVE_RAQM", None), + "harfbuzz": ("PIL._imagingft", "HAVE_RAQM", None), "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"), "mozjpeg": ("PIL._imaging", "HAVE_MOZJPEG", "libjpeg_turbo_version"), "zlib_ng": ("PIL._imaging", "HAVE_ZLIBNG", "zlib_ng_version"), @@ -134,15 +136,33 @@ def get_supported_codecs() -> list[str]: "xcb": ("PIL._imaging", "HAVE_XCB", None), } +# Raqm chooses its own bidi and shaping libraries, so Pillow cannot reliably +# report them. +deprecated_features = {"fribidi": "raqm", "harfbuzz": "raqm"} + + +def _deprecate_feature(feature: str) -> None: + if feature not in deprecated_features: + return + + # Report against the first frame outside this module, so that the warning + # points at the caller whichever entry point it used + stacklevel = 2 + frame: FrameType | None = sys._getframe() + while frame is not None and frame.f_globals.get("__name__") == __name__: + stacklevel += 1 + frame = frame.f_back + + replacement = deprecated_features[feature] + deprecate( + f'features.check("{feature}")', + None, + f'check("{replacement}")', + stacklevel=stacklevel, + ) -def check_feature(feature: str) -> bool | None: - """ - Checks if a feature is available. - :param feature: The feature to check for. - :returns: ``True`` if available, ``False`` if unavailable, ``None`` if unknown. - :raises ValueError: If the feature is not defined in this version of Pillow. - """ +def _feature_available(feature: str) -> bool | None: if feature not in features: msg = f"Unknown feature {feature}" raise ValueError(msg) @@ -159,13 +179,28 @@ def check_feature(feature: str) -> bool | None: return None +def check_feature(feature: str) -> bool | None: + """ + Checks if a feature is available. + + :param feature: The feature to check for. + :returns: ``True`` if available, ``False`` if unavailable, ``None`` if unknown. + :raises ValueError: If the feature is not defined in this version of Pillow. + """ + available = _feature_available(feature) + _deprecate_feature(feature) + return available + + def version_feature(feature: str) -> str | None: """ :param feature: The feature to check for. :returns: The version number as a string, or ``None`` if not available. :raises ValueError: If the feature is not defined in this version of Pillow. """ - if not check_feature(feature): + available = _feature_available(feature) + _deprecate_feature(feature) + if not available: return None module, flag, ver = features[feature] @@ -180,7 +215,9 @@ def get_supported_features() -> list[str]: """ :returns: A list of all supported features. """ - return [f for f in features if check_feature(f)] + return [ + f for f in features if f not in deprecated_features and _feature_available(f) + ] def check(feature: str) -> bool | None: @@ -303,11 +340,6 @@ def pilinfo(out: IO[str] | None = None, supported_formats: bool = True) -> None: zlib_ng_version = version_feature("zlib_ng") if zlib_ng_version is not None: v += ", compiled for zlib-ng " + zlib_ng_version - elif 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 dd795ed2e66..6664fe3908e 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -48,17 +48,7 @@ ; #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; @@ -1665,21 +1655,11 @@ setup_module(PyObject *m) { Py_DECREF(v); #ifdef HAVE_RAQM -#if defined(HAVE_RAQM_SYSTEM) || defined(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); Py_DECREF(v); if (have_raqm) { v = NULL; @@ -1691,32 +1671,6 @@ setup_module(PyObject *m) { #endif PyDict_SetItemString(d, "raqm_version", v ? v : Py_None); Py_XDECREF(v); - - v = NULL; -#ifdef FRIBIDI_MAJOR_VERSION - { - 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)); - if (!v) { - return -1; - } - } - } -#endif - PyDict_SetItemString(d, "fribidi_version", v ? v : Py_None); - Py_XDECREF(v); - - v = NULL; -#ifdef HB_VERSION_STRING - v = PyUnicode_FromString(hb_version_string()); - if (!v) { - return -1; - } -#endif - PyDict_SetItemString(d, "harfbuzz_version", v ? v : Py_None); - Py_XDECREF(v); } return 0; diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c deleted file mode 100644 index 7b1a8f8c51e..00000000000 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ /dev/null @@ -1,109 +0,0 @@ - -#ifndef _WIN32 -#include -#else -#define WIN32_LEAN_AND_MEAN -#include -#endif - -#define FRIBIDI_SHIM_IMPLEMENTATION -#include "fribidi.h" - - -/* FriBiDi>=1.0.0 adds bracket_types param, ignore and call legacy function */ -static 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); -} - -/* FriBiDi>=1.0.0 gets bracket types here, ignore */ -static 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; - const char **p_fribidi_version_info = 0; - - p_fribidi = 0; - - /* Microsoft needs a totally different system */ -#ifndef _WIN32 -#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); - } - if (!p_fribidi) { - p_fribidi = dlopen("/usr/local/lib/libfribidi.dylib", RTLD_LAZY); - } -#else -#define LOAD_FUNCTION(func) \ - func = (t_##func)GetProcAddress(p_fribidi, #func); \ - error = error || (func == 0); - - p_fribidi = LoadLibrary("fribidi"); - if (!p_fribidi) { - p_fribidi = LoadLibrary("fribidi-0"); - } - /* MSYS2 */ - if (!p_fribidi) { - p_fribidi = LoadLibrary("libfribidi-0"); - } -#endif - - if (!p_fribidi) { - return 1; - } - - /* 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<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_unicode_to_charset); - LOAD_FUNCTION(fribidi_charset_to_unicode); - LOAD_FUNCTION(fribidi_get_bidi_types); - LOAD_FUNCTION(fribidi_get_par_direction); - LOAD_FUNCTION(fribidi_get_par_embedding_levels); - -#ifndef _WIN32 - p_fribidi_version_info = (const char**)dlsym(p_fribidi, "fribidi_version_info"); - if (error || (p_fribidi_version_info == 0) || (*p_fribidi_version_info == 0)) { - dlclose(p_fribidi); - p_fribidi = 0; - return 2; - } -#else - p_fribidi_version_info = (const char**)GetProcAddress(p_fribidi, "fribidi_version_info"); - if (error || (p_fribidi_version_info == 0) || (*p_fribidi_version_info == 0)) { - FreeLibrary(p_fribidi); - p_fribidi = 0; - return 2; - } -#endif - fribidi_version_info = *p_fribidi_version_info; - - return 0; -} diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h deleted file mode 100644 index bbd3c23b98e..00000000000 --- a/src/thirdparty/fribidi-shim/fribidi.h +++ /dev/null @@ -1,118 +0,0 @@ - -#define FRIBIDI_MAJOR_VERSION 1 - -/* 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 -#ifdef _MSC_VER -#define FRIBIDI_ENTRY -#else -#define FRIBIDI_ENTRY __attribute__((visibility ("hidden"))) -#endif -#else -#define FRIBIDI_ENTRY extern -#endif - -#define FRIBIDI_FUNC(ret, name, ...) \ - typedef ret (*t_##name) (__VA_ARGS__); \ - FRIBIDI_ENTRY t_##name name; - -FRIBIDI_FUNC(FriBidiStrIndex, fribidi_unicode_to_charset, - FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); - -FRIBIDI_FUNC(FriBidiStrIndex, fribidi_charset_to_unicode, - FriBidiCharSet, const char *, FriBidiStrIndex, FriBidiChar *); - -FRIBIDI_FUNC(void, fribidi_get_bidi_types, - const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *); - -FRIBIDI_FUNC(FriBidiParType, fribidi_get_par_direction, - const FriBidiCharType *, const FriBidiStrIndex); - -FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, - const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, - FriBidiLevel *); - -/* FriBiDi>=1.0.0 */ -FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex, - const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex, - FriBidiParType *, FriBidiLevel *); - -/* FriBiDi>=1.0.0 */ -FRIBIDI_FUNC(void, fribidi_get_bracket_types, - const FriBidiChar *, const FriBidiStrIndex, const FriBidiCharType *, - FriBidiBracketType *); - -#undef FRIBIDI_FUNC - -/* constant, not a function */ -FRIBIDI_ENTRY const char *fribidi_version_info; - - - -/* shim */ - -FRIBIDI_ENTRY void *p_fribidi; - -FRIBIDI_ENTRY int load_fribidi(void); - -#undef FRIBIDI_ENTRY diff --git a/src/thirdparty/raqm/AUTHORS b/src/thirdparty/raqm/AUTHORS deleted file mode 100644 index bd5c3ac6b6a..00000000000 --- a/src/thirdparty/raqm/AUTHORS +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 99468479637..00000000000 --- a/src/thirdparty/raqm/COPYING +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright © 2015 Information Technology Authority (ITA) -Copyright © 2016-2026 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 deleted file mode 100644 index 8be10bf718c..00000000000 --- a/src/thirdparty/raqm/NEWS +++ /dev/null @@ -1,204 +0,0 @@ -Overview of changes leading to 0.11.0 -Thursday, July 23, 2026 -==================================== - -Update grapheme cluster boundary detection to Unicode 17, conforming to -Unicode UAX #29 and verified against the Unicode conformance test. - -Reject input that spans multiple paragraphs; raqm_layout() now fails when -the text contains a paragraph separator. - -Fix out-of-bounds read in the UTF-8 and UTF-16 decoders. - -Build, CI and documentation updates. - -New API: - * raqm_get_par_detected_direction - * raqm_allowed_grapheme_boundary - -Overview of changes leading to 0.10.5 -Saturday, April 11, 2026 -==================================== - -Check for NULL return from malloc in couple of places. - -Overview of changes leading to 0.10.4 -Thursday, February 5, 2026 -==================================== - -Add build option to skip tests. - -Add dependency override for use as a subproject. - -Fix tests when b_ndebug=true. - -Build, CI and documentation updates. - -Overview of changes leading to 0.10.3 -Tuesday, August 5, 2025 -==================================== - -Fix raqm_set_text_utf8/utf16 reading beyond len for multibyte. - -Support building against SheenBidi 2.9. - -Fix deprecation warning with latest HarfBuzz. - -Overview of changes leading to 0.10.2 -Sunday, September 22, 2024 -==================================== - -Fix Unicode codepoint conversion from UTF-16. - -Overview of changes leading to 0.10.1 -Wednesday, April 12, 2023 -==================================== - -Make combining marks always inherit the script of their base. - -Overview of changes leading to 0.10.0 -Wednesday, January 11, 2023 -==================================== - -Fix font feature ranges. - -Fix resolved direction for all-neutral text. - -Implement letter and word spacing support. - -New API: - * raqm_set_text_utf16 - -Overview of changes leading to 0.9.0 -Sunday, January 30, 2022 -==================================== - -Raise the minimum versions of Raqm dependencies: no longer conditionally -enabling any features based on specific dependency version. - -raqm_t objects can now be reused by calling raqm_clear_contents() before -re-use, to potentially reduce the number memory allocations. - -Don't hardcode python3 in tests. - -New API: - * raqm_set_freetype_load_flags_range - * raqm_clear_contents - -Overview of changes leading to 0.8.0 -Monday, December 13, 2021 -==================================== - -Remove autotools build. - -Support using SheenBiDi instead of FriBiDi for Unicode BiDi support. - -Fix running tests with Python <= 3.6. - -New API: - * raqm_get_par_resolved_direction - * raqm_get_direction_at_index - -Overview of changes leading to 0.7.2 -Monday, September 27, 2021 -==================================== - -Fix test failure with newer HarfBuzz versions. - -Apply FT_Face transformation matrix when built against FreeType 2.11 or later. - -Add meson build system. Autotools build system will be dropped in next release. - -Improve MSVC support. - -Build and documentation fixes. - -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.md b/src/thirdparty/raqm/README.md deleted file mode 100644 index a24c73c57c3..00000000000 --- a/src/thirdparty/raqm/README.md +++ /dev/null @@ -1,78 +0,0 @@ -Raqm -==== - -[![Build](https://github.com/HOST-Oman/libraqm/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/HOST-Oman/libraqm/actions) - -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] or -[SheenBidi][2]), shaping (using [HarfBuzz][3]), 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: -> https://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][4] -* [HarfBuzz][3] -* [FriBiDi][1] or [SheenBidi][2] - -To build the documentation you will also need: -* [GTK-Doc][5] - -To install dependencies on Fedora: - - sudo dnf install freetype-devel harfbuzz-devel fribidi-devel meson gtk-doc - -To install dependencies on Ubuntu: - - sudo apt-get install libfreetype6-dev libharfbuzz-dev libfribidi-dev meson gtk-doc-tools - -On Mac OS X you can use Homebrew: - - brew install freetype harfbuzz fribidi meson 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: - - $ meson build - $ ninja -C build - $ ninja -C build install - -To build the documentation, pass `-Ddocs=true` to the `meson`. - -To run the tests: - - $ ninja -C build test - -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) -6. [CEGUI](https://github.com/cegui/cegui) - - -[1]: https://github.com/fribidi/fribidi -[2]: https://github.com/Tehreer/SheenBidi -[3]: https://github.com/harfbuzz/harfbuzz -[4]: https://freetype.org/ -[5]: https://www.gtk.org/gtk-doc diff --git a/src/thirdparty/raqm/grapheme-data.h b/src/thirdparty/raqm/grapheme-data.h deleted file mode 100644 index 10be59f01cd..00000000000 --- a/src/thirdparty/raqm/grapheme-data.h +++ /dev/null @@ -1,398 +0,0 @@ -/* Generated by scripts/gen-grapheme-data.py from Unicode 17.0.0 */ -/* DO NOT EDIT MANUALLY */ - -#ifndef _RAQM_GRAPHEME_DATA_H_ -#define _RAQM_GRAPHEME_DATA_H_ - -typedef enum -{ - RAQM_GRAPHEME_OTHER, - RAQM_GRAPHEME_CR, - RAQM_GRAPHEME_LF, - RAQM_GRAPHEME_CONTROL, - RAQM_GRAPHEME_EXTEND, - RAQM_GRAPHEME_ZWJ, - RAQM_GRAPHEME_REGIONAL_INDICATOR, - RAQM_GRAPHEME_PREPEND, - RAQM_GRAPHEME_SPACING_MARK, - RAQM_GRAPHEME_L, - RAQM_GRAPHEME_V, - RAQM_GRAPHEME_T, - RAQM_GRAPHEME_LV, - RAQM_GRAPHEME_LVT, - RAQM_GRAPHEME_EXTENDED_PICTOGRAPHIC -} _raqm_grapheme_t; - -typedef enum -{ - RAQM_INCB_NONE, - RAQM_INCB_CONSONANT, - RAQM_INCB_LINKER, - RAQM_INCB_EXTEND -} _raqm_incb_t; - -#include - -static const uint8_t _raqm_u8[4446]= -{ - 33, 0, 48, 84,118,128,144,186, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 0, 19, 20, 21, 22, 0, 23, 24, 25, 26, 27, 28, 29, - 0, 30, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 33, 34, 2, - 3, 4, 5, 6, 7, 8, 2, 3, 4, 5, 6, 7, 8, 2, 3, 4, - 5, 6, 7, 8, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 36, 0, 37, 38, 39, 0, 0, 0, 40, 41, - 42, 43, 44, 45, 46, 47, 48, 49, 50, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 51, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 53, 54, - 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, - 57, 58, 59, 0, 0, 0, 60, 0, 0, 61, 62, 63, 64, 65, 0, 0, - 0, 66, 67, 68, 69, 70, 71, 72, 73, 74, 1, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, - 1, 3, 4, 5, 6, 7, 8, 9, 3, 4, 5, 6, 7, 8, 9, 3, - 4, 5, 6, 7, 8, 9, 3, 4, 5, 6, 7, 8, 9, 3, 4, 5, - 6, 7, 8, 9, 3, 4, 5, 6, 7, 8, 9, 3, 4, 5, 6, 7, - 8, 9, 3, 4, 5, 6, 7, 8, 9, 18, 11, 19, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 10, 20, 0, 0, 0, 0, 21, 0, 0, 0, 22, - 23, 24, 25, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, - 38, 12, 39, 12, 40, 41, 42, 43, 44, 45, 46, 47, 48, 13, 49, 50, - 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 0, 63, 64, 65, - 66, 0, 0, 0, 0, 0, 67, 0, 0, 0, 0, 0, 0, 68, 69, 70, - 71, 72, 0, 73, 0, 74, 0, 0, 0, 75, 76, 77, 78, 79, 80, 81, - 82, 83, 0, 0, 84, 0, 0, 0, 10, 85, 86, 0, 87, 88, 0, 89, - 0, 0, 0, 0, 0, 90, 0, 0, 91, 0, 0, 0, 92, 0, 0, 93, - 94, 95, 96, 97, 98, 99,100,101, 0, 0, 0, 0, 0,102, 0, 0, - 0, 0, 0, 0, 0,103,104, 0, 0, 0, 0, 0,105, 0,106, 0, - 107,108, 0,109, 0, 0, 0, 0, 0, 0, 0,110, 0, 0, 0, 0, - 0, 0,111, 14,112, 0, 0, 0, 0,113, 0,114,115,116,117,118, - 119,120,121,122,123, 0, 0, 0,124, 3, 4, 5, 6, 7, 8,125, - 126, 0, 0, 0, 0,127, 0, 0, 0,128, 0, 0, 11, 0, 0, 14, - 129, 0, 0, 0, 0, 0, 0, 0,130, 0, 0, 0,131, 0,132, 0, - 0,133, 0, 0,134, 0, 0, 0, 0, 0, 0, 0, 0,135,136, 0, - 0, 0, 0,137,138, 0,139,140, 0,141,142,143,144,145,146,147, - 148,149,150, 0,151, 13,152,153,154,155,156,157,158, 0, 0,159, - 160,161,162,163, 0,164, 0, 0, 0,165, 0, 0, 0,166,167, 0, - 168,169,170,171, 0, 0,172, 0, 0,173, 0,174, 0,175,176,177, - 0, 0, 0, 0,178,179,180, 0, 0,181,182, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0,183, 0, 0, 0, 0, 0, 0,184, 15, 0, 0, - 0, 0, 0, 0, 0, 0,185, 0, 0, 0, 0, 0, 0, 0,186,187, - 188, 0, 0,189, 0, 0, 0, 0, 0, 0, 0, 0, 0,190,191, 0, - 0, 0, 0, 0, 0, 0,192,193, 0, 0,194, 0, 0, 0, 0, 0, - 0,195,196,197, 0, 0, 0, 0, 0,198, 0,199, 0, 15, 0, 0, - 0, 0, 0,200, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, - 201, 0, 0, 0,202, 0, 0, 0, 0, 0, 0, 0,203, 0,204, 0, - 0,205, 0,206,207, 0,208,209,210,211,212, 2, 2,213, 2,214, - 215, 2, 2, 2,216, 17,217,218,219, 2,220, 2,221, 0, 0, 0, - 222,223,224,225,226,227,228, 2, 2, 0,229, 2, 2, 0, 0, 0, - 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 17,230, 10, 1, 1, 10, 10, 10,231, 0, 0, 0, 0, 1, 1, 1, - 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 1, 1, 1, 1, 1, 1, - 4, 1, 1, 4, 1, 1, 4, 1, 1, 5, 5, 5, 5, 9, 0, 0, - 0, 7, 0, 0, 0, 10, 10, 10, 10, 8, 8, 8, 8, 0, 0, 0, - 6, 0, 6, 0, 0, 16, 16, 16, 16, 5, 12, 0, 0, 0, 0, 6, - 5, 19, 0, 0, 0, 0, 0, 7, 5, 47, 0, 0, 0, 0, 0, 0, - 20, 0, 29, 0, 0, 0, 0, 0, 7, 0, 0, 0, 3, 49, 0, 0, - 0, 0, 0, 0, 18, 0, 0, 24, 3, 13, 0, 0, 0, 11, 0, 0, - 0, 0, 19, 0, 0, 12, 0, 0, 0, 5, 9, 0, 0, 0, 0, 0, - 17, 0, 0, 3, 3, 0, 0, 0, 52, 14, 5, 5, 5, 0, 7, 5, - 5, 5, 0, 0, 19, 26, 0, 0, 0, 27, 56, 48, 38, 57, 0, 0, - 0, 0, 0, 0, 46, 0, 0, 0, 31, 23, 0, 0, 0, 0, 0, 6, - 55, 5, 5, 5, 9, 0, 0, 0, 14, 0, 0, 0, 19, 7, 11, 0, - 0, 7, 9, 0, 0, 5, 5, 5, 11, 5, 0, 0, 0, 0, 0, 22, - 0, 28, 0, 0, 0, 0, 0, 40, 0, 0, 0, 17, 0, 18, 13, 0, - 0, 0, 24, 0, 0, 13, 0, 0, 18, 0, 0, 0, 32, 15, 15, 15, - 15, 0, 9, 0, 0, 0, 0, 0, 5, 0, 17, 3, 3, 3, 3, 3, - 21, 0, 0, 17, 3, 2, 2, 79, 80, 0, 0, 22, 81, 6, 5, 11, - 0, 5, 5, 5, 36, 29, 36, 0, 0, 64, 65, 0, 0, 5, 5, 12, - 82, 0, 7, 5, 83, 5, 37, 31, 11, 0, 0, 0, 84, 5, 5, 12, - 0, 0, 7, 36, 5, 5, 14, 14, 11, 0, 0, 14, 0, 65, 6, 5, - 5, 66, 5, 5, 5, 0, 0, 53, 46, 27, 5, 33, 54, 14, 5, 0, - 0, 0, 0, 0, 55, 34, 6, 37, 11, 11, 19, 0, 0, 27, 36, 85, - 38, 0, 14, 0, 0, 20, 0, 0, 0, 0, 0, 0, 53, 58, 35, 67, - 38, 33, 9, 0, 0, 33, 68, 31, 11, 0, 0, 0, 69, 59, 68, 31, - 11, 39, 0, 0, 0, 27, 46, 67, 86, 0, 0, 20, 6, 30, 60, 15, - 32, 35, 0, 0, 0, 70, 5, 12, 0, 0, 6, 5, 12, 70, 5, 5, - 9, 0, 0, 5, 12, 0, 0, 11, 0, 0, 71, 19, 35, 14, 5, 5, - 26, 5, 31, 0, 14, 5, 5, 14, 5, 0, 20, 0, 0, 43, 5, 57, - 34, 0, 35, 11, 7, 14, 9, 0, 0, 20, 34, 0, 19, 16, 16, 8, - 8, 0, 44, 5, 23, 15, 72, 27, 5, 0, 0, 6, 87, 0, 0, 19, - 0, 26, 32, 33, 0, 72, 15, 27, 0, 0, 6, 61, 0, 0, 88, 5, - 12, 60, 14, 5, 33, 32, 5, 5, 37, 5, 5, 5, 0, 5, 73, 0, - 0, 0, 5, 5, 23, 30, 9, 0, 0, 74, 23, 5, 11, 0, 53, 23, - 50, 0, 15, 15, 5, 5, 30, 0, 0, 12, 5, 5, 5, 43, 5, 9, - 19, 0, 56, 11, 0, 0, 0, 52, 89, 0, 0, 2, 90, 0, 0, 0, - 13, 0, 3, 21, 0, 0, 0, 13, 0, 3, 0, 25, 0, 0, 28, 0, - 0, 0, 0, 18, 25, 3, 13, 0, 28, 22, 21, 13, 22, 91, 28, 28, - 17, 0, 0, 25, 0, 62, 0, 3, 3, 3, 0, 0, 18, 45, 40, 13, - 0, 0, 0, 18, 17, 17, 3, 51, 13, 21, 18, 17, 0, 21, 0, 0, - 40, 0, 21, 13, 17, 51, 13, 0, 0, 3, 41, 25, 22, 28, 22, 3, - 41, 28, 62, 0, 22, 22, 0, 13, 0, 0, 45, 0, 62, 18, 41, 0, - 0, 22, 0, 0, 0, 0, 21, 0, 0, 0, 0, 18, 13, 13, 22, 0, - 0, 13, 0, 0, 22, 0, 0, 29, 0, 0, 18, 22, 0, 12, 5, 5, - 11, 20, 20, 6, 0, 39, 75, 0, 9, 92, 0, 0, 0, 0, 15, 15, - 15, 15, 11, 0, 0, 11, 0, 0, 6, 0, 7, 5, 11, 0, 6, 5, - 5, 44, 0, 0, 0, 10, 10, 10, 93, 6, 30, 23, 23, 0, 0, 14, - 26, 75, 34, 0, 0, 6, 0, 0, 94, 0, 0, 0, 9, 31, 37, 9, - 7, 0, 0, 39, 23, 0, 95, 0, 0, 39, 54, 58, 38, 1, 0, 0, - 0, 16, 76, 96, 8, 8, 8, 8, 0, 2, 2, 2, 0, 0, 7, 12, - 0, 14, 29, 0, 5, 0, 0, 12, 6, 0, 5, 0, 0, 0, 0, 14, - 11, 0, 0, 6, 9, 97, 0, 0, 0, 0, 0, 5, 5, 37, 9, 0, - 6, 32, 26, 34, 77, 20, 0, 0, 77, 0, 6, 5, 27, 0, 98, 0, - 0, 6, 0, 0, 0, 39, 30, 5, 26, 99, 0, 14, 69, 23, 5, 0, - 20, 32, 5, 12, 0, 33, 48, 48, 38, 35, 7, 5, 9, 0, 0, 61, - 5, 60, 71, 49, 30,100, 0, 0, 0, 29, 0, 0, 0, 0, 63, 5, - 5, 30,101, 0, 0, 61, 5, 59, 50, 43, 0, 0, 0, 30, 11, 15, - 44, 0, 0, 0, 11, 32, 5, 26, 50, 0, 0, 6, 54, 5, 5, 0, - 0, 0, 0, 0,102, 7, 44, 5, 0, 5, 5, 34, 0, 33,103,104, - 105,106, 0, 0, 0, 63, 5, 7, 15, 9, 73, 0, 0, 14, 5, 12, - 0, 6, 5,107, 12, 14, 26, 27, 0, 0, 64,108, 5, 5, 26, 11, - 0, 43, 59, 0, 0, 0, 0, 0, 39, 5, 12, 5, 44, 7, 5, 5, - 5, 5, 5, 74, 5, 43, 34, 0, 0, 14, 12, 20, 36, 5, 66, 0, - 0, 0, 0, 35,109,110, 50, 0, 0, 6, 58, 0, 0,111, 0, 0, - 0, 0, 30, 12, 35, 0, 0, 20, 0, 9, 6, 5, 5, 5, 11, 0, - 0, 5, 5, 23, 27, 78, 78, 76, 0, 63, 15, 15, 15, 15, 15, 0, - 6, 0, 0, 0, 29, 2, 0, 0, 0, 0, 14, 11, 14,112, 2,113, - 5, 12, 14, 5, 0, 0, 0, 7, 11, 5, 12, 6, 5, 5, 12, 5, - 5, 5, 5, 37, 5, 36, 31, 12, 0, 6, 20, 0, 7, 0, 5, 12, - 0, 0, 13, 0, 0, 0, 3, 3, 3, 21, 0, 0, 17, 0, 0, 0, - 28, 24, 3, 25, 0, 3,114, 42, 42, 42, 42, 42, 42, 24, 3, 3, - 3, 0, 0, 28, 0, 17, 3, 25, 3, 21, 3, 3, 3, 3, 17, 24, - 17, 45, 41,115, 5, 3, 3, 3, 41, 0, 0, 24, 25, 3, 3, 0, - 18, 45, 3, 25, 0, 0, 18, 17, 21, 13, 40, 0, 0, 0, 21, 13, - 0, 40, 0, 0, 13, 17, 13, 0, 0, 24, 0, 0, 25, 51, 0, 13, - 18, 18, 0, 17, 3, 3, 21, 18, 3, 25, 24, 3, 3, 3, 21, 51, - 3, 45, 3, 3, 3, 17, 3, 3, 3, 3, 3, 25, 3, 3, 41, 3, - 3, 0, 0,221,221, 51, 51,238,238,220,221, 68, 68, 0, 64, 0, - 68,187,187, 4, 0,153,153, 68, 0, 68, 4, 14, 0, 64, 68,136, - 136,170,170, 0,238, 0,224, 64, 0, 0, 4,238, 0,224, 0, 68, - 136,224,238,238, 14, 68,132, 72, 68, 0, 14, 64, 4,136, 68, 4, - 68,136, 72,132,136, 72, 4, 0,136, 68, 64, 4, 64, 72, 0, 0, - 128,224, 14,238,224,102,102,132, 68, 68, 72, 14,224, 4,136, 64, - 136, 8,128, 68, 8, 72, 72,224,224, 0, 48, 0,132, 72,136, 4, - 132, 4,128, 64,132,132, 8,132,132, 4, 4,132, 72, 14, 14,128, - 136,119,119,119, 0, 68, 71, 8,136, 8, 68, 4, 72, 64,128, 64, - 64,136,132, 8, 0,128, 68, 72,132,170, 10,112, 0, 0,160, 51, - 50, 19, 51, 48, 14, 3, 0,116, 64, 0,112,132,128, 72, 7, 68, - 67,128,132, 84, 51, 51, 3, 14,238,136, 0, 9, 0,132, 0,128, - 4, 0,176, 72, 8,128, 8, 4,119,116, 4,132, 4, 64, 72,136, - 128, 8, 64, 68,116,120, 72,132, 64,119, 68,136, 8, 68,128, 68, - 135, 68, 52, 51, 67,238,102,238, 78, 33, 0, 48, 64,101,112,128, - 169, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 10, 11, 12, - 13, 0, 14, 0, 0, 0, 0, 0, 15, 0, 16, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 17, 18, 19, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, - 0, 21, 22, 23, 0, 0, 0, 24, 25, 26, 27, 28, 29, 30, 31, 32, - 33, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, - 0, 0, 36, 0, 0, 0, 0, 37, 0, 38, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 39, 0, 0, 0, 0, 0, 0, 0, 0, 40, 41, 42, 0, 0, 0, 43, - 0, 0, 44, 45, 46, 47, 48, 0, 0, 0, 0, 49, 0, 0, 0, 0, - 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 8, 0, 0, 0, 0, 9, 0, 0, 0, 10, 11, 12, 13, - 0, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, - 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 2, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, 52, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 53, 0, 0, 0, 0, 0, 0, 54, 55, 56, 57, 58, 0, - 59, 0, 60, 0, 0, 0, 61, 62, 63, 64, 65, 66, 67, 68, 69, 0, - 0, 70, 0, 0, 0, 1, 71, 0, 0, 72, 0, 0, 0, 0, 0, 0, - 0, 73, 0, 74, 0, 3, 75, 0, 76, 0, 0, 0, 0, 0, 0, 77, - 4, 78, 0, 0, 0, 0, 79, 0, 0, 80, 81, 82, 83, 84, 85, 86, - 87, 88, 0, 0, 0, 89, 0, 0, 0, 0, 90, 0, 0, 0, 91, 0, - 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 92, 0, 0, - 0, 93, 0, 94, 0, 0, 95, 0, 0, 96, 0, 0, 0, 0, 0, 0, - 0, 0, 97, 98, 0, 0, 0, 0, 99,100, 0,101,102, 0,103,104, - 105,106,107,108,109,110,111, 2, 0,112,113,114,115,116,117,118, - 119,120, 0, 0,121,122,123,124,125, 0,126, 0, 0, 0,127, 0, - 0, 0,128,129, 0,130,131,132,133, 0, 0,134, 0, 0,135, 0, - 136, 0,137,138,139, 0, 0, 0, 0,140,141,142, 0, 0, 0,143, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,144, 0, 0, 0, 0, 0, - 0,145, 5, 0, 0, 0, 0, 0, 0, 0, 0, 6,146,147, 0, 0, - 148, 0, 0, 0, 0, 0, 0, 0, 0, 0,149,150, 0, 0, 0, 0, - 0, 0, 0,151,152, 0, 0,153, 0, 0, 0, 0, 0, 0,154,155, - 156, 0, 0, 0, 0, 0,157, 0, 6, 0, 5, 0, 0, 0, 0, 0, - 158, 7, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0,159, 0, 0, - 0,160, 0, 0, 0, 0, 0, 0, 0,161, 0,162, 0, 0, 0, 0, - 0, 0, 0, 0, 0,163, 3, 1, 0, 0, 1, 1, 1, 1, 0, 0, - 0, 0, 1, 1, 1, 1, 4, 0, 0, 0, 0, 0, 1, 1, 0, 13, - 0, 0, 0, 0, 0, 10, 3, 0, 0, 0, 0, 0, 20, 0, 1, 1, - 1, 0, 38, 0, 0, 0, 0, 21, 1, 39, 40, 0, 0, 0, 0, 22, - 0, 0, 9, 1, 0, 6, 0, 41, 42, 0, 0, 4, 0, 1, 22, 0, - 0, 0, 0, 0, 23, 6, 0, 0, 9, 43, 0, 44, 45, 0, 0, 46, - 0, 0, 0, 24, 0, 0, 14, 1, 47, 1, 25, 8, 2, 48, 49, 50, - 5, 51, 4, 8, 11, 52, 15, 53, 5, 54, 16, 0, 0, 55, 56, 4, - 0, 57, 16, 8, 11, 58, 59, 0, 5, 60, 4, 8, 11, 61, 15, 62, - 5, 63, 26, 0, 0, 12, 27, 17, 0, 0, 64, 8, 11, 65, 66, 67, - 5, 0, 4, 0, 0, 68, 69, 18, 5, 0, 7, 8, 2, 70, 15, 17, - 5, 0, 71, 72, 0, 0, 0, 0, 0, 73, 74, 0, 0, 0, 0, 0, - 0, 75, 76, 0, 0, 0, 0, 77, 0, 78, 0, 0, 0, 79, 80, 81, - 1, 28, 82, 0, 0, 0, 2, 2, 83, 84, 0, 85, 86, 87, 88, 19, - 0, 0, 0, 89, 0, 0, 0, 29, 0, 30, 0, 5, 0, 5, 2, 2, - 2, 90, 91, 92, 0, 0, 93, 0, 0, 0, 18, 0, 94, 0, 0, 0, - 95, 96, 0, 97, 2, 2, 2, 98, 99,100, 0, 0, 0, 1, 1, 31, - 101, 0,102,103, 2,104,105, 0, 9, 32,106, 2,107,108, 0, 0, - 109, 32, 0, 0, 20,110, 0,111,112,113, 19, 0, 0, 0, 0, 1, - 1, 6, 0, 0, 3, 7, 0, 0, 0, 3, 0, 0, 14, 0, 0,114, - 0, 0, 0, 0, 3,115, 0, 0, 0, 7,116, 0,117, 0,118, 0, - 1,119, 0, 0,120, 0, 24,121, 0, 0,122, 2, 2,123,124, 0, - 125,126, 0, 0,127,128,129, 0, 2,130, 0, 0, 0,131, 4, 0, - 132,133, 2,134,135, 0, 0, 12, 0, 0, 1, 0, 1, 0, 0, 0, - 0, 19, 0, 0, 6, 0, 0, 0, 0,136,137,138, 2,139, 0, 0, - 18, 0, 0, 0,140, 0, 0, 0,141, 0, 0, 0,142, 0, 0, 0, - 0, 14, 23, 6, 0, 0, 29, 0, 0, 0, 4, 0, 0, 33, 10, 0, - 0,143, 7, 0, 0,144, 26, 0, 0, 0,145, 2,146,147,148, 0, - 0, 34, 7, 0, 0,149,150, 0, 0, 0, 0, 0, 3,151, 0, 3, - 152, 0, 7, 0, 0,153, 27, 17,154, 35,155, 2, 2,156,157,158, - 16, 0, 0, 0, 0, 33,159, 12, 0, 0, 0, 0, 0,160,161, 0, - 0, 0, 0, 0, 3,162, 6,163, 0, 0, 0, 0, 0,164, 6, 0, - 0, 0, 0, 0,165,166, 0,167,168, 0, 0, 0, 3,169,170,171, - 2,172, 34, 0, 0, 0, 0,173, 6, 0,174, 2, 2,175,176,177, - 2, 2,178,179, 0, 0, 0, 0,180, 0, 0, 0, 0,181, 0,182, - 183,184, 0, 0, 0,185,186, 0, 0, 0, 0,187, 0, 0, 0, 0, - 0,188,189,190, 2,191,192,193, 0, 0,194,195, 0, 0, 0, 13, - 196, 0, 0, 0, 0, 35, 3, 25, 0, 0, 0, 0, 36, 7, 0,197, - 0, 0, 1, 1, 31, 1, 10, 0, 0, 0, 0, 0,198,199,200, 0, - 201, 0, 30, 0, 0, 0, 1, 1, 1,202, 1, 1, 28, 37, 36, 9, - 21, 0,203,204,205, 0, 0, 0, 12, 0, 0, 0, 13, 0, 0, 0, - 206, 37, 0, 10, 0, 0,207, 0, 0, 0, 0, 0, 0, 9, 0, 0, - 0, 0,255,255,255,255, 85, 85, 85, 85, 0, 0, 0,192, 12, 0, - 0, 0,240, 0, 0, 0, 3, 0, 0, 0, 15, 0, 0, 0, 0, 84, - 85, 85, 0, 0,192,255,255, 63, 0, 0, 85, 85, 81, 85, 0, 0, - 0, 48, 0, 0, 0,240, 0, 0,240,255,252, 3, 0, 8, 60, 0, - 0, 0, 0,192, 0, 0, 0, 60, 0, 0, 0, 0, 0, 12, 0, 0, - 0,255,252,255,255,255,255,255, 63, 0, 0,240,255,255, 0,192, - 255,255, 63, 0, 0, 0, 48, 0, 0, 0, 3, 0, 0, 12,255,255, - 255, 3,240, 15, 0, 0,240, 3, 0, 0,255,255,255, 15,255, 0, - 0, 0, 0, 0,255,255,192, 0, 0, 0,255, 3, 0, 0, 0, 3, - 0, 0, 0, 12, 0, 0,192,255, 15, 0,255,255,255,207, 60,207, - 0, 0, 0,240,255,195,255,195,243, 15,255, 0, 0, 12, 0,240, - 207,255,255,252,252, 15, 0, 0,252, 0,207,255,255,255, 85, 85, - 53, 3,252,255, 3, 8,252,255, 85, 85, 0, 0, 85, 85, 17, 80, - 5, 51, 0,192, 0, 69, 5, 0, 0, 48, 0, 0, 0, 3, 60,192, - 195, 15, 15, 12, 0, 0, 81, 84, 5, 3,252,207, 3, 8, 0, 0, - 244,255, 81, 84, 5,243, 0,252, 0, 69, 4, 0, 0, 0, 3, 3, - 0, 0, 85, 85, 5,243, 3,240,243, 11, 0, 60, 21, 0, 0, 0, - 0,195, 51,240,243, 15, 85, 85,213, 51, 0, 0, 48,192,240, 51, - 0,192, 12,255, 63, 0, 0,192,255, 63, 12,255,255, 3, 0, 0, - 255, 63, 0, 0, 15, 0, 0,204, 12, 0,252,255,255, 63,255,243, - 0,252,255,255,252,255, 0, 48, 0, 0, 85, 85, 21,252,243,255, - 56,124, 85, 5, 95,245, 7, 20, 0, 80,253, 87, 85, 85, 53, 60, - 0, 28, 0, 0, 0,252, 85,207,255, 15, 0, 48,252,255,239, 0, - 0, 12, 0, 0,192,207, 0, 0, 12, 0, 63,192, 3, 0, 48, 0, - 252, 0, 0,192,195, 0, 85, 49,255, 63, 50,252,255, 3,192,255, - 255,195,255,255,255, 0,255, 0, 64, 1, 64, 85, 85, 85, 85,255, - 255, 15,240, 86, 85, 1, 79, 85, 85, 85,241, 15,191, 95, 0, 0, - 64, 5, 0, 48, 15,204,255,240, 0, 0, 63,255,255,255,243,255, - 3, 12, 0, 3, 15, 0, 0, 0, 60, 0, 63,255,255, 15, 48, 48, - 192, 0, 0, 60, 0, 3, 0, 15, 0, 0, 15, 0, 0,192, 0,240, - 255, 15,207, 0, 0, 0, 63, 0, 84, 64,213,240, 15, 15, 2, 0, - 0, 0, 85, 77, 85, 85, 0, 0, 80, 21, 0, 0,252, 63, 60, 60, - 0, 0,192, 0, 0, 3, 84, 0, 16, 83,243,195, 3,240, 85, 85, - 21, 15, 0, 32, 0, 0, 85, 85, 21, 0, 0, 12, 3, 12, 0,240, - 63, 0,253, 60, 0,255, 85, 84, 84, 85, 85, 5, 63,128, 0,255, - 0, 0, 0, 0,252, 15, 0, 0,192, 3,195, 3, 0,192,192, 63, - 60, 0,127, 85, 85, 85, 85,213,255,252,191, 3, 0, 0, 0, 65, - 0, 0, 0,240,255, 63, 3, 0,252,195, 15,255, 0, 48,192,255, - 63, 0, 0, 0,192, 51, 0,240,255, 3, 85, 85, 69, 16, 85, 5, - 195,255, 51,204, 15,240, 50, 0, 0, 0,240, 51, 0, 0,195,255, - 51,204,243, 0, 0, 0,240, 15, 0,207, 0, 0, 0, 15,192,255, - 63,204, 0, 0,192, 12,255,255, 0, 0, 0, 0, 0,204,240,207, - 255, 0,255,255, 60, 0, 85, 21, 4, 85, 85, 20, 85, 85, 3, 0, - 192, 47, 0,255,240, 0,253,255,127, 85,213,255,195, 63, 0,128, - 0, 0,253, 63,252, 85, 85, 0,240,255,255, 63, 11, 0,243, 51, - 0, 0,255, 63,255,207,240,255,255,255,255,255,240,255,243, 60, - 0, 0,252, 63, 48,207,255,207, 0, 0, 15,204, 0, 0,192, 3, - 0, 0, 15, 85, 85, 85, 81, 85, 85, 85, 85,240, 63, 0, 47, 0, - 0, 0, 0, 0, 48, 0, 3,192,255,255,255, 15, 0, 0,255,255, - 15,252, 0, 0, 0, 60, 0,252, 15,252, 63, 0,192,255, 63,252, - 255, 0, 0, 0,240, 15,255, 63,192,255,255, 63,255,255,255,255, - 195,255,207,243, 63, 0,192, 48, 0,240, 0,255, 63, 0, -}; -static const uint16_t _raqm_u16[928]= -{ - 0, 0, 0, 0, 8, 8, 8, 8, 12, 12, 12, 12, 16, 20, 4, 24, - 4, 28, 4, 16, 20, 4, 24, 4, 28, 4, 16, 20, 4, 24, 4, 28, - 4, 16, 20, 4, 24, 4, 28, 4, 32, 32, 32, 32, 0, 0, 0, 140, - 164, 0, 0, 168, 176, 0, 0, 180, 0, 92, 0, 0, 0, 0, 0, 64, - 0, 0, 252, 0, 12, 12, 12, 260, 268, 8, 0, 0, 8, 8, 272, 0, - 32, 32, 32, 0, 276, 0, 0, 0, 0, 144, 32, 280, 284, 0, 0, 0, - 288, 292, 0, 0, 68, 32, 0, 36, 0, 296, 300, 0, 304, 72, 0, 32, - 308, 0, 0, 0, 0, 0, 148, 36, 0, 0, 68, 152, 0, 312, 316, 0, - 0, 320, 0, 0, 0, 324, 0, 0, 76, 32, 328, 32, 156, 0, 0, 332, - 336, 340, 40, 0, 80, 0, 0, 344, 160, 56, 40, 84, 348, 72, 0, 352, - 356, 0, 40, 76, 80, 0, 0, 172, 160, 360, 40, 0, 364, 0, 0, 368, - 372, 56, 0, 0, 376, 0, 0, 172, 380, 88, 40, 0, 80, 0, 0, 384, - 388, 88, 40, 392, 396, 56, 40, 0, 80, 0, 0, 0, 400, 404, 0, 408, - 0, 0, 0, 412, 416, 0, 0, 0, 0, 0, 0, 420, 424, 0, 0, 0, - 0, 428, 0, 432, 0, 0, 0, 436, 440, 444, 32, 184, 448, 0, 0, 0, - 0, 0, 188, 452, 0, 456, 36, 460, 464, 192, 0, 0, 44, 44, 44, 44, - 44, 44, 60, 60, 60, 60, 468, 48, 48, 48, 48, 48, 0, 188, 0, 0, - 0, 196, 0, 200, 0, 40, 0, 40, 0, 0, 0, 472, 476, 152, 0, 0, - 480, 0, 0, 0, 88, 0, 484, 0, 0, 0, 488, 492, 0, 496, 0, 0, - 0, 500, 504, 508, 0, 0, 0, 32, 32, 204, 512, 0, 516, 0, 0, 520, - 524, 0, 68, 208, 100, 0, 528, 0, 0, 0, 532, 208, 0, 0, 536, 540, - 0, 544, 548, 552, 556, 0, 560, 564, 212, 0, 8, 0, 0, 32, 32, 36, - 0, 0, 216, 212, 0, 568, 220, 0, 0, 224, 572, 0, 104, 0, 108, 576, - 216, 0, 0, 0, 0, 0, 224, 580, 112, 0, 0, 584, 588, 592, 596, 600, - 604, 608, 612, 616, 0, 620, 624, 628, 632, 636, 220, 640, 644, 648, 652, 228, - 656, 660, 228, 0, 0, 232, 664, 236, 0, 0, 0, 668, 232, 672, 0, 0, - 0, 676, 0, 0, 0, 0, 52, 116, 0, 0, 0, 52, 0, 0, 32, 32, - 0, 0, 76, 680, 0, 684, 0, 0, 0, 688, 0, 0, 0, 0, 52, 692, - 0, 0, 0, 116, 696, 0, 700, 0, 704, 0, 0, 708, 712, 0, 32, 716, - 0, 0, 720, 0, 724, 728, 44, 732, 156, 0, 0, 736, 36, 0, 120, 0, - 0, 0, 740, 744, 748, 0, 0, 752, 0, 0, 0, 756, 72, 0, 760, 764, - 0, 0, 768, 0, 24, 4, 772, 60, 776, 48, 48, 780, 0, 84, 0, 0, - 32, 0, 32, 0, 0, 0, 0, 784, 0, 0, 0, 192, 0, 0, 36, 0, - 0, 0, 0, 788, 792, 0, 0, 796, 0, 0, 88, 0, 0, 0, 800, 0, - 0, 0, 804, 0, 0, 0, 808, 0, 0, 0, 0, 76, 148, 36, 0, 0, - 196, 0, 0, 0, 812, 0, 0, 816, 64, 0, 0, 820, 100, 0, 0, 824, - 828, 0, 0, 0, 124, 0, 832, 128, 836, 0, 0, 840, 100, 0, 0, 844, - 848, 0, 0, 0, 0, 0, 240, 852, 72, 0, 0, 0, 0, 52, 856, 0, - 860, 56, 864, 128, 0, 0, 0, 868, 872, 876, 880, 0, 0, 0, 0, 884, - 888, 84, 0, 0, 0, 0, 0, 892, 896, 0, 0, 0, 0, 0, 52, 900, - 36, 904, 0, 0, 0, 0, 0, 908, 36, 0, 0, 0, 0, 0, 912, 916, - 0, 920, 924, 0, 0, 0, 240, 928, 0, 0, 0, 932, 936, 0, 0, 0, - 0, 940, 944, 0, 948, 0, 0, 952, 56, 956, 0, 0, 960, 964, 0, 0, - 0, 0, 968, 0, 0, 0, 972, 976, 0, 980, 984, 988, 0, 0, 0, 992, - 996, 0, 0, 0,1000,1004, 0, 0, 0, 0, 0,1008,1012, 0, 0,1016, - 124,1020, 0, 0, 0, 0, 0, 8,1024,1028, 0, 0, 0, 92,1032, 0, - 0, 0, 0, 128, 0, 0,1036, 0, 52,1040, 244, 244,1044, 124, 0, 0, - 0, 0, 248, 116, 0,1048,1052, 0, 32, 32, 204, 32, 64, 0, 0, 0, - 0, 0,1056,1060,1064, 0,1068, 0, 200, 0, 0, 0, 32, 32, 32,1072, - 32, 32, 184, 120, 248, 68, 144, 0,1076,1080,1084, 0, 52, 0, 0, 0, - 0, 0, 84, 0, 0, 0, 92, 0, 0, 0,1088, 120, 0, 64, 0, 0, - 1092, 0, 0, 0,1096, 0, 96, 0, 0,1100, 104, 112, 236, 112, 0, 256, - 0, 0, 0,1104,1108,1112, 132, 12, 12, 12,1116,1120,1124,1128, 104,1132, - 108, 12, 256, 12, 12, 12,1136, 12, 12,1140, 12, 12, 12, 12, 12,1144, - 12, 12, 12,1148,1152, 12,1156,1160,1164,1168,1172,1176,1180,1184,1188,1192, - 12, 0, 0, 0,1196,1200,1204,1208, 0, 264, 12, 12, 96, 0, 0, 0, - 136, 264, 0, 0, 136, 0, 132, 96,1212, 108, 12, 12, 96, 12, 12,1216, - 1220, 12, 12, 12, 0, 136, 132, 12, 8, 8, 32, 32, 32, 32, 32, 8, -}; - -static inline uint8_t _raqm_b4 (const uint8_t* a, unsigned i) -{ - return (a[i>>1]>>((i&1)<<2))&15; -} -static inline uint8_t _raqm_get_grapheme_break (unsigned u) -{ - /* packtab: [2^4,2^4,2^3,2^2,2^2,2^2] */ - return u<921600u ? (uint8_t)(_raqm_b4(_raqm_u8+2081u,((_raqm_u8[857u+_raqm_u16[((_raqm_u8[257u+((_raqm_u8[57u+((_raqm_b4(_raqm_u8,((((((((((u)>>2))>>2))>>2))>>3))>>4)))<<4)+((((((((((u)>>2))>>2))>>2))>>3))&15)])<<3)+((((((((u)>>2))>>2))>>2))&7)])<<2)+((((((u)>>2))>>2))&3)]+((((u)>>2))&3)])<<2)+((u)&3))) : 0; -} -static inline uint8_t _raqm_b2 (const uint8_t* a, unsigned i) -{ - return (a[i>>2]>>((i&3)<<1))&3; -} -static inline uint8_t _raqm_get_incb (unsigned u) -{ - /* packtab: [2^4,2^4,2^3,2^2,2^4] */ - return u<918000u ? (uint8_t)(_raqm_b2(_raqm_u8+3614u,((_raqm_u8[2958u+((_raqm_u8[2550u+((_raqm_u8[2370u+((_raqm_b4(_raqm_u8+2313u,((((((((u)>>4))>>2))>>3))>>4)))<<4)+((((((((u)>>4))>>2))>>3))&15)])<<3)+((((((u)>>4))>>2))&7)])<<2)+((((u)>>4))&3)])<<4)+((u)&15))) : 0; -} - -#endif /* _RAQM_GRAPHEME_DATA_H_ */ diff --git a/src/thirdparty/raqm/raqm-version.h b/src/thirdparty/raqm/raqm-version.h deleted file mode 100644 index 2b584fcc717..00000000000 --- a/src/thirdparty/raqm/raqm-version.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 11 -#define RAQM_VERSION_MICRO 0 - -#define RAQM_VERSION_STRING "0.11.0" - -#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 deleted file mode 100644 index 36ff187499f..00000000000 --- a/src/thirdparty/raqm/raqm.c +++ /dev/null @@ -1,2950 +0,0 @@ -/* - * Copyright © 2015 Information Technology Authority (ITA) - * Copyright © 2016-2023 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" -#endif - -#include -#include - -#ifdef RAQM_SHEENBIDI -#ifdef RAQM_SHEENBIDI_GT_2_9 -#include -#else -#include -#endif -#else -#ifdef HAVE_FRIBIDI_SYSTEM -#include -#else -#include "../fribidi-shim/fribidi.h" -#endif -#endif - -#include -#include - -#include "raqm.h" -#include "grapheme-data.h" - -/** - * SECTION:raqm - * @title: Raqm - * @short_description: A library for complex text layout - * @include: raqm.h - * - * Raqm is a lightweight 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 RAQM_STATIC -# 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(...) -# define RAQM_STATIC static -#endif - -#define RAQM_BIDI_LEVEL_IS_RTL(level) \ - ((level) & 1) - -#ifdef RAQM_SHEENBIDI - typedef SBLevel _raqm_bidi_level_t; -#else - typedef FriBidiLevel _raqm_bidi_level_t; -#endif - -typedef struct -{ - FT_Face ftface; - int ftloadflags; - hb_language_t lang; - hb_script_t script; - int spacing_after; -} _raqm_text_info; - -typedef struct _raqm_run raqm_run_t; - -struct _raqm -{ - int ref_count; - - uint32_t *text; - uint16_t *text_utf16; - char *text_utf8; - size_t text_len; - size_t text_capacity_bytes; - - _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_run_t *runs_pool; - - raqm_glyph_t *glyphs; - size_t glyphs_capacity; - - int invisible_glyph; -}; - -struct _raqm_run -{ - uint32_t pos; - uint32_t len; - - hb_direction_t direction; - hb_script_t script; - hb_font_t *font; - hb_buffer_t *buffer; - - raqm_run_t *next; -}; - -static size_t -_raqm_encoding_to_u32_index (raqm_t *rq, - size_t index); - -static void -_raqm_init_text_info (raqm_t *rq) -{ - hb_language_t 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].ftloadflags = -1; - rq->text_info[i].lang = default_lang; - rq->text_info[i].script = HB_SCRIPT_INVALID; - rq->text_info[i].spacing_after = 0; - } -} - -static void -_raqm_release_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); - } -} - -static bool -_raqm_compare_text_info (_raqm_text_info a, - _raqm_text_info b) -{ - if (a.ftface != b.ftface) - return false; - - if (a.ftloadflags != b.ftloadflags) - return false; - - if (a.lang != b.lang) - return false; - - if (a.script != b.script) - return false; - - /* Spacing shouldn't break runs, so we don't compare them here. */ - - return true; -} - -static void -_raqm_free_text(raqm_t* rq) -{ - free (rq->text); - rq->text = NULL; - rq->text_info = NULL; - rq->text_utf8 = NULL; - rq->text_utf16 = NULL; - rq->text_len = 0; - rq->text_capacity_bytes = 0; -} - -static bool -_raqm_alloc_text(raqm_t *rq, - size_t len, - bool need_utf8, - bool need_utf16) -{ - /* Allocate contiguous memory block for texts and text_info */ - size_t mem_size = (sizeof (uint32_t) + sizeof (_raqm_text_info)) * len; - if (need_utf8) - mem_size += sizeof (char) * len; - else if (need_utf16) - mem_size += sizeof (uint16_t) * len; - - if (mem_size > rq->text_capacity_bytes) - { - void* new_mem = realloc (rq->text, mem_size); - if (!new_mem) - { - _raqm_free_text (rq); - return false; - } - - rq->text_capacity_bytes = mem_size; - rq->text = new_mem; - } - - rq->text_info = (_raqm_text_info*)(rq->text + len); - rq->text_utf8 = need_utf8 ? (char*)(rq->text_info + len) : NULL; - rq->text_utf16 = need_utf16 ? (uint16_t*)(rq->text_info + len) : NULL; - - return true; -} - -static raqm_run_t* -_raqm_alloc_run (raqm_t *rq) -{ - raqm_run_t *run = rq->runs_pool; - if (run) - { - rq->runs_pool = run->next; - } - else - { - run = malloc (sizeof (raqm_run_t)); - if (!run) - return NULL; - run->font = NULL; - run->buffer = NULL; - } - - run->pos = 0; - run->len = 0; - run->direction = HB_DIRECTION_INVALID; - run->script = HB_SCRIPT_INVALID; - run->next = NULL; - - return run; -} - -static void -_raqm_free_runs (raqm_run_t *runs) -{ - while (runs) - { - raqm_run_t *run = runs; - runs = runs->next; - - if (run->buffer) - hb_buffer_destroy (run->buffer); - - if (run->font) - hb_font_destroy (run->font); - - free (run); - } -} - -/** - * 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->base_dir = RAQM_DIRECTION_DEFAULT; - rq->resolved_dir = RAQM_DIRECTION_DEFAULT; - - rq->features = NULL; - rq->features_len = 0; - - rq->invisible_glyph = 0; - - rq->text = NULL; - rq->text_utf16 = NULL; - rq->text_utf8 = NULL; - rq->text_info = NULL; - rq->text_capacity_bytes = 0; - rq->text_len = 0; - - rq->runs = NULL; - rq->runs_pool = NULL; - - rq->glyphs = NULL; - rq->glyphs_capacity = 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; -} - -/** - * 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 raqm_reference(). - * - * Since: 0.1 - */ -void -raqm_destroy (raqm_t *rq) -{ - if (!rq || --rq->ref_count != 0) - return; - - _raqm_release_text_info (rq); - _raqm_free_text (rq); - _raqm_free_runs (rq->runs); - _raqm_free_runs (rq->runs_pool); - free (rq->glyphs); - free (rq->features); - free (rq); -} - -/** - * raqm_clear_contents: - * @rq: a #raqm_t. - * - * Clears internal state of previously used raqm_t object, making it ready - * for reuse and keeping some of allocated memory to increase performance. - * - * Since: 0.9 - */ -void -raqm_clear_contents (raqm_t *rq) -{ - if (!rq) - return; - - _raqm_release_text_info (rq); - - /* Return allocated runs to the pool, keep hb buffers for reuse */ - raqm_run_t *run = rq->runs; - while (run) - { - if (run->buffer) - hb_buffer_reset (run->buffer); - - if (run->font) - { - hb_font_destroy (run->font); - run->font = NULL; - } - - if (!run->next) - { - run->next = rq->runs_pool; - rq->runs_pool = rq->runs; - rq->runs = NULL; - break; - } - - run = run->next; - } - - rq->text_len = 0; - rq->resolved_dir = RAQM_DIRECTION_DEFAULT; -} - -/** - * 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 characters will be replaced with U+FFFD. The text must - * represent a single full paragraph, since doing the layout of chunks of text - * separately can give improper output. - * - * The text must be set before any of the functions that assign attributes to - * it, e.g. raqm_set_freetype_face(), raqm_set_language(), etc. - * - * 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; - - /* Call raqm_clear_contents to reuse this raqm_t */ - if (rq->text_len) - return false; - - /* Empty string, don’t fail but do nothing */ - if (!len) - return true; - - if (!_raqm_alloc_text(rq, len, false, false)) - return false; - - rq->text_len = len; - memcpy (rq->text, text, sizeof (uint32_t) * len); - _raqm_init_text_info (rq); - - return true; -} - -/* Ported from HarfBuzz’s hb_utf8_t::next(). */ -static const char * -_raqm_get_utf8_codepoint (const char *str, - const char *end, - uint32_t *out_codepoint) -{ - uint32_t c = (unsigned char) *str++; - - if (c > 0x7Fu) - { - if (c >= 0xC2u && c <= 0xDFu) /* Two-byte */ - { - unsigned int t1; - if (str < end && (t1 = (unsigned char) str[0] - 0x80u) <= 0x3Fu) - { - c = ((c & 0x1Fu) << 6) | t1; - str += 1; - } - else - c = 0xFFFDu; - } - else if (c >= 0xE0u && c <= 0xEFu) /* Three-byte */ - { - unsigned int t1, t2; - if (end - str >= 2 && - (t1 = (unsigned char) str[0] - 0x80u) <= 0x3Fu && - (t2 = (unsigned char) str[1] - 0x80u) <= 0x3Fu) - { - c = ((c & 0x0Fu) << 12) | (t1 << 6) | t2; - if (c < 0x0800u || (c >= 0xD800u && c <= 0xDFFFu)) - c = 0xFFFDu; - else - str += 2; - } - else - c = 0xFFFDu; - } - else if (c >= 0xF0u && c <= 0xF4u) /* Four-byte */ - { - unsigned int t1, t2, t3; - if (end - str >= 3 && - (t1 = (unsigned char) str[0] - 0x80u) <= 0x3Fu && - (t2 = (unsigned char) str[1] - 0x80u) <= 0x3Fu && - (t3 = (unsigned char) str[2] - 0x80u) <= 0x3Fu) - { - c = ((c & 0x07u) << 18) | (t1 << 12) | (t2 << 6) | t3; - if (c < 0x10000u || c > 0x10FFFFu) - c = 0xFFFDu; - else - str += 3; - } - else - c = 0xFFFDu; - } - else - c = 0xFFFDu; - } - - *out_codepoint = c; - return str; -} - -static size_t -_raqm_u8_to_u32 (const char *text, size_t len, uint32_t *unicode) -{ - size_t in_len = 0; - uint32_t *out_utf32 = unicode; - const char *in_utf8 = text; - const char *end = text + len; - - while ((in_len < len) && (*in_utf8 != '\0')) - { - const char *out_utf8 = _raqm_get_utf8_codepoint (in_utf8, end, out_utf32); - in_len += out_utf8 - in_utf8; - in_utf8 = out_utf8; - ++out_utf32; - } - - return (out_utf32 - unicode); -} - -/* Ported from HarfBuzz’s hb_utf16_xe_t::next(). */ -static const uint16_t * -_raqm_get_utf16_codepoint (const uint16_t *str, - const uint16_t *end, - uint32_t *out_codepoint) -{ - uint32_t c = *str++; - - if (c < 0xD800u || c > 0xDFFFu) - { - *out_codepoint = c; - return str; - } - - if (c <= 0xDBFFu && str < end) - { - /* High-surrogate in c */ - uint32_t l = *str; - if (l >= 0xDC00u && l <= 0xDFFFu) - { - /* Low-surrogate in l */ - *out_codepoint = (c << 10) + l - ((0xD800u << 10) - 0x10000u + 0xDC00u); - str += 1; - return str; - } - } - - /* Lonely / out-of-order surrogate. */ - *out_codepoint = 0xFFFDu; - return str; -} - -static size_t -_raqm_u16_to_u32 (const uint16_t *text, size_t len, uint32_t *unicode) -{ - size_t in_len = 0; - uint32_t *out_utf32 = unicode; - const uint16_t *in_utf16 = text; - const uint16_t *end = text + len; - - while ((in_len < len) && (*in_utf16 != '\0')) - { - const uint16_t *out_utf16 = _raqm_get_utf16_codepoint (in_utf16, end, out_utf32); - in_len += (out_utf16 - in_utf16); - in_utf16 = out_utf16; - ++out_utf32; - } - - return (out_utf32 - unicode); -} - -/** - * 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) -{ - if (!rq || !text) - return false; - - /* Call raqm_clear_contents to reuse this raqm_t */ - if (rq->text_len) - return false; - - /* Empty string, don’t fail but do nothing */ - if (!len) - return true; - - if (!_raqm_alloc_text(rq, len, true, false)) - return false; - - rq->text_len = _raqm_u8_to_u32 (text, len, rq->text); - memcpy (rq->text_utf8, text, sizeof (char) * len); - _raqm_init_text_info (rq); - - return true; -} - -/** - * raqm_set_text_utf16: - * @rq: a #raqm_t. - * @text: a UTF-16 encoded text string. - * @len: the length of @text in UTF-16 shorts. - * - * Same as raqm_set_text(), but for text encoded in UTF-16 encoding. - * - * Return value: - * `true` if no errors happened, `false` otherwise. - * - * Since: 0.10 - */ -bool -raqm_set_text_utf16 (raqm_t *rq, - const uint16_t *text, - size_t len) -{ - if (!rq || !text) - return false; - - /* Call raqm_clear_contents to reuse this raqm_t */ - if (rq->text_len) - return false; - - /* Empty string, don’t fail but do nothing */ - if (!len) - return true; - - if (!_raqm_alloc_text(rq, len, false, true)) - return false; - - rq->text_len = _raqm_u16_to_u32 (text, len, rq->text); - memcpy (rq->text_utf16, text, sizeof (uint16_t) * len); - _raqm_init_text_info (rq); - - return true; -} -/** - * 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](https://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 the 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 starting at @start. The @start and @len are - * input string array indices (i.e. counting bytes in UTF-8 and scalar 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; - - if (!rq) - return false; - - if (!rq->text_len) - return true; - - end = _raqm_encoding_to_u32_index (rq, start + len); - start = _raqm_encoding_to_u32_index (rq, start); - - 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; -} - -static bool -_raqm_add_font_feature (raqm_t *rq, - hb_feature_t fea) -{ - void* new_features; - - if (!rq) - return false; - - new_features = realloc (rq->features, - sizeof (hb_feature_t) * (rq->features_len + 1)); - if (!new_features) - return false; - - if (fea.start != HB_FEATURE_GLOBAL_START) - fea.start = _raqm_encoding_to_u32_index (rq, fea.start); - if (fea.end != HB_FEATURE_GLOBAL_END) - fea.end = _raqm_encoding_to_u32_index (rq, fea.end); - - rq->features = new_features; - rq->features[rq->features_len] = fea; - rq->features_len++; - - 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) - _raqm_add_font_feature (rq, fea); - - return ok; -} - -static hb_font_t * -_raqm_create_hb_font (raqm_t *rq, - FT_Face face, - int loadflags) -{ - hb_font_t *font = hb_ft_font_create_referenced (face); - - if (loadflags >= 0) - hb_ft_font_set_load_flags (font, 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. - * - * The text must be set with raqm_set_text() or one of its variants before - * calling this function. - * - * 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 the first character that should use @face from the input string. - * @len: number of elements 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, counting elements - * according to the underlying encoding. @start must always be aligned to the - * start of an encoded codepoint, and @len must always end at a codepoint's - * final element. - * - * 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, and are properly aligned. - * - * 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; - - if (!rq) - return false; - - if (!rq->text_len) - return true; - - end = _raqm_encoding_to_u32_index (rq, start + len); - start = _raqm_encoding_to_u32_index (rq, start); - - return _raqm_set_freetype_face (rq, face, start, end); -} - -static bool -_raqm_set_freetype_load_flags (raqm_t *rq, - int flags, - 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++) - rq->text_info[i].ftloadflags = flags; - - return true; -} - -/** - * 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. - * - * Return value: - * `true` if no errors happened, `false` otherwise. - * - * Since: 0.3 - */ -bool -raqm_set_freetype_load_flags (raqm_t *rq, - int flags) -{ - return _raqm_set_freetype_load_flags(rq, flags, 0, rq->text_len); -} - -/** - * raqm_set_freetype_load_flags_range: - * @rq: a #raqm_t. - * @flags: FreeType load flags. - * @start: index of the first character that should use @flags. - * @len: number of characters using @flags. - * - * Sets the load flags passed to FreeType when loading glyphs for @len-number - * of characters staring at @start. Flags should be the same as used by the - * client when rendering corresponding FreeType glyphs. The @start and @len - * are input string array indices (i.e. counting bytes in UTF-8 and scalar - * values in UTF-32). - * - * This method can be used repeatedly to set different flags for different - * parts of the text. It is the responsibility of the client to make sure that - * flag ranges cover the whole text. - * - * See also raqm_set_freetype_load_flags(). - * - * Return value: - * `true` if no errors happened, `false` otherwise. - * - * Since: 0.9 - */ -bool -raqm_set_freetype_load_flags_range (raqm_t *rq, - int flags, - size_t start, - size_t len) -{ - size_t end; - - if (!rq) - return false; - - if (!rq->text_len) - return true; - - end = _raqm_encoding_to_u32_index (rq, start + len); - start = _raqm_encoding_to_u32_index (rq, start); - - return _raqm_set_freetype_load_flags (rq, flags, start, end); -} - -static bool -_raqm_set_spacing (raqm_t *rq, - int spacing, - bool word_spacing, - 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++) - { - bool set_spacing = i == 0; - if (!set_spacing) - set_spacing = raqm_allowed_grapheme_boundary (rq, i - 1); - - if (set_spacing) - { - if (word_spacing) - { - if (raqm_allowed_grapheme_boundary (rq, i)) - { - /* CSS word separators, word spacing is only applied on these.*/ - if (rq->text[i] == 0x0020 || /* Space */ - rq->text[i] == 0x00A0 || /* No Break Space */ - rq->text[i] == 0x1361 || /* Ethiopic Word Space */ - rq->text[i] == 0x10100 || /* Aegean Word Separator Line */ - rq->text[i] == 0x10101 || /* Aegean Word Separator Dot */ - rq->text[i] == 0x1039F || /* Ugaric Word Divider */ - rq->text[i] == 0x1091F) /* Phoenician Word Separator */ - { - rq->text_info[i].spacing_after = spacing; - } - } - } - else - { - rq->text_info[i].spacing_after = spacing; - } - } - } - - return true; -} - -/** - * raqm_set_letter_spacing_range: - * @rq: a #raqm_t. - * @spacing: amount of spacing in Freetype Font Units (26.6 format). - * @start: index of the first character that should use @spacing. - * @len: number of characters using @spacing. - * - * Set the letter spacing or tracking for a given range, the value - * will be added onto the advance and offset for RTL, and the advance for - * other directions. Letter spacing will be applied between characters, so - * the last character will not have spacing applied after it. - * Note that not all scripts have a letter-spacing tradition, - * for example, Arabic does not, while Devanagari does. - * - * This will also add “disable `liga`, `clig`, `hlig`, `dlig`, and `calt`” font - * features to the internal features list, so call this function after setting - * the font features for best spacing results. - * - * Return value: - * `true` if no errors happened, `false` otherwise. - * - * Since: 0.10 - */ -bool -raqm_set_letter_spacing_range(raqm_t *rq, - int spacing, - size_t start, - size_t len) -{ - size_t end; - - if (!rq) - return false; - - if (!rq->text_len) - return true; - - end = start + len - 1; - - if (spacing != 0) - { -#define NUM_TAGS 5 - static char *tags[NUM_TAGS] = { "clig", "liga", "hlig", "dlig", "calt" }; - for (size_t i = 0; i < NUM_TAGS; i++) - { - hb_feature_t fea = { hb_tag_from_string(tags[i], 5), 0, start, end }; - _raqm_add_font_feature (rq, fea); - } -#undef NUM_TAGS - } - - start = _raqm_encoding_to_u32_index (rq, start); - end = _raqm_encoding_to_u32_index (rq, end); - - return _raqm_set_spacing (rq, spacing, false, start, end); -} - -/** - * raqm_set_word_spacing_range: - * @rq: a #raqm_t. - * @spacing: amount of spacing in Freetype Font Units (26.6 format). - * @start: index of the first character that should use @spacing. - * @len: number of characters using @spacing. - * - * Set the word spacing for a given range. Word spacing will only be applied to - * 'word separator' characters, such as 'space', 'no break space' and - * 'Ethiopic word separator'. - * The value will be added onto the advance and offset for RTL, and the advance - * for other directions. - * - * Return value: - * `true` if no errors happened, `false` otherwise. - * - * Since: 0.10 - */ -bool -raqm_set_word_spacing_range(raqm_t *rq, - int spacing, - size_t start, - size_t len) -{ - size_t end; - - if (!rq) - return false; - - if (!rq->text_len) - return true; - - end = _raqm_encoding_to_u32_index (rq, start + len); - start = _raqm_encoding_to_u32_index (rq, start); - - return _raqm_set_spacing (rq, spacing, true, start, end); -} - -/** - * 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 glyphs. - * - * If @gid is negative, invisible glyphs will be suppressed from the output. - * - * If @gid is zero, invisible glyphs will be rendered as space. - * - * If @gid is a positive number, it will be used for invisible glyphs. - * - * 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; - - rq->invisible_glyph = gid; - return true; -} - -static bool -_raqm_itemize (raqm_t *rq); - -static bool -_raqm_shape (raqm_t *rq); - -static bool -_raqm_is_paragraph_separator (uint32_t ch) -{ - /* Unicode characters with Bidi class B (Paragraph_Separator). */ - return ch == 0x000A || ch == 0x000D || - ch == 0x001C || ch == 0x001D || ch == 0x001E || - ch == 0x0085 || ch == 0x2029; -} - -/** - * 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. It also fails - * if the text is not a single paragraph (see raqm_set_text()). - * - * 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; - - /* The text must be a single paragraph. */ - if (_raqm_is_paragraph_separator (rq->text[i])) - 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); -static uint32_t -_raqm_u32_to_u16_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); - - if (count > rq->glyphs_capacity) - { - void* new_mem = realloc (rq->glyphs, sizeof (raqm_glyph_t) * count); - if (!new_mem) - { - *length = 0; - return NULL; - } - - rq->glyphs = new_mem; - rq->glyphs_capacity = count; - } - - *length = count; - - 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->text_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 - } - else if (rq->text_utf16) - { - for (size_t i = 0; i < count; i++) - rq->glyphs[i].cluster = _raqm_u32_to_u16_index (rq, - rq->glyphs[i].cluster); - } - return rq->glyphs; -} - -/** - * raqm_get_par_resolved_direction: - * @rq: a #raqm_t. - * - * Gets the resolved direction of the paragraph; - * - * Return value: - * The #raqm_direction_t specifying the resolved direction of text, - * or #RAQM_DIRECTION_DEFAULT if raqm_layout() has not been called on @rq. - * - * Since: 0.8 - */ -raqm_direction_t -raqm_get_par_resolved_direction (raqm_t *rq) -{ - if (!rq) - return RAQM_DIRECTION_DEFAULT; - - return rq->resolved_dir; -} - -static raqm_direction_t -_raqm_detect_direction (raqm_t *rq); - -/** - * raqm_get_par_detected_direction: - * @rq: a #raqm_t. - * - * Gets the natural direction of the text as detected from its content - * following the Unicode Bidirectional Algorithm, regardless of the base - * direction set with raqm_set_par_direction(). Unlike - * raqm_get_par_resolved_direction(), this does not require raqm_layout() to - * have been called, and can report that the text is direction neutral. - * - * Return value: - * #RAQM_DIRECTION_LTR or #RAQM_DIRECTION_RTL if the text has a strong - * direction, or #RAQM_DIRECTION_DEFAULT if the text is direction neutral (has - * no strong characters) or no text has been set. #RAQM_DIRECTION_TTB is never - * returned, as vertical direction is a layout property, not a property of the - * text content. - * - * Since: 0.11 - */ -raqm_direction_t -raqm_get_par_detected_direction (raqm_t *rq) -{ - if (!rq || !rq->text || rq->text_len == 0) - return RAQM_DIRECTION_DEFAULT; - - return _raqm_detect_direction (rq); -} - -/** - * raqm_get_direction_at_index: - * @rq: a #raqm_t. - * @index: (in): character index. - * - * Gets the resolved direction of the character at specified index; - * - * Return value: - * The #raqm_direction_t specifying the resolved direction of text at the - * specified index, or #RAQM_DIRECTION_DEFAULT if raqm_layout() has not been - * called on @rq. - * - * Since: 0.8 - */ -raqm_direction_t -raqm_get_direction_at_index (raqm_t *rq, - size_t index) -{ - if (!rq) - return RAQM_DIRECTION_DEFAULT; - - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) - { - if (run->pos <= index && index < run->pos + run->len) - { - switch (run->direction) - { - case HB_DIRECTION_LTR: - return RAQM_DIRECTION_LTR; - case HB_DIRECTION_RTL: - return RAQM_DIRECTION_RTL; - case HB_DIRECTION_TTB: - return RAQM_DIRECTION_TTB; - default: - return RAQM_DIRECTION_DEFAULT; - } - } - } - - return RAQM_DIRECTION_DEFAULT; -} - -static bool -_raqm_resolve_scripts (raqm_t *rq); - -static hb_direction_t -_raqm_hb_dir (raqm_t *rq, _raqm_bidi_level_t level) -{ - hb_direction_t dir = HB_DIRECTION_LTR; - - if (rq->base_dir == RAQM_DIRECTION_TTB) - dir = HB_DIRECTION_TTB; - else if (RAQM_BIDI_LEVEL_IS_RTL(level)) - dir = HB_DIRECTION_RTL; - - return dir; -} - -typedef struct -{ - size_t pos; - size_t len; - _raqm_bidi_level_t level; -} _raqm_bidi_run; - -#ifdef RAQM_SHEENBIDI -static _raqm_bidi_run * -_raqm_bidi_itemize (raqm_t *rq, size_t *run_count) -{ - _raqm_bidi_run *runs; - SBAlgorithmRef bidi; - SBParagraphRef par; - SBUInteger par_len; - SBLineRef line; - - SBLevel base_level = SBLevelDefaultLTR; - SBCodepointSequence input = { - SBStringEncodingUTF32, - (void *) rq->text, - rq->text_len - }; - - if (rq->base_dir == RAQM_DIRECTION_RTL) - base_level = 1; - else if (rq->base_dir == RAQM_DIRECTION_LTR) - base_level = 0; - - /* paragraph */ - bidi = SBAlgorithmCreate (&input); - par = SBAlgorithmCreateParagraph (bidi, 0, INT32_MAX, base_level); - par_len = SBParagraphGetLength (par); - - /* lines */ - line = SBParagraphCreateLine (par, 0, par_len); - *run_count = SBLineGetRunCount (line); - - if (SBParagraphGetBaseLevel (par) == 1) - rq->resolved_dir = RAQM_DIRECTION_RTL; - else - rq->resolved_dir = RAQM_DIRECTION_LTR; - - runs = malloc (sizeof (_raqm_bidi_run) * (*run_count)); - if (runs) - { - const SBRun *sheenbidi_runs = SBLineGetRunsPtr(line); - - for (size_t i = 0; i < (*run_count); ++i) - { - runs[i].pos = sheenbidi_runs[i].offset; - runs[i].len = sheenbidi_runs[i].length; - runs[i].level = sheenbidi_runs[i].level; - } - } - - SBLineRelease (line); - SBParagraphRelease (par); - SBAlgorithmRelease (bidi); - - return runs; -} - -static raqm_direction_t -_raqm_detect_direction (raqm_t *rq) -{ - raqm_direction_t dir = RAQM_DIRECTION_DEFAULT; - size_t isolate = 0; - - /* UBA: P2 and P3. */ - for (size_t i = 0; i < rq->text_len; i++) - { - SBBidiType type = SBCodepointGetBidiType (rq->text[i]); - - if (type == SBBidiTypeLRI || type == SBBidiTypeRLI || type == SBBidiTypeFSI) - isolate++; - else if (type == SBBidiTypePDI && isolate > 0) - isolate--; - else if (isolate == 0 && SBBidiTypeIsStrong (type)) - { - dir = (type == SBBidiTypeL) ? RAQM_DIRECTION_LTR : RAQM_DIRECTION_RTL; - break; - } - } - - return dir; -} -#else -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); - if (!runs) - { - *run_count = 0; - return NULL; - } - - 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 _raqm_bidi_run * -_raqm_bidi_itemize (raqm_t *rq, size_t *run_count) -{ - FriBidiParType par_type = FRIBIDI_PAR_ON; - _raqm_bidi_run *runs = NULL; - - FriBidiCharType *types; - _raqm_bidi_level_t *levels; - int max_level = 0; - FriBidiBracketType *btypes; - - types = calloc (rq->text_len, sizeof (FriBidiCharType)); - btypes = calloc (rq->text_len, sizeof (FriBidiBracketType)); - levels = calloc (rq->text_len, sizeof (_raqm_bidi_level_t)); - - if (!types || !levels || !btypes) - 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; - - fribidi_get_bidi_types (rq->text, rq->text_len, types); - 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); - - if (par_type == FRIBIDI_PAR_RTL) - rq->resolved_dir = RAQM_DIRECTION_RTL; - else - rq->resolved_dir = RAQM_DIRECTION_LTR; - - if (max_level == 0) - goto done; - - /* Get the number of bidi runs */ - runs = _raqm_reorder_runs (types, rq->text_len, par_type, levels, run_count); - -done: - free (types); - free (levels); - free (btypes); - - return runs; -} - -static raqm_direction_t -_raqm_detect_direction (raqm_t *rq) -{ - raqm_direction_t dir = RAQM_DIRECTION_DEFAULT; - FriBidiParType par_type; - FriBidiCharType *types; - - types = calloc (rq->text_len, sizeof (FriBidiCharType)); - if (types) - { - fribidi_get_bidi_types (rq->text, rq->text_len, types); - par_type = fribidi_get_par_direction (types, rq->text_len); - - if (par_type == FRIBIDI_PAR_LTR) - dir = RAQM_DIRECTION_LTR; - else if (par_type == FRIBIDI_PAR_RTL) - dir = RAQM_DIRECTION_RTL; - - free (types); - } - - return dir; -} -#endif - -static bool -_raqm_itemize (raqm_t *rq) -{ - _raqm_bidi_run *runs = NULL; - raqm_run_t *last; - size_t run_count = 0; - bool ok = true; - -#ifdef RAQM_TESTING - static char *dir_names[] = { - "DEFAULT", - "RTL", - "LTR", - "TTB" - }; - - assert (rq->base_dir < sizeof (dir_names)); - RAQM_TEST ("Direction is: %s\n\n", dir_names[rq->base_dir]); -#endif - - if (!_raqm_resolve_scripts (rq)) - { - ok = false; - goto done; - } - - if (rq->base_dir == RAQM_DIRECTION_TTB) - { - /* Treat every thing as LTR in vertical text */ - run_count = 1; - rq->resolved_dir = RAQM_DIRECTION_TTB; - runs = malloc (sizeof (_raqm_bidi_run)); - if (runs) - { - runs->pos = 0; - runs->len = rq->text_len; - runs->level = 0; - } - } - else - runs = _raqm_bidi_itemize (rq, &run_count); - - if (!runs) - { - ok = false; - goto done; - } - -#ifdef RAQM_TESTING - assert (rq->resolved_dir < sizeof (dir_names)); - if (rq->base_dir == RAQM_DIRECTION_DEFAULT) - RAQM_TEST ("Resolved direction is: %s\n\n", dir_names[rq->resolved_dir]); - RAQM_TEST ("Number of runs before script itemization: %zu\n\n", run_count); - - RAQM_TEST ("BiDi 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 = _raqm_alloc_run (rq); - 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, - rq->text_info[run->pos].ftloadflags); - 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 = _raqm_alloc_run (rq); - 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, - info.ftloadflags); - 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, - rq->text_info[run->pos].ftloadflags); - 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 = _raqm_alloc_run (rq); - 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, - info.ftloadflags); - 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); - - 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 uint32_t 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 uint32_t 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) - -static hb_script_t -_raqm_unicode_script (hb_codepoint_t u) -{ - static hb_unicode_funcs_t* unicode_funcs; - - unicode_funcs = hb_unicode_funcs_get_default (); - - /* Make combining marks inherit the script of their bases, regardless of - * their own script. - */ - if (hb_unicode_general_category (unicode_funcs, u) == HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) - return HB_SCRIPT_INHERITED; - - return hb_unicode_script (unicode_funcs, u); -} - -/* 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; - - for (size_t i = 0; i < rq->text_len; ++i) - rq->text_info[i].script = _raqm_unicode_script (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 void -_raqm_ft_transform (int *x, - int *y, - FT_Matrix matrix) -{ - FT_Vector vector; - vector.x = *x; - vector.y = *y; - - FT_Vector_Transform (&vector, &matrix); - - *x = vector.x; - *y = vector.y; -} - -#if !HB_VERSION_ATLEAST (10, 4, 0) -# define hb_ft_font_get_ft_face hb_ft_font_get_face -#endif - -static bool -_raqm_shape (raqm_t *rq) -{ - hb_buffer_flags_t hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT; - - if (rq->invisible_glyph < 0) - hb_buffer_flags |= HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES; - - for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) - { - if (!run->buffer) - 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); - - if (rq->invisible_glyph > 0) - hb_buffer_set_invisible_glyph (run->buffer, rq->invisible_glyph); - - hb_shape_full (run->font, run->buffer, rq->features, rq->features_len, - NULL); - - { - FT_Matrix matrix; - hb_glyph_info_t *info; - hb_glyph_position_t *pos; - unsigned int len; - - FT_Get_Transform (hb_ft_font_get_ft_face (run->font), &matrix, NULL); - pos = hb_buffer_get_glyph_positions (run->buffer, &len); - info = hb_buffer_get_glyph_infos (run->buffer, &len); - - for (unsigned int i = 0; i < len; i++) - { - _raqm_ft_transform (&pos[i].x_advance, &pos[i].y_advance, matrix); - _raqm_ft_transform (&pos[i].x_offset, &pos[i].y_offset, matrix); - - bool set_spacing = false; - if (run->direction == HB_DIRECTION_RTL) - { - set_spacing = i == 0; - if (!set_spacing) - set_spacing = info[i].cluster != info[i-1].cluster; - } - else - { - set_spacing = i == len - 1; - if (!set_spacing) - set_spacing = info[i].cluster != info[i+1].cluster; - } - - _raqm_text_info rq_info = rq->text_info[info[i].cluster]; - - if (rq_info.spacing_after != 0 && set_spacing) - { - if (run->direction == HB_DIRECTION_TTB) - pos[i].y_advance -= rq_info.spacing_after; - else if (run->direction == HB_DIRECTION_RTL) - { - pos[i].x_advance += rq_info.spacing_after; - pos[i].x_offset += rq_info.spacing_after; - } - else - pos[i].x_advance += rq_info.spacing_after; - } - } - } - } - - return true; -} - -/* Count equivalent UTF-8 bytes in codepoint */ -static size_t -_raqm_count_codepoint_utf8_bytes (uint32_t chr) -{ - if (0 == ((uint32_t) 0xffffff80 & chr)) - return 1; - else if (0 == ((uint32_t) 0xfffff800 & chr)) - return 2; - else if (0 == ((uint32_t) 0xffff0000 & chr)) - return 3; - else - return 4; -} - -/* Convert index from UTF-32 to UTF-8 */ -static uint32_t -_raqm_u32_to_u8_index (raqm_t *rq, - uint32_t index) -{ - size_t length = 0; - - for (uint32_t i = 0; i < index; ++i) - length += _raqm_count_codepoint_utf8_bytes (rq->text[i]); - - return length; -} - -/* Convert index from UTF-8 to UTF-32 */ -static size_t -_raqm_u8_to_u32_index (raqm_t *rq, - size_t index) -{ - const unsigned char *s = (const unsigned char *) rq->text_utf8; - const unsigned char *t = s; - size_t length = 0; - - while (((size_t) (s - t) < index) && ('\0' != *s)) - { - if (0xf0 == (0xf8 & *s)) - s += 4; - else if (0xe0 == (0xf0 & *s)) - s += 3; - else if (0xc0 == (0xe0 & *s)) - s += 2; - else - s += 1; - - length++; - } - - if ((size_t) (s-t) > index) - length--; - - return length; -} - -/* Count equivalent UTF-16 short in codepoint */ -static size_t -_raqm_count_codepoint_utf16_short (uint32_t chr) -{ - if (chr > 0x010000) - return 2; - else - return 1; -} - -/* Convert index from UTF-32 to UTF-16 */ -static uint32_t -_raqm_u32_to_u16_index (raqm_t *rq, - uint32_t index) -{ - size_t length = 0; - - for (uint32_t i = 0; i < index; ++i) - length += _raqm_count_codepoint_utf16_short (rq->text[i]); - - return length; -} - -/* Convert index from UTF-16 to UTF-32 */ -static size_t -_raqm_u16_to_u32_index (raqm_t *rq, - size_t index) -{ - const uint16_t *s = (const uint16_t *) rq->text_utf16; - const uint16_t *t = s; - size_t length = 0; - - while (((size_t) (s - t) < index) && ('\0' != *s)) - { - if (*s < 0xD800 || *s > 0xDBFF) - s += 1; - else - s += 2; - - length++; - } - - if ((size_t) (s-t) > index) - length--; - - return length; -} - -static inline size_t -_raqm_encoding_to_u32_index (raqm_t *rq, - size_t index) -{ - if (rq->text_utf8) - return _raqm_u8_to_u32_index (rq, index); - else if (rq->text_utf16) - return _raqm_u16_to_u32_index (rq, index); - return index; -} - -/** - * 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; - - *index = _raqm_encoding_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, *index)) - 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->text_utf8) - *index = _raqm_u32_to_u8_index (rq, *index); - else if (rq->text_utf16) - *index = _raqm_u32_to_u16_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, *index)) - { - 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, *index)) - { - *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; -} - -/** - * raqm_allowed_grapheme_boundary: - * @rq: a #raqm_t. - * @index: the index of the boundary to check. - * - * Checks whether a grapheme cluster boundary is allowed between the characters - * at @index and @index + 1, according to the Unicode Standard Annex #29 rules. - * - * The @rq must have had text set on it using raqm_set_text(). - * - * Returns: `true` if a boundary is allowed, `false` otherwise. - * - * Since: 0.11 - **/ -RAQM_API bool -raqm_allowed_grapheme_boundary (raqm_t *rq, - size_t index) -{ - if (!rq) - return true; - - /* GB1/GB2: break at start and end of text */ - if (index >= rq->text_len - 1) - return true; - - uint32_t l_char = rq->text[index]; - uint32_t r_char = rq->text[index + 1]; - _raqm_grapheme_t l = _raqm_get_grapheme_break (l_char); - _raqm_grapheme_t r = _raqm_get_grapheme_break (r_char); - - /* GB3: CR × LF */ - if (l == RAQM_GRAPHEME_CR && r == RAQM_GRAPHEME_LF) - return false; - - /* GB4: (Control|CR|LF) ÷ */ - if (l == RAQM_GRAPHEME_CONTROL || l == RAQM_GRAPHEME_CR || - l == RAQM_GRAPHEME_LF) - return true; - - /* GB5: ÷ (Control|CR|LF) */ - if (r == RAQM_GRAPHEME_CONTROL || r == RAQM_GRAPHEME_CR || - r == RAQM_GRAPHEME_LF) - return true; - - /* GB6: L × (L|V|LV|LVT) */ - if (l == RAQM_GRAPHEME_L && - (r == RAQM_GRAPHEME_L || r == RAQM_GRAPHEME_V || - r == RAQM_GRAPHEME_LV || r == RAQM_GRAPHEME_LVT)) - return false; - - /* GB7: (LV|V) × (V|T) */ - if ((l == RAQM_GRAPHEME_LV || l == RAQM_GRAPHEME_V) && - (r == RAQM_GRAPHEME_V || r == RAQM_GRAPHEME_T)) - return false; - - /* GB8: (LVT|T) × T */ - if ((l == RAQM_GRAPHEME_LVT || l == RAQM_GRAPHEME_T) && - r == RAQM_GRAPHEME_T) - return false; - - /* GB9: × (Extend|ZWJ) */ - if (r == RAQM_GRAPHEME_EXTEND || r == RAQM_GRAPHEME_ZWJ) - return false; - - /* GB9a: × SpacingMark */ - if (r == RAQM_GRAPHEME_SPACING_MARK) - return false; - - /* GB9b: Prepend × */ - if (l == RAQM_GRAPHEME_PREPEND) - return false; - - /* GB9c: \p{InCB=Consonant} [\p{InCB=Extend}\p{InCB=Linker}]* - * \p{InCB=Linker} [\p{InCB=Extend}\p{InCB=Linker}]* × \p{InCB=Consonant} - * - * If r_char is InCB=Consonant, look back for the required pattern. */ - if (_raqm_get_incb (r_char) == RAQM_INCB_CONSONANT) - { - bool found_linker = false; - size_t j = index + 1; - while (j > 0) - { - j--; - _raqm_incb_t incb = _raqm_get_incb (rq->text[j]); - if (incb == RAQM_INCB_LINKER) - found_linker = true; - else if (incb == RAQM_INCB_EXTEND) - ; /* Continue looking back through Extend characters */ - else if (incb == RAQM_INCB_CONSONANT) - { - if (found_linker) - return false; - break; - } - else - break; - } - } - - /* GB11: ExtPict Extend* ZWJ × ExtPict */ - if (l == RAQM_GRAPHEME_ZWJ && - r == RAQM_GRAPHEME_EXTENDED_PICTOGRAPHIC) - { - /* Look back past the ZWJ at index for Extend* ExtPict */ - for (size_t j = index; j > 0; j--) - { - _raqm_grapheme_t g = _raqm_get_grapheme_break (rq->text[j - 1]); - if (g == RAQM_GRAPHEME_EXTEND) - continue; - if (g == RAQM_GRAPHEME_EXTENDED_PICTOGRAPHIC) - return false; - break; - } - } - - /* GB12/GB13: Do not break between regional indicator symbols if there is - * an odd number of RI characters before the break point. */ - if (l == RAQM_GRAPHEME_REGIONAL_INDICATOR && - r == RAQM_GRAPHEME_REGIONAL_INDICATOR) - { - size_t ri_count = 0; - for (size_t j = index + 1; j > 0; j--) - { - if (_raqm_get_grapheme_break (rq->text[j - 1]) != RAQM_GRAPHEME_REGIONAL_INDICATOR) - break; - ri_count++; - } - /* If odd number of RI before the break, don't break (they pair up) */ - if (ri_count % 2 == 1) - return false; - } - - /* GB999: Otherwise, break everywhere. */ - return true; -} - -/** - * 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 to the specified version. - * - * Return value: - * `true` if library version is less than or equal to the specified 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 to the specified version. - * - * Return value: - * `true` if library version is less than or equal to the specified 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 deleted file mode 100644 index 4b60f148e01..00000000000 --- a/src/thirdparty/raqm/raqm.h +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright © 2015 Information Technology Authority (ITA) - * Copyright © 2016-2023 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 - -#ifndef RAQM_API -#define RAQM_API -#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 the states 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 the original character in the 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_API raqm_t * -raqm_create (void); - -RAQM_API raqm_t * -raqm_reference (raqm_t *rq); - -RAQM_API void -raqm_destroy (raqm_t *rq); - -RAQM_API void -raqm_clear_contents (raqm_t *rq); - -RAQM_API bool -raqm_set_text (raqm_t *rq, - const uint32_t *text, - size_t len); - -RAQM_API bool -raqm_set_text_utf8 (raqm_t *rq, - const char *text, - size_t len); -RAQM_API bool -raqm_set_text_utf16 (raqm_t *rq, - const uint16_t *text, - size_t len); - -RAQM_API bool -raqm_set_par_direction (raqm_t *rq, - raqm_direction_t dir); - -RAQM_API bool -raqm_set_language (raqm_t *rq, - const char *lang, - size_t start, - size_t len); - -RAQM_API bool -raqm_add_font_feature (raqm_t *rq, - const char *feature, - int len); - -RAQM_API bool -raqm_set_freetype_face (raqm_t *rq, - FT_Face face); - -RAQM_API bool -raqm_set_freetype_face_range (raqm_t *rq, - FT_Face face, - size_t start, - size_t len); - -RAQM_API bool -raqm_set_freetype_load_flags (raqm_t *rq, - int flags); - -RAQM_API bool -raqm_set_freetype_load_flags_range (raqm_t *rq, - int flags, - size_t start, - size_t len); - -RAQM_API bool -raqm_set_letter_spacing_range(raqm_t *rq, - int spacing, - size_t start, - size_t len); -RAQM_API bool -raqm_set_word_spacing_range(raqm_t *rq, - int spacing, - size_t start, - size_t len); - -RAQM_API bool -raqm_set_invisible_glyph (raqm_t *rq, - int gid); - -RAQM_API bool -raqm_layout (raqm_t *rq); - -RAQM_API raqm_glyph_t * -raqm_get_glyphs (raqm_t *rq, - size_t *length); - -RAQM_API raqm_direction_t -raqm_get_par_resolved_direction (raqm_t *rq); - -RAQM_API raqm_direction_t -raqm_get_par_detected_direction (raqm_t *rq); - -RAQM_API raqm_direction_t -raqm_get_direction_at_index (raqm_t *rq, - size_t index); - -RAQM_API bool -raqm_index_to_position (raqm_t *rq, - size_t *index, - int *x, - int *y); - -RAQM_API bool -raqm_position_to_index (raqm_t *rq, - int x, - int y, - size_t *index); - -RAQM_API void -raqm_version (unsigned int *major, - unsigned int *minor, - unsigned int *micro); - -RAQM_API const char * -raqm_version_string (void); - -RAQM_API bool -raqm_allowed_grapheme_boundary (raqm_t *rq, - size_t index); - -RAQM_API bool -raqm_version_atleast (unsigned int major, - unsigned int minor, - unsigned int micro); - - -#ifdef __cplusplus -} -#endif -#undef _RAQM_H_IN_ -#endif /* _RAQM_H_ */ diff --git a/winbuild/README.md b/winbuild/README.md index 50ad0a895d2..8b7855b6dc9 100644 --- a/winbuild/README.md +++ b/winbuild/README.md @@ -22,9 +22,9 @@ cd /D C:\Pillow\winbuild %PYTHON%\python.exe build_prepare.py -v --depends=C:\pillow-depends build\build_dep_all.cmd cd .. -%PYTHON%\python.exe -m pip install -v -C raqm=vendor -C fribidi=vendor . +%PYTHON%\python.exe -m pip install -v -C raqm=enable . path C:\Pillow\winbuild\build\bin;%PATH% %PYTHON%\python.exe selftest.py %PYTHON%\python.exe -m pytest -vv -x --cov PIL --cov Tests --cov-report term --cov-report xml Tests -%PYTHON%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor . +%PYTHON%\python.exe -m pip wheel -v -C raqm=enable . ``` diff --git a/winbuild/build.rst b/winbuild/build.rst index 9be9847e728..8aaa311b439 100644 --- a/winbuild/build.rst +++ b/winbuild/build.rst @@ -86,24 +86,24 @@ Once the dependencies are built, make sure the required environment variables are set by running ``winbuild\build\build_env.cmd`` and install Pillow with pip:: winbuild\build\build_env.cmd - python.exe -m pip install -v -C raqm=vendor -C fribidi=vendor . + python.exe -m pip install -v -C raqm=enable . You can also install Pillow in `editable mode`_:: winbuild\build\build_env.cmd - python.exe -m pip install -v -C raqm=vendor -C fribidi=vendor -e . + python.exe -m pip install -v -C raqm=enable -e . To build a binary wheel instead, run:: winbuild\build\build_env.cmd - python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor . + python.exe -m pip wheel -v -C raqm=enable . .. _editable mode: https://setuptools.pypa.io/en/stable/userguide/development_mode.html Testing Pillow -------------- -Some binary dependencies (e.g. ``fribidi.dll``) will be stored in the +Some binary dependencies (e.g. ``djpeg.exe``) will be stored in the ``winbuild\build\bin`` directory; this directory should be added to ``PATH`` before running tests. @@ -121,8 +121,8 @@ Here's an example script to build on Windows:: build\build_dep_all.cmd build\build_env.cmd cd .. - %PYTHON%\python.exe -m pip install -v -C raqm=vendor -C fribidi=vendor . + %PYTHON%\python.exe -m pip install -v -C raqm=enable . path C:\Pillow\winbuild\build\bin;%PATH% %PYTHON%\python.exe selftest.py %PYTHON%\python.exe -m pytest -vv -x --cov PIL --cov Tests --cov-report term --cov-report xml Tests - %PYTHON%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor . + %PYTHON%\python.exe -m pip wheel -v -C raqm=enable . From 41eb03b66bffa09a7ee516bea8f985a879c8ad20 Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Sat, 29 Aug 2026 12:01:10 +0300 Subject: [PATCH 3/7] Remove FriBiDi from the Windows build Nothing links FriBiDi any more. --- .github/INCIDENT_RESPONSE.md | 2 +- .github/dependencies.json | 1 - .github/renovate.json | 9 --- .github/workflows/wheels.yml | 10 +-- winbuild/build.rst | 6 +- winbuild/build_prepare.py | 25 +------- winbuild/fribidi.cmake | 116 ----------------------------------- 7 files changed, 8 insertions(+), 161 deletions(-) delete mode 100644 winbuild/fribidi.cmake diff --git a/.github/INCIDENT_RESPONSE.md b/.github/INCIDENT_RESPONSE.md index 1c2e395ddf8..88c2f1c0008 100644 --- a/.github/INCIDENT_RESPONSE.md +++ b/.github/INCIDENT_RESPONSE.md @@ -241,7 +241,7 @@ require a Pillow point release even if Pillow's own code is unchanged. | [lcms2](https://www.littlecms.com/) | ICC color management | [GitHub](https://github.com/mm2/Little-CMS/security) | | [harfbuzz](https://harfbuzz.github.io/) | Text shaping (via raqm) | [GitHub](https://github.com/harfbuzz/harfbuzz/security) | | [raqm](https://github.com/HOST-Oman/libraqm) | Complex text layout | [GitHub](https://github.com/HOST-Oman/libraqm) | -| [fribidi](https://github.com/fribidi/fribidi) | Unicode bidi (via raqm) | [GitHub](https://github.com/fribidi/fribidi) | +| [sheenbidi](https://github.com/Tehreer/SheenBidi) | Unicode bidi (via raqm) | [GitHub](https://github.com/Tehreer/SheenBidi) | | [zlib](https://zlib.net/) | Deflate compression | [zlib.net](https://zlib.net/) | | [liblzma / xz-utils](https://tukaani.org/xz/) | XZ/LZMA compression | [GitHub](https://github.com/tukaani-project/xz/security) | | [bzip2](https://gitlab.com/bzip2/bzip2) | BZ2 compression | [GitLab](https://gitlab.com/bzip2/bzip2/-/work_items) | diff --git a/.github/dependencies.json b/.github/dependencies.json index 0f05ff1f1ec..4e438c334cf 100644 --- a/.github/dependencies.json +++ b/.github/dependencies.json @@ -2,7 +2,6 @@ "brotli": "1.2.0", "bzip2": "1.0.8", "freetype": "2.14.3", - "fribidi": "1.0.16", "harfbuzz": "14.4.0", "jpegturbo": "3.2.0", "lcms2": "2.19.1", diff --git a/.github/renovate.json b/.github/renovate.json index 4030ff38a80..43a3c97f9ff 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -41,15 +41,6 @@ "extractVersionTemplate": "^VER-(?[\\d-]+)$", "versioningTemplate": "regex:^(?\\d+)[.-](?\\d+)[.-](?\\d+)$" }, - { - "customType": "regex", - "managerFilePatterns": ["/^\\.github/dependencies\\.json$/"], - "matchStrings": ["\"fribidi\":\\s*\"(?\\d+[^\"]*)\""], - "depNameTemplate": "fribidi", - "packageNameTemplate": "fribidi/fribidi", - "datasourceTemplate": "github-releases", - "extractVersionTemplate": "^v(?.+)$" - }, { "customType": "regex", "managerFilePatterns": ["/^\\.github/dependencies\\.json$/"], diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 17023104bf9..e85423c6e5d 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -22,7 +22,7 @@ on: - "setup.py" - "wheels/*" - "winbuild/build_prepare.py" - - "winbuild/fribidi.cmake" + - "winbuild/raqm.cmake" tags: - "*" pull_request: @@ -221,8 +221,6 @@ jobs: run: | for f in winbuild/build/license/*; do name=$(basename "${f%.*}") - # Skip FriBiDi license, it is not included in the wheel. - [[ $name == fribidi* ]] && continue # Skip imagequant license, it is not included in the wheel. [[ $name == libimagequant* ]] && continue echo "" >> LICENSE @@ -261,12 +259,6 @@ jobs: name: dist-windows-${{ matrix.cibw_arch }} path: ./wheelhouse/*.whl - - name: Upload fribidi.dll - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: fribidi-windows-${{ matrix.cibw_arch }} - path: winbuild\build\bin\fribidi* - sdist: if: github.event_name != 'schedule' || github.event.repository.fork == false runs-on: ubuntu-latest diff --git a/winbuild/build.rst b/winbuild/build.rst index 8aaa311b439..32b359a96e6 100644 --- a/winbuild/build.rst +++ b/winbuild/build.rst @@ -43,7 +43,7 @@ Run ``build_prepare.py`` to configure the build:: usage: winbuild\build_prepare.py [-h] [-v] [-d PILLOW_BUILD] [--depends PILLOW_DEPS] [--architecture {x86,AMD64,ARM64}] [--nmake] - [--no-imagequant] [--no-fribidi] + [--no-imagequant] [--no-raqm] Download and generate build scripts for Pillow dependencies. @@ -59,8 +59,8 @@ Run ``build_prepare.py`` to configure the build:: build architecture (default: same as host Python) --nmake build dependencies using NMake instead of Ninja --no-imagequant skip GPL-licensed optional dependency libimagequant - --no-fribidi, --no-raqm - skip LGPL-licensed optional dependency FriBiDi + --no-raqm skip optional dependency Raqm and its SheenBidi + dependency --no-avif skip optional dependency libavif Arguments can also be supplied using the environment variables PILLOW_BUILD, diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 13e0646aec4..8b005d9bd25 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -346,24 +346,6 @@ def cmd_msbuild( "headers": [r"src\*.h"], "libs": [r"*.lib"], }, - "fribidi": { - "url": f"https://github.com/fribidi/fribidi/archive/v{V['fribidi']}.zip", - "filename": f"fribidi-{V['fribidi']}.zip", - "license": "COPYING", - "build": [ - cmd_copy(r"COPYING", rf"{{bin_dir}}\fribidi-{V['fribidi']}-COPYING"), - cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"), - # generated tab.i files cannot be cross-compiled - " ^&^& ".join( - [ - "if {architecture}==ARM64 cmd /c call {vcvarsall} x86", - *cmds_cmake("fribidi-gen", "-DARCH=x86", build_dir="build_x86"), - ] - ), - *cmds_cmake("fribidi", "-DARCH={architecture}"), - ], - "bins": [r"*.dll"], - }, "sheenbidi": { "url": f"https://github.com/Tehreer/SheenBidi/archive/v{V['sheenbidi']}.tar.gz", "filename": f"SheenBidi-{V['sheenbidi']}.tar.gz", @@ -706,10 +688,9 @@ def main() -> None: help="skip GPL-licensed optional dependency libimagequant", ) parser.add_argument( - "--no-fribidi", "--no-raqm", action="store_true", - help="skip LGPL-licensed optional dependency FriBiDi", + help="skip optional dependency Raqm and its SheenBidi dependency", ) parser.add_argument( "--no-avif", @@ -754,8 +735,8 @@ def main() -> None: disabled = [] if args.no_imagequant: disabled += ["libimagequant"] - if args.no_fribidi: - disabled += ["fribidi"] + if args.no_raqm: + disabled += ["sheenbidi", "raqm"] if args.no_avif or args.architecture == "ARM64": disabled += ["libavif"] diff --git a/winbuild/fribidi.cmake b/winbuild/fribidi.cmake deleted file mode 100644 index b16e0784c98..00000000000 --- a/winbuild/fribidi.cmake +++ /dev/null @@ -1,116 +0,0 @@ -cmake_minimum_required(VERSION 3.12) - -project(fribidi) - - -add_definitions(-D_CRT_SECURE_NO_WARNINGS) - -include_directories(lib) - -function(extract_regex_1 var text regex) - string(REGEX MATCH ${regex} _ ${text}) - set(${var} "${CMAKE_MATCH_1}" PARENT_SCOPE) -endfunction() - - -function(fribidi_conf) - file(READ configure.ac FRIBIDI_CONF) - extract_regex_1(FRIBIDI_MAJOR_VERSION "${FRIBIDI_CONF}" "\\(fribidi_major_version, ([0-9]+)\\)") - extract_regex_1(FRIBIDI_MINOR_VERSION "${FRIBIDI_CONF}" "\\(fribidi_minor_version, ([0-9]+)\\)") - extract_regex_1(FRIBIDI_MICRO_VERSION "${FRIBIDI_CONF}" "\\(fribidi_micro_version, ([0-9]+)\\)") - extract_regex_1(FRIBIDI_INTERFACE_VERSION "${FRIBIDI_CONF}" "\\(fribidi_interface_version, ([0-9]+)\\)") - extract_regex_1(FRIBIDI_INTERFACE_AGE "${FRIBIDI_CONF}" "\\(fribidi_interface_age, ([0-9]+)\\)") - extract_regex_1(FRIBIDI_BINARY_AGE "${FRIBIDI_CONF}" "\\(fribidi_binary_age, ([0-9]+)\\)") - set(FRIBIDI_VERSION "${FRIBIDI_MAJOR_VERSION}.${FRIBIDI_MINOR_VERSION}.${FRIBIDI_MICRO_VERSION}") - set(PACKAGE "fribidi") - set(PACKAGE_NAME "GNU FriBidi") - set(PACKAGE_BUGREPORT "https://github.com/fribidi/fribidi/issues/new") - set(SIZEOF_INT 4) - set(FRIBIDI_MSVC_BUILD_PLACEHOLDER "#define FRIBIDI_BUILT_WITH_MSVC") - message("Detected ${PACKAGE_NAME} version ${FRIBIDI_VERSION}") - configure_file(lib/fribidi-config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/lib/fribidi-config.h @ONLY) -endfunction() -fribidi_conf() - - -option(ARCH "Target architecture") -if(${ARCH} STREQUAL ARM64) - set(GEN FALSE) -else() - set(GEN TRUE) -endif() -message("Generate tab.i files: " ${GEN}) - -function(prepend var prefix) - set(out "") - foreach(f ${ARGN}) - list(APPEND out "${prefix}${f}") - endforeach() - set(${var} "${out}" PARENT_SCOPE) -endfunction() - -macro(fribidi_definitions _TGT) - target_compile_definitions(${_TGT} PUBLIC - HAVE_MEMSET - HAVE_MEMMOVE - HAVE_STRDUP - HAVE_STDLIB_H=1 - HAVE_STRING_H=1 - HAVE_MEMORY_H=1 - #HAVE_STRINGS_H - #HAVE_SYS_TIMES_H - STDC_HEADERS=1 - HAVE_STRINGIZE=1) -endmacro() - -function(fribidi_gen _NAME _OUTNAME _PARAM) - set(_OUT ${CMAKE_CURRENT_SOURCE_DIR}/lib/${_OUTNAME}) - if(GEN) - prepend(_DEP "${CMAKE_CURRENT_SOURCE_DIR}/gen.tab/" ${ARGN}) - add_executable(gen-${_NAME} - gen.tab/gen-${_NAME}.c - gen.tab/packtab.c) - fribidi_definitions(gen-${_NAME}) - target_compile_definitions(gen-${_NAME} - PUBLIC DONT_HAVE_FRIBIDI_CONFIG_H) - add_custom_command( - COMMAND gen-${_NAME} ${_PARAM} ${_DEP} > ${_OUT} - DEPENDS ${_DEP} - OUTPUT ${_OUT}) - endif(GEN) - list(APPEND FRIBIDI_SOURCES_GENERATED "${_OUT}") - set(FRIBIDI_SOURCES_GENERATED ${FRIBIDI_SOURCES_GENERATED} PARENT_SCOPE) -endfunction() - -fribidi_gen(unicode-version fribidi-unicode-version.h "" - unidata/ReadMe.txt unidata/BidiMirroring.txt) - - -macro(fribidi_tab _NAME) - fribidi_gen(${_NAME}-tab ${_NAME}.tab.i 2 ${ARGN}) - if(GEN) - target_sources(gen-${_NAME}-tab - PRIVATE lib/fribidi-unicode-version.h) - endif(GEN) -endmacro() - -fribidi_tab(bidi-type unidata/UnicodeData.txt) -fribidi_tab(joining-type unidata/UnicodeData.txt unidata/ArabicShaping.txt) -fribidi_tab(arabic-shaping unidata/UnicodeData.txt) -fribidi_tab(mirroring unidata/BidiMirroring.txt) -fribidi_tab(brackets unidata/BidiBrackets.txt unidata/UnicodeData.txt) -fribidi_tab(brackets-type unidata/BidiBrackets.txt) - -add_custom_target(fribidi-gen DEPENDS ${FRIBIDI_SOURCES_GENERATED}) - - -file(GLOB FRIBIDI_SOURCES lib/*.c) -file(GLOB FRIBIDI_HEADERS lib/*.h) - -add_library(fribidi SHARED - ${FRIBIDI_SOURCES} - ${FRIBIDI_HEADERS} - ${FRIBIDI_SOURCES_GENERATED}) -fribidi_definitions(fribidi) -target_compile_definitions(fribidi - PUBLIC "-DFRIBIDI_BUILD") From 16225fddee0f6b0c5afe6e685c53964dd98f20b7 Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Sat, 29 Aug 2026 12:03:56 +0300 Subject: [PATCH 4/7] Enable Raqm in iOS wheels There are no licensing issues now with FriBiDi being replaced with SheenBidi which is statically built as well. --- .github/workflows/wheels-dependencies.sh | 11 ++++------- checks/check_wheel.py | 3 --- pyproject.toml | 3 +-- setup.py | 7 +++++-- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 778d705c95a..e02d34b4d95 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -167,7 +167,7 @@ function build_harfbuzz { local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz) (cd $out_dir \ - && meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dfreetype=enabled -Dglib=disabled -Dtests=disabled $HOST_MESON_FLAGS) + && meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dfreetype=enabled -Dglib=disabled -Dtests=disabled -Dutilities=disabled $HOST_MESON_FLAGS) (cd $out_dir/build \ && meson install) touch harfbuzz-stamp @@ -337,12 +337,9 @@ function build { build_freetype fi - if [[ -z "$IOS_SDK" ]]; then - # TODO: raqm is not built for iOS - build_harfbuzz - build_sheenbidi - build_raqm - fi + build_harfbuzz + build_sheenbidi + build_raqm } function create_meson_cross_config { diff --git a/checks/check_wheel.py b/checks/check_wheel.py index ed714d682a5..e9382051260 100644 --- a/checks/check_wheel.py +++ b/checks/check_wheel.py @@ -45,8 +45,5 @@ def test_wheel_features() -> None: if sys.platform == "win32": expected_features.remove("xcb") - elif sys.platform == "ios": - # TODO: raqm is not built for iOS - expected_features.remove("raqm") assert set(features.get_supported_features()) == expected_features diff --git a/pyproject.toml b/pyproject.toml index 7eed34ff847..3f1b30b3539 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,8 +113,7 @@ macos.environment.PATH = """\ $(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin\ """ # Disable platform guessing on iOS -# TODO: raqm is not built for iOS -ios.config-settings = "raqm=disable imagequant=disable platform-guessing=disable" +ios.config-settings = "raqm=enable imagequant=disable platform-guessing=disable" # iOS needs to be given a specific pytest invocation and list of test sources. ios.test-command = [ "python -m selftest", diff --git a/setup.py b/setup.py index be0fb155d64..c2332b7422f 100644 --- a/setup.py +++ b/setup.py @@ -894,8 +894,11 @@ def build_extensions(self) -> None: defs.append(("HAVE_RAQM", None)) libs.append(feature.get("raqm")) - if feature.get("raqm") and sys.platform == "win32": - # Raqm is linked statically on Windows, so link what it needs. + if ( + feature.get("raqm") + and sys.platform in ["ios", "win32"] + and _find_library_file(self, "SheenBidi") + ): libs.extend(["harfbuzz", "SheenBidi"]) if sys.platform == "ios": From 547de853c2a1e7db6c3a35f82b38ae0652a7ef5a Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Sat, 29 Aug 2026 12:04:44 +0300 Subject: [PATCH 5/7] Build Raqm against SheenBidi in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Linux runner now builds Raqm against SheenBidi like wheel sdo. Raqm with FriBiDi path is still covered by Homebrew’s libraqm on macOS and the MSYS2 one on Windows. --- .ci/install.sh | 3 +-- depends/README.rst | 2 +- depends/install_raqm.sh | 2 +- depends/install_sheenbidi.sh | 14 ++++++++++++++ 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100755 depends/install_sheenbidi.sh diff --git a/.ci/install.sh b/.ci/install.sh index f68bd237e3e..2c9e013c52d 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -22,7 +22,6 @@ packages=( ghostscript imagemagick # ImageMagick is used by Tests/test_file_palm.py libfreetype6-dev - libfribidi-dev libharfbuzz-dev libjpeg-turbo8-dev liblcms2-dev @@ -57,7 +56,7 @@ pushd depends && ./install_webp.sh && popd pushd depends && ./install_imagequant.sh && popd # raqm -pushd depends && sudo ./install_raqm.sh && popd +pushd depends && sudo ./install_sheenbidi.sh && sudo ./install_raqm.sh && popd # libavif pushd depends && ./install_libavif.sh && popd diff --git a/depends/README.rst b/depends/README.rst index 7ef4edfd652..b69b5b0cc4c 100644 --- a/depends/README.rst +++ b/depends/README.rst @@ -2,7 +2,7 @@ Depends ======= ``install_openjpeg.sh``, ``install_webp.sh``, ``install_imagequant.sh``, -``install_raqm.sh`` can be used to download, +``install_sheenbidi.sh``, and ``install_raqm.sh`` can be used to download, build & install non-packaged dependencies; useful for testing on CI. ``install_extra_test_images.sh`` can be used to install additional test images diff --git a/depends/install_raqm.sh b/depends/install_raqm.sh index f2dcb8f5278..cb838a32947 100755 --- a/depends/install_raqm.sh +++ b/depends/install_raqm.sh @@ -8,6 +8,6 @@ archive=libraqm-0.11.0 pushd $archive -meson build --prefix=/usr && ninja -C build install +meson build --prefix=/usr -Dsheenbidi=true && ninja -C build install popd diff --git a/depends/install_sheenbidi.sh b/depends/install_sheenbidi.sh new file mode 100755 index 00000000000..19dc8b51fbf --- /dev/null +++ b/depends/install_sheenbidi.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# install sheenbidi + +version=3.0.0 + +archive=SheenBidi-$version + +./download-and-extract.sh $archive https://github.com/Tehreer/SheenBidi/archive/refs/tags/v$version.tar.gz + +pushd $archive + +meson setup build --prefix=/usr && ninja -C build install + +popd From db52c0a53bb6bc97c2354336c96497156ea5c33c Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Sat, 29 Aug 2026 12:06:53 +0300 Subject: [PATCH 6/7] Document Raqm changes --- docs/deprecations.rst | 9 ++++++ docs/installation/basic-installation.rst | 9 ++---- docs/installation/building-from-source.rst | 26 ++++++--------- docs/releasenotes/13.0.0.rst | 37 ++++++++++++++++++++++ 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 882221177c1..e323e3bfc12 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -41,6 +41,15 @@ JpegImageFile.load_djpeg Use the built-in JPEG decoder instead, or call ``djpeg`` directly and decode the resulting image with Pillow. +fribidi and harfbuzz features +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 13.0.0 + +``fribidi`` and ``harfbuzz`` are deprecated aliases of ``raqm``. Raqm selects its +own bidirectional text and shaping libraries when it is built, so Pillow cannot +reliably report which ones are in use. + Removed features ---------------- diff --git a/docs/installation/basic-installation.rst b/docs/installation/basic-installation.rst index 142d8d0cd36..02cbdea62c5 100644 --- a/docs/installation/basic-installation.rst +++ b/docs/installation/basic-installation.rst @@ -32,8 +32,7 @@ and :pypi:`olefile` for Pillow to read FPX and MIC images:: We provide binaries for Linux for each of the supported Python versions in the manylinux wheel format. These include support for all - optional libraries except libimagequant. Raqm support requires - FriBiDi to be installed separately:: + optional libraries except libimagequant:: python3 -m pip install --upgrade pip python3 -m pip install --upgrade Pillow @@ -47,8 +46,7 @@ and :pypi:`olefile` for Pillow to read FPX and MIC images:: We provide binaries for macOS for each of the supported Python versions in the wheel format. These include support for all optional - libraries except libimagequant. Raqm support requires - FriBiDi to be installed separately:: + libraries except libimagequant:: python3 -m pip install --upgrade pip python3 -m pip install --upgrade Pillow @@ -70,8 +68,7 @@ and :pypi:`olefile` for Pillow to read FPX and MIC images:: We provide Pillow binaries for Windows compiled for the matrix of supported Pythons in the wheel format. These include x86, x86-64 and arm64 versions. These binaries include support - for all optional libraries except libimagequant and libxcb. Raqm support - requires FriBiDi to be installed separately:: + for all optional libraries except libimagequant and libxcb:: python3 -m pip install --upgrade pip python3 -m pip install --upgrade Pillow diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 93dd6121646..5423e758d01 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -71,21 +71,13 @@ Many of Pillow's features require external libraries: * **libraqm** provides complex text layout support. - * libraqm provides bidirectional text support (using FriBiDi), + * libraqm provides bidirectional text support (using SheenBidi or FriBiDi), shaping (using HarfBuzz), and proper script itemization. As a result, Raqm can support most writing systems covered by Unicode. - * libraqm depends on the following libraries: FreeType, HarfBuzz, - FriBiDi, make sure that you install them before installing libraqm - if not available as package in your system. + * libraqm depends on the following libraries: FreeType, HarfBuzz, and either + SheenBidi or FriBiDi. Make sure that you install them before installing + libraqm if not available as package in your system. * Setting text direction or font features is not supported without libraqm. - * Pillow wheels since version 8.2.0 include a modified version of libraqm that - loads libfribidi at runtime if it is installed. - On Windows this requires compiling FriBiDi and installing ``fribidi.dll`` - into a directory listed in the `Dynamic-link library search order (Microsoft Learn) - `_ - (``fribidi-0.dll`` or ``libfribidi-0.dll`` are also detected). - See `Build Options`_ to see how to build this version. - * Previous versions of Pillow (5.0.0 to 8.1.2) linked libraqm dynamically at runtime. * **libxcb** provides X11 screengrab support. @@ -120,10 +112,10 @@ Many of Pillow's features require external libraries: sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \ libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \ - libharfbuzz-dev libfribidi-dev libxcb1-dev + libharfbuzz-dev libxcb1-dev To install libraqm, ``sudo apt-get install meson`` and then see - ``depends/install_raqm.sh``. + ``depends/install_sheenbidi.sh`` and ``depends/install_raqm.sh``. Build prerequisites for libavif on Ubuntu are installed with:: @@ -135,7 +127,7 @@ Many of Pillow's features require external libraries: sudo dnf install libtiff-devel libjpeg-devel openjpeg2-devel zlib-devel \ freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel \ - harfbuzz-devel fribidi-devel libraqm-devel libimagequant-devel libxcb-devel + libraqm-devel libimagequant-devel libxcb-devel Note that the package manager may be yum or DNF, depending on the exact distribution. @@ -143,7 +135,7 @@ Many of Pillow's features require external libraries: Prerequisites are installed for **Alpine** with:: sudo apk add tiff-dev jpeg-dev openjpeg-dev zlib-dev freetype-dev lcms2-dev \ - libwebp-dev tcl-dev tk-dev harfbuzz-dev fribidi-dev libimagequant-dev \ + libwebp-dev tcl-dev tk-dev libraqm-dev libimagequant-dev \ libxcb-dev libpng-dev See also the ``Dockerfile``\s in the Test Infrastructure repo @@ -222,7 +214,7 @@ Many of Pillow's features require external libraries: Prerequisites are installed on **FreeBSD 15.1** with:: - sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi libxcb libavif libraqm libimagequant + sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg libxcb libavif libraqm libimagequant .. tab:: Android diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst index 6962851c20f..04060270814 100644 --- a/docs/releasenotes/13.0.0.rst +++ b/docs/releasenotes/13.0.0.rst @@ -71,6 +71,14 @@ ExifTags.IFD.Makernote ``ExifTags.IFD.Makernote`` has been removed. Instead, use ``ExifTags.IFD.MakerNote``. +Raqm is no longer bundled +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Pillow used to compile a bundled copy of libraqm, together with a shim that loaded +libfribidi at runtime, because FriBiDi is LGPL-licensed and could not be linked into +Pillow's wheels. libraqm is now linked like any other dependency, so building Pillow +with text layout support requires libraqm to be installed. + Deprecations ============ @@ -89,6 +97,20 @@ ImageQt align8to32() ``ImageQt.align8to32()`` has been deprecated. This was an undocumented helper function intended for internal use, so there is no replacement. +fribidi and harfbuzz features +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``fribidi`` and ``harfbuzz`` are now deprecated aliases of ``raqm``. Raqm selects its +own bidirectional text and shaping libraries when it is built, so Pillow cannot +reliably report which ones are in use. + +raqm=vendor and fribidi=vendor config settings +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``-C raqm=vendor`` and ``-C fribidi=vendor`` build config settings are deprecated +and ignored. Raqm is no longer bundled, so there is nothing to vendor. + + API changes =========== @@ -119,6 +141,21 @@ Two new filters are available for :py:meth:`~PIL.Image.Image.resize` and Other changes ============= +Wheels no longer need FriBiDi at runtime +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Wheels bundle Raqm built against SheenBidi, which is Apache-2.0-licensed and so can +be shipped. Text layout works out of the box, where it previously required a system +copy of libfribidi that the bundled shim could load at import time. On Windows this +means ``fribidi.dll`` no longer has to be installed separately. + +iOS wheels now support Raqm +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Raqm was disabled in iOS wheels because FriBiDi could not be shipped and iOS cannot +load a system copy at runtime. iOS wheels now support text layout like every other +platform. + Python 3.15 ^^^^^^^^^^^ From 55b80613e294c90cab6acac80c710ceea3345475 Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Sat, 29 Aug 2026 13:34:45 +0300 Subject: [PATCH 7/7] Update Raqm license text The shipped copy carried a 2016 copyright line and an old address. Take the text from the Raqm 0.11.0 release. --- wheels/dependency_licenses/RAQM.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wheels/dependency_licenses/RAQM.txt b/wheels/dependency_licenses/RAQM.txt index 196511ef688..99468479637 100644 --- a/wheels/dependency_licenses/RAQM.txt +++ b/wheels/dependency_licenses/RAQM.txt @@ -1,7 +1,7 @@ The MIT License (MIT) Copyright © 2015 Information Technology Authority (ITA) -Copyright © 2016 Khaled Hosny +Copyright © 2016-2026 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