From 584b1fd01dfe4c60e465220e2aef14966f5e9911 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 26 Aug 2025 04:18:02 -0400 Subject: [PATCH 1/6] Remove version from FreeType wrap file Latest Meson (1.9.0 at this time) is now erroring because of the Harfbuzz contains a differently-named wrap file that provides `freetype2`. This may be a bug upstream, but we really don't need the version in the filename. --- extern/meson.build | 8 +------- lib/matplotlib/__init__.py | 2 +- subprojects/{freetype-2.13.3.wrap => freetype2.wrap} | 4 ++++ 3 files changed, 6 insertions(+), 8 deletions(-) rename subprojects/{freetype-2.13.3.wrap => freetype2.wrap} (69%) diff --git a/extern/meson.build b/extern/meson.build index 7f7c2511c3d5..f4e14530369d 100644 --- a/extern/meson.build +++ b/extern/meson.build @@ -9,14 +9,8 @@ subdir('agg24-svn') if get_option('system-freetype') freetype_dep = dependency('freetype2', version: '>=9.11.3') else - # This is the version of FreeType to use when building a local version. It - # must match the value in `lib/matplotlib.__init__.py`. Also update the docs - # in `docs/devel/dependencies.rst`. Bump the cache key in - # `.circleci/config.yml` when changing requirements. - LOCAL_FREETYPE_VERSION = '2.13.3' - freetype_proj = subproject( - f'freetype-@LOCAL_FREETYPE_VERSION@', + 'freetype2', default_options: [ 'default_library=static', 'brotli=disabled', diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 651936dc19c2..dc6b703e942a 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1333,7 +1333,7 @@ def _val_or_rc(val, *rc_names): def _init_tests(): # The version of FreeType to install locally for running the tests. This must match - # the value in `meson.build`. + # the value in `subprojects/freetype2.wrap`. LOCAL_FREETYPE_VERSION = '2.13.3' from matplotlib import ft2font diff --git a/subprojects/freetype-2.13.3.wrap b/subprojects/freetype2.wrap similarity index 69% rename from subprojects/freetype-2.13.3.wrap rename to subprojects/freetype2.wrap index 68f688a35861..e1d0fb112ca9 100644 --- a/subprojects/freetype-2.13.3.wrap +++ b/subprojects/freetype2.wrap @@ -1,3 +1,7 @@ +# This is the version of FreeType to use when building a local version. It +# must match the value in `lib/matplotlib.__init__.py`. Also update the docs +# in `docs/devel/dependencies.rst`. Bump the cache key in +# `.circleci/config.yml` when changing requirements. [wrap-file] directory = freetype-2.13.3 source_url = https://download.savannah.gnu.org/releases/freetype/freetype-2.13.3.tar.xz From 0635d3ab2445d2e8ea1a16a3882835b5b950e7a6 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 18 Dec 2024 22:04:51 -0500 Subject: [PATCH 2/6] Add libraqm and its dependencies to build Also add some missing license entries in more places. --- LICENSE/LICENSE_FREETYPE | 46 ++++ LICENSE/LICENSE_HARFBUZZ | 42 ++++ LICENSE/LICENSE_LIBRAQM | 22 ++ LICENSE/LICENSE_SHEENBIDI | 202 ++++++++++++++++++ doc/project/license.rst | 32 +++ extern/meson.build | 29 +++ lib/matplotlib/ft2font.pyi | 1 + meson.build | 8 +- meson.options | 2 + src/ft2font.h | 2 + src/ft2font_wrapper.cpp | 1 + src/meson.build | 2 +- subprojects/harfbuzz.wrap | 13 ++ subprojects/libraqm-0.10.3.wrap | 8 + .../harfbuzz-11.2.0-bundle-freetype.patch | 36 ++++ .../libraqm-0.10.2-bundle-freetype.patch | 11 + subprojects/packagefiles/libraqm-203.patch | 27 +++ subprojects/sheenbidi.wrap | 5 + 18 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 LICENSE/LICENSE_FREETYPE create mode 100644 LICENSE/LICENSE_HARFBUZZ create mode 100644 LICENSE/LICENSE_LIBRAQM create mode 100755 LICENSE/LICENSE_SHEENBIDI create mode 100644 subprojects/harfbuzz.wrap create mode 100644 subprojects/libraqm-0.10.3.wrap create mode 100644 subprojects/packagefiles/harfbuzz-11.2.0-bundle-freetype.patch create mode 100644 subprojects/packagefiles/libraqm-0.10.2-bundle-freetype.patch create mode 100644 subprojects/packagefiles/libraqm-203.patch create mode 100644 subprojects/sheenbidi.wrap diff --git a/LICENSE/LICENSE_FREETYPE b/LICENSE/LICENSE_FREETYPE new file mode 100644 index 000000000000..8b9ce9e2e6e3 --- /dev/null +++ b/LICENSE/LICENSE_FREETYPE @@ -0,0 +1,46 @@ +FREETYPE LICENSES +----------------- + +The FreeType 2 font engine is copyrighted work and cannot be used +legally without a software license. In order to make this project +usable to a vast majority of developers, we distribute it under two +mutually exclusive open-source licenses. + +This means that *you* must choose *one* of the two licenses described +below, then obey all its terms and conditions when using FreeType 2 in +any of your projects or products. + + - The FreeType License, found in the file `docs/FTL.TXT`, which is + similar to the original BSD license *with* an advertising clause + that forces you to explicitly cite the FreeType project in your + product's documentation. All details are in the license file. + This license is suited to products which don't use the GNU General + Public License. + + Note that this license is compatible to the GNU General Public + License version 3, but not version 2. + + - The GNU General Public License version 2, found in + `docs/GPLv2.TXT` (any later version can be used also), for + programs which already use the GPL. Note that the FTL is + incompatible with GPLv2 due to its advertisement clause. + +The contributed BDF and PCF drivers come with a license similar to +that of the X Window System. It is compatible to the above two +licenses (see files `src/bdf/README` and `src/pcf/README`). The same +holds for the source code files `src/base/fthash.c` and +`include/freetype/internal/fthash.h`; they were part of the BDF driver +in earlier FreeType versions. + +The gzip module uses the zlib license (see `src/gzip/zlib.h`) which +too is compatible to the above two licenses. + +The files `src/autofit/ft-hb.c` and `src/autofit/ft-hb.h` contain code +taken almost verbatim from the HarfBuzz file `hb-ft.cc`, which uses +the 'Old MIT' license, compatible to the above two licenses. + +The MD5 checksum support (only used for debugging in development +builds) is in the public domain. + + +--- end of LICENSE.TXT --- diff --git a/LICENSE/LICENSE_HARFBUZZ b/LICENSE/LICENSE_HARFBUZZ new file mode 100644 index 000000000000..1dd917e9f2e7 --- /dev/null +++ b/LICENSE/LICENSE_HARFBUZZ @@ -0,0 +1,42 @@ +HarfBuzz is licensed under the so-called "Old MIT" license. Details follow. +For parts of HarfBuzz that are licensed under different licenses see individual +files names COPYING in subdirectories where applicable. + +Copyright © 2010-2022 Google, Inc. +Copyright © 2015-2020 Ebrahim Byagowi +Copyright © 2019,2020 Facebook, Inc. +Copyright © 2012,2015 Mozilla Foundation +Copyright © 2011 Codethink Limited +Copyright © 2008,2010 Nokia Corporation and/or its subsidiary(-ies) +Copyright © 2009 Keith Stribley +Copyright © 2011 Martin Hosken and SIL International +Copyright © 2007 Chris Wilson +Copyright © 2005,2006,2020,2021,2022,2023 Behdad Esfahbod +Copyright © 2004,2007,2008,2009,2010,2013,2021,2022,2023 Red Hat, Inc. +Copyright © 1998-2005 David Turner and Werner Lemberg +Copyright © 2016 Igalia S.L. +Copyright © 2022 Matthias Clasen +Copyright © 2018,2021 Khaled Hosny +Copyright © 2018,2019,2020 Adobe, Inc +Copyright © 2013-2015 Alexei Podtelezhnikov + +For full copyright notices consult the individual files in the package. + + +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. diff --git a/LICENSE/LICENSE_LIBRAQM b/LICENSE/LICENSE_LIBRAQM new file mode 100644 index 000000000000..97e2489b7798 --- /dev/null +++ b/LICENSE/LICENSE_LIBRAQM @@ -0,0 +1,22 @@ +The MIT License (MIT) + +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. diff --git a/LICENSE/LICENSE_SHEENBIDI b/LICENSE/LICENSE_SHEENBIDI new file mode 100755 index 000000000000..d64569567334 --- /dev/null +++ b/LICENSE/LICENSE_SHEENBIDI @@ -0,0 +1,202 @@ + + 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/doc/project/license.rst b/doc/project/license.rst index eba9ef23cf62..2cad3f25b95d 100644 --- a/doc/project/license.rst +++ b/doc/project/license.rst @@ -71,6 +71,38 @@ Bundled software .. literalinclude:: ../../LICENSE/LICENSE_QT4_EDITOR :language: none +Rendering software +------------------ + +.. dropdown:: Agg + :class-container: sdd + + .. literalinclude:: ../../extern/agg24-svn/src/copying + :language: none + +.. dropdown:: FreeType + :class-container: sdd + + .. literalinclude:: ../../LICENSE/LICENSE_FREETYPE + :language: none + +.. dropdown:: Harfbuzz + :class-container: sdd + + .. literalinclude:: ../../LICENSE/LICENSE_HARFBUZZ + :language: none + +.. dropdown:: libraqm + :class-container: sdd + + .. literalinclude:: ../../LICENSE/LICENSE_LIBRAQM + :language: none + +.. dropdown:: SheenBidi + :class-container: sdd + + .. literalinclude:: ../../LICENSE/LICENSE_SHEENBIDI + :language: none .. _licenses-cmaps-styles: diff --git a/extern/meson.build b/extern/meson.build index f4e14530369d..2723baa47505 100644 --- a/extern/meson.build +++ b/extern/meson.build @@ -24,6 +24,35 @@ else freetype_dep = freetype_proj.get_variable('freetype_dep') endif +if get_option('system-libraqm') + libraqm_dep = dependency('raqm', version: '>=0.10.3') +else + subproject('harfbuzz', + default_options: [ + 'default_library=static', + 'cairo=disabled', + 'coretext=disabled', + 'directwrite=disabled', + 'fontations=disabled', + 'freetype=enabled', + 'gdi=disabled', + 'glib=disabled', + 'gobject=disabled', + 'harfruzz=disabled', + 'icu=disabled', + 'tests=disabled', + ] + ) + subproject('sheenbidi', default_options: ['default_library=static']) + libraqm_proj = subproject('libraqm-0.10.3', + default_options: [ + 'default_library=static', + 'sheenbidi=true', + ] + ) + libraqm_dep = libraqm_proj.get_variable('libraqm_dep') +endif + if get_option('system-qhull') qhull_dep = dependency('qhull_r', version: '>=8.0.2', required: false) if not qhull_dep.found() diff --git a/lib/matplotlib/ft2font.pyi b/lib/matplotlib/ft2font.pyi index a4ddc84358c1..55c076bb68b6 100644 --- a/lib/matplotlib/ft2font.pyi +++ b/lib/matplotlib/ft2font.pyi @@ -8,6 +8,7 @@ from numpy.typing import NDArray __freetype_build_type__: str __freetype_version__: str +__libraqm_version__: str # We can't change the type hints for standard library chr/ord, so character codes are a # simple type alias. diff --git a/meson.build b/meson.build index 54249473fe8e..239ae7827b73 100644 --- a/meson.build +++ b/meson.build @@ -7,18 +7,24 @@ project( '-m', 'setuptools_scm', check: true).stdout().strip(), # qt_editor backend is MIT # ResizeObserver at end of lib/matplotlib/backends/web_backend/js/mpl.js is CC0 - # Carlogo, STIX and Computer Modern is OFL + # Carlogo, STIX, Computer Modern, and Last Resort are OFL # DejaVu is Bitstream Vera and Public Domain license: 'PSF-2.0 AND MIT AND CC0-1.0 AND OFL-1.1 AND Bitstream-Vera AND Public-Domain', license_files: [ 'LICENSE/LICENSE', + 'extern/agg24-svn/src/copying', 'LICENSE/LICENSE_AMSFONTS', 'LICENSE/LICENSE_BAKOMA', 'LICENSE/LICENSE_CARLOGO', 'LICENSE/LICENSE_COLORBREWER', 'LICENSE/LICENSE_COURIERTEN', + 'LICENSE/LICENSE_FREETYPE', + 'LICENSE/LICENSE_HARFBUZZ', 'LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER', + 'LICENSE/LICENSE_LAST_RESORT_FONT', + 'LICENSE/LICENSE_LIBRAQM', 'LICENSE/LICENSE_QT4_EDITOR', + 'LICENSE/LICENSE_SHEENBIDI', 'LICENSE/LICENSE_SOLARIZED', 'LICENSE/LICENSE_STIX', 'LICENSE/LICENSE_YORICK', diff --git a/meson.options b/meson.options index d21cbedb9bb9..7e03ff405f85 100644 --- a/meson.options +++ b/meson.options @@ -7,6 +7,8 @@ # FreeType on AIX. option('system-freetype', type: 'boolean', value: false, description: 'Build against system version of FreeType') +option('system-libraqm', type: 'boolean', value: false, + description: 'Build against system version of libraqm') option('system-qhull', type: 'boolean', value: false, description: 'Build against system version of Qhull') diff --git a/src/ft2font.h b/src/ft2font.h index 80bc490f4bad..ffaf511ab9ca 100644 --- a/src/ft2font.h +++ b/src/ft2font.h @@ -26,6 +26,8 @@ extern "C" { #include FT_TRUETYPE_TABLES_H } +#include + namespace py = pybind11; // By definition, FT_FIXED as 2 16bit values stored in a single long. diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 99023836b001..65fcb4b7e013 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -1690,6 +1690,7 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) m.attr("__freetype_version__") = version_string; m.attr("__freetype_build_type__") = FREETYPE_BUILD_TYPE; + m.attr("__libraqm_version__") = raqm_version_string(); auto py_int = py::module_::import("builtins").attr("int"); m.attr("CharacterCodeType") = py_int; m.attr("GlyphIndexType") = py_int; diff --git a/src/meson.build b/src/meson.build index d479a8b84aa2..8b52bf739c03 100644 --- a/src/meson.build +++ b/src/meson.build @@ -53,7 +53,7 @@ extension_data = { 'ft2font_wrapper.cpp', ), 'dependencies': [ - freetype_dep, pybind11_dep, agg_dep.partial_dependency(includes: true), + freetype_dep, libraqm_dep, pybind11_dep, agg_dep.partial_dependency(includes: true), ], 'cpp_args': [ '-DFREETYPE_BUILD_TYPE="@0@"'.format( diff --git a/subprojects/harfbuzz.wrap b/subprojects/harfbuzz.wrap new file mode 100644 index 000000000000..cc5e227f0ca2 --- /dev/null +++ b/subprojects/harfbuzz.wrap @@ -0,0 +1,13 @@ +[wrap-file] +directory = harfbuzz-11.2.1 +source_url = https://github.com/harfbuzz/harfbuzz/releases/download/11.2.1/harfbuzz-11.2.1.tar.xz +source_filename = harfbuzz-11.2.1.tar.xz +source_hash = 093714c8548a285094685f0bdc999e202d666b59eeb3df2ff921ab68b8336a49 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/harfbuzz_11.2.1-1/harfbuzz-11.2.1.tar.xz +wrapdb_version = 11.2.1-1 + +# This patch allows using our bundled FreeType. +diff_files = harfbuzz-11.2.0-bundle-freetype.patch + +[provide] +dependency_names = harfbuzz, harfbuzz-cairo, harfbuzz-gobject, harfbuzz-icu, harfbuzz-subset diff --git a/subprojects/libraqm-0.10.3.wrap b/subprojects/libraqm-0.10.3.wrap new file mode 100644 index 000000000000..87061a231cba --- /dev/null +++ b/subprojects/libraqm-0.10.3.wrap @@ -0,0 +1,8 @@ +[wrap-file] +source_url = https://github.com/HOST-Oman/libraqm/archive/v0.10.3/libraqm-0.10.3.tar.gz +source_filename = libraqm-0.10.3.tar.gz +source_hash = fe1fe28b32f97ef97b325ca5d2defb0704da1ef048372ec20e85e1f587e20965 + +# First patch allows using our bundled FreeType. +# Second patch is for use as a subproject https://github.com/HOST-Oman/libraqm/pull/203 +diff_files = libraqm-0.10.2-bundle-freetype.patch, libraqm-203.patch diff --git a/subprojects/packagefiles/harfbuzz-11.2.0-bundle-freetype.patch b/subprojects/packagefiles/harfbuzz-11.2.0-bundle-freetype.patch new file mode 100644 index 000000000000..fa7be0b54afd --- /dev/null +++ b/subprojects/packagefiles/harfbuzz-11.2.0-bundle-freetype.patch @@ -0,0 +1,36 @@ +diff -uPNr harfbuzz-11.2.0.orig/meson.build harfbuzz-11.2.0/meson.build +--- harfbuzz-11.2.0.orig/meson.build 2025-04-28 08:56:32.000000000 -0400 ++++ harfbuzz-11.2.0/meson.build 2025-05-03 03:25:39.602646412 -0400 +@@ -115,31 +115,7 @@ + # Sadly, FreeType's versioning schemes are different between pkg-config and CMake + + # Try pkg-config name +- freetype_dep = dependency('freetype2', +- version: freetype_min_version, +- method: 'pkg-config', +- required: false, +- allow_fallback: false) +- if not freetype_dep.found() +- # Try cmake name +- freetype_dep = dependency('Freetype', +- version: freetype_min_version_actual, +- method: 'cmake', +- required: false, +- allow_fallback: false) +- # Subproject fallback +- if not freetype_dep.found() +- freetype_proj = subproject('freetype2', +- version: freetype_min_version_actual, +- required: get_option('freetype'), +- default_options: ['harfbuzz=disabled']) +- if freetype_proj.found() +- freetype_dep = freetype_proj.get_variable('freetype_dep') +- else +- freetype_dep = dependency('', required: false) +- endif +- endif +- endif ++ freetype_dep = dependency('freetype2', version: freetype_min_version) + endif + + glib_dep = dependency('glib-2.0', version: glib_min_version, required: get_option('glib')) diff --git a/subprojects/packagefiles/libraqm-0.10.2-bundle-freetype.patch b/subprojects/packagefiles/libraqm-0.10.2-bundle-freetype.patch new file mode 100644 index 000000000000..5e9a6b7f9ed5 --- /dev/null +++ b/subprojects/packagefiles/libraqm-0.10.2-bundle-freetype.patch @@ -0,0 +1,11 @@ +--- a/meson.build 2025-03-26 03:32:12.444735795 -0400 ++++ b/meson.build 2025-03-26 03:32:16.117435140 -0400 +@@ -45,8 +45,7 @@ + if not freetype.found() + freetype = dependency( + 'freetype2', + version: '>= @0@'.format(freetype_version[0]), +- method: 'pkg-config', + fallback: ['freetype2', 'freetype_dep'], + default_options: [ + 'png=disabled', diff --git a/subprojects/packagefiles/libraqm-203.patch b/subprojects/packagefiles/libraqm-203.patch new file mode 100644 index 000000000000..6628fec1d111 --- /dev/null +++ b/subprojects/packagefiles/libraqm-203.patch @@ -0,0 +1,27 @@ +From 8cedfc989998bb2cf23c2c1b40802effad72b0ed Mon Sep 17 00:00:00 2001 +From: Elliott Sales de Andrade +Date: Thu, 7 Aug 2025 18:07:15 -0400 +Subject: [PATCH] Add dependency override for use as a subproject + +--- + src/meson.build | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/src/meson.build b/src/meson.build +index 0a32f832..ca7c13d1 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -42,6 +42,13 @@ libraqm = library( + install: true, + ) + ++libraqm_dep = declare_dependency( ++ include_directories: include_directories('.'), ++ link_with: libraqm, ++) ++ ++meson.override_dependency(meson.project_name(), libraqm_dep) ++ + libraqm_test = static_library( + 'raqm-test', + 'raqm.c', diff --git a/subprojects/sheenbidi.wrap b/subprojects/sheenbidi.wrap new file mode 100644 index 000000000000..c58277d47499 --- /dev/null +++ b/subprojects/sheenbidi.wrap @@ -0,0 +1,5 @@ +[wrap-file] +directory = SheenBidi-2.9.0 +source_url = https://github.com/Tehreer/SheenBidi/archive/refs/tags/v2.9.0/sheenbidi-2.9.0.tar.gz +source_filename = sheenbidi-2.9.0.tar.gz +source_hash = e90ae142c6fc8b94366f3526f84b349a2c10137f87093db402fe51f6eace6d13 From b0ded3aadda70932fa2130df61ed9629cd7d54e4 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 26 Feb 2025 09:27:53 -0500 Subject: [PATCH 3/6] Implement text shaping with libraqm --- lib/matplotlib/_text_helpers.py | 17 ----- lib/matplotlib/tests/test_ft2font.py | 6 +- lib/matplotlib/tests/test_text.py | 24 +++---- src/ft2font.cpp | 97 ++++++++++++++++++---------- 4 files changed, 78 insertions(+), 66 deletions(-) diff --git a/lib/matplotlib/_text_helpers.py b/lib/matplotlib/_text_helpers.py index 1a9b4e4c989c..a874c8f4bf81 100644 --- a/lib/matplotlib/_text_helpers.py +++ b/lib/matplotlib/_text_helpers.py @@ -25,23 +25,6 @@ def warn_on_missing_glyph(codepoint, fontnames): f"({chr(codepoint).encode('ascii', 'namereplace').decode('ascii')}) " f"missing from font(s) {fontnames}.") - block = ("Hebrew" if 0x0590 <= codepoint <= 0x05ff else - "Arabic" if 0x0600 <= codepoint <= 0x06ff else - "Devanagari" if 0x0900 <= codepoint <= 0x097f else - "Bengali" if 0x0980 <= codepoint <= 0x09ff else - "Gurmukhi" if 0x0a00 <= codepoint <= 0x0a7f else - "Gujarati" if 0x0a80 <= codepoint <= 0x0aff else - "Oriya" if 0x0b00 <= codepoint <= 0x0b7f else - "Tamil" if 0x0b80 <= codepoint <= 0x0bff else - "Telugu" if 0x0c00 <= codepoint <= 0x0c7f else - "Kannada" if 0x0c80 <= codepoint <= 0x0cff else - "Malayalam" if 0x0d00 <= codepoint <= 0x0d7f else - "Sinhala" if 0x0d80 <= codepoint <= 0x0dff else - None) - if block: - _api.warn_external( - f"Matplotlib currently does not support {block} natively.") - def layout(string, font, *, kern_mode=Kerning.DEFAULT): """ diff --git a/lib/matplotlib/tests/test_ft2font.py b/lib/matplotlib/tests/test_ft2font.py index 6b405287e5d7..70e611e17bcc 100644 --- a/lib/matplotlib/tests/test_ft2font.py +++ b/lib/matplotlib/tests/test_ft2font.py @@ -775,9 +775,9 @@ def test_ft2font_set_text(): xys = font.set_text('AADAT.XC-J') np.testing.assert_array_equal( xys, - [(0, 0), (512, 0), (1024, 0), (1600, 0), (2112, 0), (2496, 0), (2688, 0), - (3200, 0), (3712, 0), (4032, 0)]) - assert font.get_width_height() == (4288, 768) + [(0, 0), (533, 0), (1045, 0), (1608, 0), (2060, 0), (2417, 0), (2609, 0), + (3065, 0), (3577, 0), (3940, 0)]) + assert font.get_width_height() == (4196, 768) assert font.get_num_glyphs() == 10 assert font.get_descent() == 192 assert font.get_bitmap_offset() == (6, 0) diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index 9d943fa9df13..bdf7ce72a2df 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -113,6 +113,18 @@ def find_matplotlib_font(**kw): ax.set_yticks([]) +@image_comparison(['complex.png']) +def test_complex_shaping(): + # Raqm is Arabic for writing; note that because Arabic is RTL, the characters here + # may seem to be in a different order than expected, but libraqm will order them + # correctly for us. + text = ( + 'Arabic: \N{Arabic Letter REH}\N{Arabic FATHA}\N{Arabic Letter QAF}' + '\N{Arabic SUKUN}\N{Arabic Letter MEEM}') + fig = plt.figure(figsize=(3, 1)) + fig.text(0.5, 0.5, text, size=32, ha='center', va='center') + + @image_comparison(['multiline']) def test_multiline(): plt.figure() @@ -826,18 +838,6 @@ def test_pdf_kerning(): plt.figtext(0.1, 0.5, "ATATATATATATATATATA", size=30) -def test_unsupported_script(recwarn): - fig = plt.figure() - t = fig.text(.5, .5, "\N{BENGALI DIGIT ZERO}") - fig.canvas.draw() - assert all(isinstance(warn.message, UserWarning) for warn in recwarn) - assert ( - [warn.message.args for warn in recwarn] == - [(r"Glyph 2534 (\N{BENGALI DIGIT ZERO}) missing from font(s) " - + f"{t.get_fontname()}.",), - (r"Matplotlib currently does not support Bengali natively.",)]) - - # See gh-26152 for more information on this xfail @pytest.mark.xfail(pyparsing_version.release == (3, 1, 0), reason="Error messages are incorrect with pyparsing 3.1.0") diff --git a/src/ft2font.cpp b/src/ft2font.cpp index ebb7d5204d80..22199a0fd19b 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -227,6 +227,11 @@ void FT2Font::open(FT_Open_Args &open_args) if (open_args.stream != nullptr) { face->face_flags |= FT_FACE_FLAG_EXTERNAL_STREAM; } + + // This allows us to get back to our data if we need it, though it makes a pointer + // loop, so don't set a free-function for it. + face->generic.data = this; + face->generic.finalizer = nullptr; } void FT2Font::close() @@ -333,48 +338,69 @@ void FT2Font::set_text( bbox.xMin = bbox.yMin = 32000; bbox.xMax = bbox.yMax = -32000; - FT_UInt previous = 0; - FT2Font *previous_ft_object = nullptr; + auto rq = raqm_create(); + if (!rq) { + throw std::runtime_error("failed to compute text layout"); + } + [[maybe_unused]] auto const& rq_cleanup = + std::unique_ptr, decltype(&raqm_destroy)>( + rq, raqm_destroy); + + if (!raqm_set_text(rq, reinterpret_cast(text.data()), + text.size())) + { + throw std::runtime_error("failed to set text for layout"); + } + if (!raqm_set_freetype_face(rq, face)) { + throw std::runtime_error("failed to set text face for layout"); + } + if (!raqm_set_freetype_load_flags(rq, flags)) { + throw std::runtime_error("failed to set text flags for layout"); + } + if (!raqm_layout(rq)) { + throw std::runtime_error("failed to layout text"); + } - for (auto codepoint : text) { - FT_UInt glyph_index = 0; - FT_BBox glyph_bbox; - FT_Pos last_advance; + std::set glyph_seen_fonts; + glyph_seen_fonts.insert(face->family_name); - FT_Error charcode_error, glyph_error; - std::set glyph_seen_fonts; - FT2Font *ft_object_with_glyph = this; - bool was_found = load_char_with_fallback(ft_object_with_glyph, glyph_index, glyphs, - char_to_font, codepoint, flags, - charcode_error, glyph_error, glyph_seen_fonts, false); - if (!was_found) { - ft_glyph_warn((FT_ULong)codepoint, glyph_seen_fonts); - // render missing glyph tofu - // come back to top-most font - ft_object_with_glyph = this; - char_to_font[codepoint] = ft_object_with_glyph; - ft_object_with_glyph->load_glyph(glyph_index, flags); - } else if (ft_object_with_glyph->warn_if_used) { - ft_glyph_warn((FT_ULong)codepoint, glyph_seen_fonts); - } + size_t num_glyphs = 0; + auto const& rq_glyphs = raqm_get_glyphs(rq, &num_glyphs); - // retrieve kerning distance and move pen position - if ((ft_object_with_glyph == previous_ft_object) && // if both fonts are the same - ft_object_with_glyph->has_kerning() && // if the font knows how to kern - previous && glyph_index // and we really have 2 glyphs - ) { - pen.x += ft_object_with_glyph->get_kerning(previous, glyph_index, FT_KERNING_DEFAULT); + for (size_t i = 0; i < num_glyphs; i++) { + auto const& rglyph = rq_glyphs[i]; + + // Warn for missing glyphs. + if (rglyph.index == 0) { + ft_glyph_warn(text[rglyph.cluster], glyph_seen_fonts); + continue; + } + FT2Font *wrapped_font = static_cast(rglyph.ftface->generic.data); + if (wrapped_font->warn_if_used) { + ft_glyph_warn(text[rglyph.cluster], glyph_seen_fonts); } // extract glyph image and store it in our table - FT_Glyph &thisGlyph = glyphs[glyphs.size() - 1]; + FT_Error error; + error = FT_Load_Glyph(rglyph.ftface, rglyph.index, flags); + if (error) { + throw std::runtime_error("failed to load glyph"); + } + FT_Glyph thisGlyph; + error = FT_Get_Glyph(rglyph.ftface->glyph, &thisGlyph); + if (error) { + throw std::runtime_error("failed to get glyph"); + } + + pen.x += rglyph.x_offset; + pen.y += rglyph.y_offset; - last_advance = ft_object_with_glyph->get_face()->glyph->advance.x; FT_Glyph_Transform(thisGlyph, nullptr, &pen); FT_Glyph_Transform(thisGlyph, &matrix, nullptr); xys.push_back(pen.x); xys.push_back(pen.y); + FT_BBox glyph_bbox; FT_Glyph_Get_CBox(thisGlyph, FT_GLYPH_BBOX_SUBPIXELS, &glyph_bbox); bbox.xMin = std::min(bbox.xMin, glyph_bbox.xMin); @@ -382,11 +408,14 @@ void FT2Font::set_text( bbox.yMin = std::min(bbox.yMin, glyph_bbox.yMin); bbox.yMax = std::max(bbox.yMax, glyph_bbox.yMax); - pen.x += last_advance; - - previous = glyph_index; - previous_ft_object = ft_object_with_glyph; + if ((flags & FT_LOAD_NO_HINTING) != 0) { + pen.x += rglyph.x_advance - rglyph.x_offset; + } else { + pen.x += hinting_factor * rglyph.x_advance - rglyph.x_offset; + } + pen.y += rglyph.y_advance - rglyph.y_offset; + glyphs.push_back(thisGlyph); } FT_Vector_Transform(&pen, &matrix); From 98135232085af3c5585e4d7a077318f3d511306d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 3 Apr 2025 04:09:06 -0400 Subject: [PATCH 4/6] Implement font fallback for libraqm --- lib/matplotlib/tests/test_text.py | 12 +++++- src/ft2font.cpp | 63 +++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index bdf7ce72a2df..8dba63eeef32 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -121,8 +121,16 @@ def test_complex_shaping(): text = ( 'Arabic: \N{Arabic Letter REH}\N{Arabic FATHA}\N{Arabic Letter QAF}' '\N{Arabic SUKUN}\N{Arabic Letter MEEM}') - fig = plt.figure(figsize=(3, 1)) - fig.text(0.5, 0.5, text, size=32, ha='center', va='center') + math_signs = '\N{N-ary Product}\N{N-ary Coproduct}\N{N-ary summation}\N{Integral}' + text = math_signs + text + math_signs + fig = plt.figure(figsize=(6, 2)) + fig.text(0.5, 0.75, text, size=32, ha='center', va='center') + # Also check fallback behaviour: + # - English should use cmr10 + # - Math signs should use DejaVu Sans Display (and thus be larger than the rest) + # - Arabic should use DejaVu Sans + fig.text(0.5, 0.25, text, size=32, ha='center', va='center', + family=['cmr10', 'DejaVu Sans Display', 'DejaVu Sans']) @image_comparison(['multiline']) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 22199a0fd19b..890fc61974b0 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -361,9 +362,71 @@ void FT2Font::set_text( throw std::runtime_error("failed to layout text"); } + std::vector> face_substitutions; std::set glyph_seen_fonts; glyph_seen_fonts.insert(face->family_name); + // Attempt to use fallback fonts if necessary. + for (auto const& fallback : fallbacks) { + size_t num_glyphs = 0; + auto const& rq_glyphs = raqm_get_glyphs(rq, &num_glyphs); + bool new_fallback_used = false; + + // Sort clusters (n.b. std::map is ordered), as RTL text will be returned in + // display, not source, order. + std::map cluster_missing; + for (size_t i = 0; i < num_glyphs; i++) { + auto const& rglyph = rq_glyphs[i]; + + // Sometimes multiple glyphs are necessary for a single cluster; if any are + // not found, we want to "poison" the whole set and keep them missing. + cluster_missing[rglyph.cluster] |= (rglyph.index == 0); + } + + for (auto it = cluster_missing.cbegin(); it != cluster_missing.cend(); ) { + auto [cluster, missing] = *it; + ++it; // Early change so we can access the next cluster below. + if (missing) { + auto next = (it != cluster_missing.cend()) ? it->first : text.size(); + for (auto i = cluster; i < next; i++) { + face_substitutions.emplace_back(i, fallback->face); + } + new_fallback_used = true; + } + } + + if (!new_fallback_used) { + // If we never used a fallback, then we're good to go with the existing + // layout we have already made. + break; + } + + // If a fallback was used, then re-attempt the layout with the new fonts. + if (!fallback->warn_if_used) { + glyph_seen_fonts.insert(fallback->face->family_name); + } + + raqm_clear_contents(rq); + if (!raqm_set_text(rq, + reinterpret_cast(text.data()), + text.size())) + { + throw std::runtime_error("failed to set text for layout"); + } + if (!raqm_set_freetype_face(rq, face)) { + throw std::runtime_error("failed to set text face for layout"); + } + for (auto [cluster, fallback] : face_substitutions) { + raqm_set_freetype_face_range(rq, fallback, cluster, 1); + } + if (!raqm_set_freetype_load_flags(rq, flags)) { + throw std::runtime_error("failed to set text flags for layout"); + } + if (!raqm_layout(rq)) { + throw std::runtime_error("failed to layout text"); + } + } + size_t num_glyphs = 0; auto const& rq_glyphs = raqm_get_glyphs(rq, &num_glyphs); From b36b97f3da663f65584203c8c1051e653e882338 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 8 Aug 2025 03:05:02 -0400 Subject: [PATCH 5/6] DOC: Add What's New entry for complex text layout --- doc/release/next_whats_new/libraqm.rst | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 doc/release/next_whats_new/libraqm.rst diff --git a/doc/release/next_whats_new/libraqm.rst b/doc/release/next_whats_new/libraqm.rst new file mode 100644 index 000000000000..8312f2f9432c --- /dev/null +++ b/doc/release/next_whats_new/libraqm.rst @@ -0,0 +1,42 @@ +Complex text layout with libraqm +-------------------------------- + +Text support has been extended to include complex text layout. This support includes: + +1. Languages that require advanced layout, such as Arabic or Hebrew. +2. Text that mixes left-to-right and right-to-left languages. + + .. plot:: + :show-source-link: False + + text = 'Here is some رَقْم in اَلْعَرَبِيَّةُ' + fig = plt.figure(figsize=(6, 1)) + fig.text(0.5, 0.5, text, size=32, ha='center', va='center') + +3. Ligatures that combine several adjacent characters for improved legibility. + + .. plot:: + :show-source-link: False + + text = 'f\N{Hair Space}f\N{Hair Space}i \N{Rightwards Arrow} ffi' + fig = plt.figure(figsize=(3, 1)) + fig.text(0.5, 0.5, text, size=32, ha='center', va='center') + +4. Combining multiple or double-width diacritics. + + .. plot:: + :show-source-link: False + + text = ( + 'a\N{Combining Circumflex Accent}\N{Combining Double Tilde}' + 'c\N{Combining Diaeresis}') + text = ' + '.join( + c if c in 'ac' else f'\N{Dotted Circle}{c}' + for c in text) + f' \N{Rightwards Arrow} {text}' + fig = plt.figure(figsize=(6, 1)) + fig.text(0.5, 0.5, text, size=32, ha='center', va='center', + # Builtin DejaVu Sans doesn't support multiple diacritics. + family=['Noto Sans', 'DejaVu Sans']) + +Note, all advanced features require corresponding font support, and may require +additional fonts over the builtin DejaVu Sans. From 04c8eefb7e42029e0194d84f3cb30fc5e31df061 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 26 Aug 2025 00:10:47 -0400 Subject: [PATCH 6/6] ci: Ignore coverage data from subprojects and generated files We only care about our own source files, so anything in `subprojects` or the `build` directory can be ignored, thus fixing the bugginess with Harfbuzz headers. --- .github/workflows/tests.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 343d497a4696..b2ba198589ca 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -397,13 +397,14 @@ jobs: if [[ "${{ runner.os }}" != 'macOS' ]]; then LCOV_IGNORE_ERRORS=',' # do not ignore any lcov errors by default if [[ "${{ matrix.os }}" = ubuntu-24.04 ]]; then - # filter mismatch and unused-entity errors detected by lcov 2.x - LCOV_IGNORE_ERRORS='mismatch,unused' + # filter mismatch errors detected by lcov 2.x + LCOV_IGNORE_ERRORS='mismatch' fi lcov --rc lcov_branch_coverage=1 --ignore-errors $LCOV_IGNORE_ERRORS \ - --capture --directory . --output-file coverage.info + --capture --directory . --exclude $PWD/subprojects --exclude $PWD/build \ + --output-file coverage.info lcov --rc lcov_branch_coverage=1 --ignore-errors $LCOV_IGNORE_ERRORS \ - --output-file coverage.info --extract coverage.info $PWD/src/'*' $PWD/lib/'*' + --output-file coverage.info --extract coverage.info $PWD/src/'*' lcov --rc lcov_branch_coverage=1 --ignore-errors $LCOV_IGNORE_ERRORS \ --list coverage.info find . -name '*.gc*' -delete