From 4c379322ceec52fbc035d96d8dc9ba8eb4cb96a2 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Sun, 5 Apr 2026 17:37:04 -0700 Subject: [PATCH 01/19] chore: create readme, roadmap, and license files --- LICENSE.md | 7 +++++++ README.md | 30 ++++++++++++++++++++++++++++++ ROADMAP.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 ROADMAP.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..9befde1 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2026 Hasan Zakeri + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b21e1e3 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# harfrust-python + +Python bindings for [HarfRust](https://github.com/nickkuk/harfrust), a pure-Rust port of the [HarfBuzz](https://harfbuzz.github.io/) text shaping engine. + +## What is text shaping? + +Text shaping is the process of converting a sequence of Unicode codepoints into positioned glyphs — selecting the right glyph forms, applying ligatures, kerning, and reordering as required by the script. It is a critical step in any text rendering pipeline, especially for complex scripts like Arabic, Devanagari, and Thai. + +HarfBuzz is the industry-standard text shaping engine used by Firefox, Chrome, Android, and many other platforms. HarfRust is a faithful pure-Rust port of HarfBuzz, and this project aims to make that engine accessible from Python. + +## Goals + +- **Standalone Python package** — a proper, independently usable Python library for text shaping, not just a test utility. +- **Two-tier API** — a high-level `shape()` function for quick one-shot shaping, and a lower-level object API (`Font`, `Buffer`, `GlyphBuffer`) for full control over the shaping pipeline. +- **Pythonic interface** — string-based construction for configuration types (`Direction("rtl")`, `Feature("+kern")`, `Variation("wght=700")`), iteration over glyph results, and clear error messages. +- **HarfBuzz test compatibility** — ability to run against HarfBuzz's `.tests` regression format, enabling direct comparison between the two engines. + +## Technical Approach + +- **PyO3 + maturin** — the standard modern toolchain for building Rust extensions for Python. +- **Owned-container pattern** — the Python `Font` object owns all its backing data, with transient Rust borrows scoped to individual method calls. This cleanly bridges Rust's lifetime system and Python's garbage-collected memory model. +- **Standalone project** — not a member of the harfrust Cargo workspace, allowing an independent release cadence and CI configuration. + +## Status + +This project is in early development. See the [development plan](https://github.com/nickkuk/harfrust-python/blob/main/PLAN.md) for the phased roadmap. + +## License + +MIT diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..67546f6 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,43 @@ +# Development Plan + +## Phase 1 — Project Skeleton + +Buildable Python package that imports successfully. Set up `Cargo.toml` (PyO3 cdylib), `pyproject.toml` (maturin backend), a minimal `src/lib.rs` with a version function, and `python/harfrust/__init__.py` that re-exports it. Include an MIT `LICENSE` file and a GitHub Actions CI workflow (`maturin develop` + `pytest`). + +**Done when:** `maturin develop && pytest` passes with a single `test_import` test, LICENSE exists, and CI is green. + +## Phase 2 — Value Types + +Expose `Direction`, `Script`, `Language`, `Feature`, and `Variation` as Python classes. Each wraps the corresponding harfrust type and uses its `FromStr` impl for string-based construction (e.g. `Direction("rtl")`, `Feature("+kern")`, `Variation("wght=700")`). + +**Done when:** types can be constructed, compared, stringified, and their properties accessed from Python. + +## Phase 3 — High-Level `shape()` Function + +Wrap `hr_shape::shape()` and `hr_shape::run_from_args()` as top-level Python functions. Set up a `.tests` regression suite with bundled test fonts and data, plus optional discovery of an external harfrust checkout via `HARFRUST_SOURCE`. + +**Done when:** `harfrust.shape(font_path, text, options)` returns a serialized glyph string and bundled `.tests` cases pass. + +## Phase 4 — Buffer Class + +Expose `UnicodeBuffer` as a Python `Buffer` class with `add_str()`, direction/script/language properties, `guess_segment_properties()`, and `len()`. Uses `Option` internally to track consumption by `shape()`. + +**Done when:** buffers can be created, populated, configured, and passed to shaping. + +## Phase 5 — Font Class and Object-Level Shaping + +Full object API: `Font` (owned-container pattern), `GlyphBuffer`, `GlyphInfo`, `GlyphPosition`. Supports loading from file or bytes, setting variations and point size, shaping a buffer, iterating results, serializing output, and recycling buffers via `GlyphBuffer.clear()`. + +**Done when:** the object API produces output identical to the `shape()` string function, and buffer consumption/recycling works correctly. + +## Phase 6 — Type Stubs, Polish, and Packaging + +Add PEP 561 type stubs (`__init__.pyi`, `py.typed`) and expand the README with usage examples and installation instructions. + +**Done when:** the package is publishable with full IDE autocomplete support. + +## Phase 7 — shapecmp Test Harness + +Separate pure-Python project (`shapecmp-python/`) that compares harfrust vs harfbuzz shaping output across font/text corpora. Includes a CLI for single comparisons, batch `.tests` runs, corpus sweeps, failure minimization, and `.tests` file emission. + +**Done when:** `shapecmp run-tests` can execute HarfBuzz `.tests` files against both engines and report differences. From a868ebfe7046fae6bfd68133fc240628ab4528e9 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Sun, 5 Apr 2026 22:26:33 -0700 Subject: [PATCH 02/19] chore: define project structure --- .gitignore | 1 + Cargo.toml | 14 ++++++++++++++ pyproject.toml | 22 ++++++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a9bffb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.cargo/config.toml \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..140307f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "harfrust-python" +version = "0.1.0" +edition = "2021" +rust-version = "1.85" +license = "MIT" + +[lib] +name = "_harfrust" +crate-type = ["cdylib"] + +[dependencies] +harfrust = { version = "0.5.2", features = ["std"] } +pyo3 = { version = "0.28.3", features = ["extension-module"] } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1b50d5d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["maturin>=1.8"] +build-backend = "maturin" + +[project] +name = "pyharfrust" +version = "0.1.0" +description = "Python bindings for the HarfRust text shaping engine" +requires-python = ">=3.9" +license = "MIT" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", +] + +[tool.maturin] +features = ["pyo3/extension-module"] +python-source = "python" +module-name = "harfrust._harfrust" + +[tool.pytest.ini_options] +testpaths = ["tests"] \ No newline at end of file From 8849a9098b423b4cbec2ee0504ea123b948cd958 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Mon, 6 Apr 2026 00:10:31 -0700 Subject: [PATCH 03/19] feat: implement Phase 1 project skeleton Minimal buildable PyO3 extension that imports successfully: - src/lib.rs: PyO3 module exposing __version__ from Cargo.toml - python/pyharfrust/__init__.py: re-exports __version__ - tests/test_import.py: verifies import and version attribute - pyproject.toml: maturin backend, dev deps in dependency-groups, pyright config for native module resolution - Cargo.toml: renamed to pyharfrust/_pyharfrust, harfrust 0.5.2 + pyo3 0.28.3 - .pre-commit-config.yaml: formatting on commit, clippy/tests on push - .gitignore: trimmed to project-relevant patterns - uv.lock and Cargo.lock committed for reproducible builds - github workflow checks for rust and python --- .github/workflows/ci.yml | 56 ++++++ .gitignore | 22 ++- .pre-commit-config.yaml | 65 +++++++ Cargo.lock | 214 +++++++++++++++++++++++ Cargo.toml | 6 +- LICENSE.md | 2 +- pyproject.toml | 21 ++- python/pyharfrust/__init__.py | 3 + src/lib.rs | 7 + tests/test_import.py | 4 + uv.lock | 310 ++++++++++++++++++++++++++++++++++ 11 files changed, 702 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .pre-commit-config.yaml create mode 100644 Cargo.lock create mode 100644 python/pyharfrust/__init__.py create mode 100644 src/lib.rs create mode 100644 tests/test_import.py create mode 100644 uv.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fb7f113 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,56 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + ci: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - uses: Swatinem/rust-cache@v2 + + - uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + cache-dependency-glob: uv.lock + + - name: Rust formatting + run: cargo fmt --all --check + + - name: Clippy + run: cargo clippy --all -- -D warnings + + - name: Rust tests + run: cargo test --all + + - name: Install Python deps + run: uv sync --locked + + - name: Build extension + run: uv run maturin develop + + - name: Ruff check + run: uv run ruff check + + - name: Ruff format + run: uv run ruff format --check + + - name: Python tests + run: uv run pytest diff --git a/.gitignore b/.gitignore index 2a9bffb..4332ceb 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,21 @@ -.cargo/config.toml \ No newline at end of file +# Local Cargo config (dev overrides for sibling checkouts) +.cargo/config.toml + +# Rust +target/ +**/*.rs.bk + +# Python +__pycache__/ +*.py[cod] +*.so +*.pyd +*.egg-info/ + +# Environments +.venv/ + +# Testing +.pytest_cache/ + +**/*.dSYM diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..701441f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,65 @@ +default_install_hook_types: [pre-commit, pre-push] +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: check-merge-conflict + - id: no-commit-to-branch + args: [--branch, main] + +- repo: https://github.com/astral-sh/uv-pre-commit + rev: 0.11.3 + hooks: + - id: uv-lock + args: [--locked] + +# pre-commit: fast formatting and lint checks only +- repo: local + hooks: + - id: cargo-fmt + name: cargo fmt + entry: cargo fmt --all --check + language: system + types: [rust] + pass_filenames: false + + - id: ruff-check + name: ruff check + entry: uv run ruff check --fix + language: system + types: [python] + + - id: ruff-format + name: ruff format + entry: uv run ruff format --force-exclude + language: system + types: [python] + +# pre-push: compilation, linting, and tests + - id: cargo-clippy + name: cargo clippy + entry: cargo clippy --all -- -D warnings + language: system + types: [rust] + pass_filenames: false + stages: [pre-push] + + - id: cargo-test + name: cargo test + entry: cargo test --all + language: system + types: [rust] + pass_filenames: false + stages: [pre-push] + + - id: pytest + name: pytest + entry: uv run pytest + language: system + types_or: [python, rust] + pass_filenames: false + stages: [pre-push] diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..05decba --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,214 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "font-types" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d9237c6d82152100c691fb77ea18037b402bcc7257d2c876a4ffac81bc22a1c" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "harfrust" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da2e5ae821f6e96664977bf974d6d6a2d6682f9ccee23e62ec1d134246845f9" +dependencies = [ + "bitflags", + "bytemuck", + "core_maths", + "read-fonts", + "smallvec", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyharfrust" +version = "0.1.0" +dependencies = [ + "harfrust", + "pyo3", +] + +[[package]] +name = "pyo3" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" +dependencies = [ + "libc", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", +] + +[[package]] +name = "pyo3-build-config" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" +dependencies = [ + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "read-fonts" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" +dependencies = [ + "bytemuck", + "core_maths", + "font-types", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/Cargo.toml b/Cargo.toml index 140307f..dfbe8c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "harfrust-python" +name = "pyharfrust" version = "0.1.0" edition = "2021" rust-version = "1.85" license = "MIT" [lib] -name = "_harfrust" +name = "_pyharfrust" crate-type = ["cdylib"] [dependencies] harfrust = { version = "0.5.2", features = ["std"] } -pyo3 = { version = "0.28.3", features = ["extension-module"] } \ No newline at end of file +pyo3 = { version = "0.28.3", features = ["extension-module"] } diff --git a/LICENSE.md b/LICENSE.md index 9befde1..7dbb3f1 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -4,4 +4,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +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/pyproject.toml b/pyproject.toml index 1b50d5d..e016d14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,17 +6,32 @@ build-backend = "maturin" name = "pyharfrust" version = "0.1.0" description = "Python bindings for the HarfRust text shaping engine" -requires-python = ">=3.9" +requires-python = ">=3.11" license = "MIT" classifiers = [ "Programming Language :: Rust", "Programming Language :: Python :: Implementation :: CPython", ] +dependencies = [] + +[dependency-groups] +dev = [ + "maturin>=1.12.6", + "pre-commit>=4.3.0", + "pyright>=1.1.408", + "pytest>=9.0.2", + "ruff>=0.15.9", +] [tool.maturin] features = ["pyo3/extension-module"] python-source = "python" -module-name = "harfrust._harfrust" +module-name = "pyharfrust._pyharfrust" + +[tool.pyright] +pythonPlatform = "All" +typeCheckingMode = "basic" +reportMissingModuleSource = false [tool.pytest.ini_options] -testpaths = ["tests"] \ No newline at end of file +testpaths = ["tests"] diff --git a/python/pyharfrust/__init__.py b/python/pyharfrust/__init__.py new file mode 100644 index 0000000..3f2f340 --- /dev/null +++ b/python/pyharfrust/__init__.py @@ -0,0 +1,3 @@ +from pyharfrust._pyharfrust import __version__ + +__all__ = ["__version__"] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8fe709b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,7 @@ +use pyo3::prelude::*; + +#[pymodule] +fn _pyharfrust(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add("__version__", env!("CARGO_PKG_VERSION"))?; + Ok(()) +} diff --git a/tests/test_import.py b/tests/test_import.py new file mode 100644 index 0000000..26936d1 --- /dev/null +++ b/tests/test_import.py @@ -0,0 +1,4 @@ +def test_import(): + import pyharfrust + + assert hasattr(pyharfrust, "__version__") diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..7a6a955 --- /dev/null +++ b/uv.lock @@ -0,0 +1,310 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "filelock" +version = "3.25.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, +] + +[[package]] +name = "identify" +version = "2.6.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "maturin" +version = "1.12.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/18/8b2eebd3ea086a5ec73d7081f95ec64918ceda1900075902fc296ea3ad55/maturin-1.12.6.tar.gz", hash = "sha256:d37be3a811a7f2ee28a0fa0964187efa50e90f21da0c6135c27787fa0b6a89db", size = 269165, upload-time = "2026-03-01T14:54:04.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/8b/9ddfde8a485489e3ebdc50ee3042ef1c854f00dfea776b951068f6ffe451/maturin-1.12.6-py3-none-linux_armv6l.whl", hash = "sha256:6892b4176992fcc143f9d1c1c874a816e9a041248eef46433db87b0f0aff4278", size = 9789847, upload-time = "2026-03-01T14:54:09.172Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e8/5f7fd3763f214a77ac0388dbcc71cc30aec5490016bd0c8e6bd729fc7b0a/maturin-1.12.6-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c0c742beeeef7fb93b6a81bd53e75507887e396fd1003c45117658d063812dad", size = 19023833, upload-time = "2026-03-01T14:53:46.743Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7f/706ff3839c8b2046436d4c2bc97596c558728264d18abc298a1ad862a4be/maturin-1.12.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2cb41139295eed6411d3cdafc7430738094c2721f34b7eeb44f33cac516115dc", size = 9821620, upload-time = "2026-03-01T14:54:12.04Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9c/70917fb123c8dd6b595e913616c9c72d730cbf4a2b6cac8077dc02a12586/maturin-1.12.6-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:351f3af1488a7cbdcff3b6d8482c17164273ac981378a13a4a9937a49aec7d71", size = 9849107, upload-time = "2026-03-01T14:53:48.971Z" }, + { url = "https://files.pythonhosted.org/packages/59/ea/f1d6ad95c0a12fbe761a7c28a57540341f188564dbe8ad730a4d1788cd32/maturin-1.12.6-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:6dbddfe4dc7ddee60bbac854870bd7cfec660acb54d015d24597d59a1c828f61", size = 10242855, upload-time = "2026-03-01T14:53:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/93/1b/2419843a4f1d2fb4747f3dc3d9c4a2881cd97a3274dd94738fcdf0835e79/maturin-1.12.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:8fdb0f63e77ee3df0f027a120e9af78dbc31edf0eb0f263d55783c250c33b728", size = 9674972, upload-time = "2026-03-01T14:53:52.763Z" }, + { url = "https://files.pythonhosted.org/packages/71/46/b60ab2fc996d904b40e55bd475599dcdccd8f7ad3e649bf95e87970df466/maturin-1.12.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:fa84b7493a2e80759cacc2e668fa5b444d55b9994e90707c42904f55d6322c1e", size = 9645755, upload-time = "2026-03-01T14:53:58.497Z" }, + { url = "https://files.pythonhosted.org/packages/a4/96/03f2b55a8c226805115232fc23c4a4f33f0c9d39e11efab8166dc440f80d/maturin-1.12.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:e90dc12bc6a38e9495692a36c9e231c4d7e0c9bfde60719468ab7d8673db3c45", size = 12737612, upload-time = "2026-03-01T14:54:05.393Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c2/648667022c5b53cdccefa67c245e8a984970f3045820f00c2e23bdb2aff4/maturin-1.12.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:06fc8d089f98623ce924c669b70911dfed30f9a29956c362945f727f9abc546b", size = 10455028, upload-time = "2026-03-01T14:54:07.349Z" }, + { url = "https://files.pythonhosted.org/packages/63/d6/5b5efe3ca0c043357ed3f8d2b2d556169fdbf1ff75e50e8e597708a359d2/maturin-1.12.6-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:75133e56274d43b9227fd49dca9a86e32f1fd56a7b55544910c4ce978c2bb5aa", size = 10014531, upload-time = "2026-03-01T14:53:54.548Z" }, + { url = "https://files.pythonhosted.org/packages/68/d5/39c594c27b1a8b32a0cb95fff9ad60b888c4352d1d1c389ac1bd20dc1e16/maturin-1.12.6-py3-none-win32.whl", hash = "sha256:3f32e0a3720b81423c9d35c14e728cb1f954678124749776dc72d533ea1115e8", size = 8553012, upload-time = "2026-03-01T14:53:50.706Z" }, + { url = "https://files.pythonhosted.org/packages/94/66/b262832a91747e04051e21f986bd01a8af81fbffafacc7d66a11e79aab5f/maturin-1.12.6-py3-none-win_amd64.whl", hash = "sha256:977290159d252db946054a0555263c59b3d0c7957135c69e690f4b1558ee9983", size = 9890470, upload-time = "2026-03-01T14:53:56.659Z" }, + { url = "https://files.pythonhosted.org/packages/e3/47/76b8ca470ddc8d7d36aa8c15f5a6aed1841806bb93a0f4ead8ee61e9a088/maturin-1.12.6-py3-none-win_arm64.whl", hash = "sha256:bae91976cdc8148038e13c881e1e844e5c63e58e026e8b9945aa2d19b3b4ae89", size = 8606158, upload-time = "2026-03-01T14:54:02.423Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyharfrust" +version = "0.1.0" +source = { editable = "." } + +[package.dev-dependencies] +dev = [ + { name = "maturin" }, + { name = "pre-commit" }, + { name = "pyright" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "maturin", specifier = ">=1.12.6" }, + { name = "pre-commit", specifier = ">=4.3.0" }, + { name = "pyright", specifier = ">=1.1.408" }, + { name = "pytest", specifier = ">=9.0.2" }, + { name = "ruff", specifier = ">=0.15.9" }, +] + +[[package]] +name = "pyright" +version = "1.1.408" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/b2/5db700e52554b8f025faa9c3c624c59f1f6c8841ba81ab97641b54322f16/pyright-1.1.408.tar.gz", hash = "sha256:f28f2321f96852fa50b5829ea492f6adb0e6954568d1caa3f3af3a5f555eb684", size = 4400578, upload-time = "2026-01-08T08:07:38.795Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl", hash = "sha256:090b32865f4fdb1e0e6cd82bf5618480d48eecd2eb2e70f960982a3d9a4c17c1", size = 6399144, upload-time = "2026-01-08T08:07:37.082Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/88/815e53084c5079a59df912825a279f41dd2e0df82281770eadc732f5352c/python_discovery-1.2.1.tar.gz", hash = "sha256:180c4d114bff1c32462537eac5d6a332b768242b76b69c0259c7d14b1b680c9e", size = 58457, upload-time = "2026-03-26T22:30:44.496Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl", hash = "sha256:b6a957b24c1cd79252484d3566d1b49527581d46e789aaf43181005e56201502", size = 31674, upload-time = "2026-03-26T22:30:43.396Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/97/e9f1ca355108ef7194e38c812ef40ba98c7208f47b13ad78d023caa583da/ruff-0.15.9.tar.gz", hash = "sha256:29cbb1255a9797903f6dde5ba0188c707907ff44a9006eb273b5a17bfa0739a2", size = 4617361, upload-time = "2026-04-02T18:17:20.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/1f/9cdfd0ac4b9d1e5a6cf09bedabdf0b56306ab5e333c85c87281273e7b041/ruff-0.15.9-py3-none-linux_armv6l.whl", hash = "sha256:6efbe303983441c51975c243e26dff328aca11f94b70992f35b093c2e71801e1", size = 10511206, upload-time = "2026-04-02T18:16:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f6/32bfe3e9c136b35f02e489778d94384118bb80fd92c6d92e7ccd97db12ce/ruff-0.15.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4965bac6ac9ea86772f4e23587746f0b7a395eccabb823eb8bfacc3fa06069f7", size = 10923307, upload-time = "2026-04-02T18:17:08.645Z" }, + { url = "https://files.pythonhosted.org/packages/ca/25/de55f52ab5535d12e7aaba1de37a84be6179fb20bddcbe71ec091b4a3243/ruff-0.15.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf05aad70ca5b5a0a4b0e080df3a6b699803916d88f006efd1f5b46302daab8", size = 10316722, upload-time = "2026-04-02T18:16:44.206Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/690d75f3fd6278fe55fff7c9eb429c92d207e14b25d1cae4064a32677029/ruff-0.15.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9439a342adb8725f32f92732e2bafb6d5246bd7a5021101166b223d312e8fc59", size = 10623674, upload-time = "2026-04-02T18:16:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ec/176f6987be248fc5404199255522f57af1b4a5a1b57727e942479fec98ad/ruff-0.15.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5e6faf9d97c8edc43877c3f406f47446fc48c40e1442d58cfcdaba2acea745", size = 10351516, upload-time = "2026-04-02T18:16:57.206Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fc/51cffbd2b3f240accc380171d51446a32aa2ea43a40d4a45ada67368fbd2/ruff-0.15.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b34a9766aeec27a222373d0b055722900fbc0582b24f39661aa96f3fe6ad901", size = 11150202, upload-time = "2026-04-02T18:17:06.452Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d4/25292a6dfc125f6b6528fe6af31f5e996e19bf73ca8e3ce6eb7fa5b95885/ruff-0.15.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89dd695bc72ae76ff484ae54b7e8b0f6b50f49046e198355e44ea656e521fef9", size = 11988891, upload-time = "2026-04-02T18:17:18.575Z" }, + { url = "https://files.pythonhosted.org/packages/13/e1/1eebcb885c10e19f969dcb93d8413dfee8172578709d7ee933640f5e7147/ruff-0.15.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce187224ef1de1bd225bc9a152ac7102a6171107f026e81f317e4257052916d5", size = 11480576, upload-time = "2026-04-02T18:16:52.986Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/a1548ac378a78332a4c3dcf4a134c2475a36d2a22ddfa272acd574140b50/ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0c7c341f68adb01c488c3b7d4b49aa8ea97409eae6462d860a79cf55f431b6", size = 11254525, upload-time = "2026-04-02T18:17:02.041Z" }, + { url = "https://files.pythonhosted.org/packages/42/aa/4bb3af8e61acd9b1281db2ab77e8b2c3c5e5599bf2a29d4a942f1c62b8d6/ruff-0.15.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:55cc15eee27dc0eebdfcb0d185a6153420efbedc15eb1d38fe5e685657b0f840", size = 11204072, upload-time = "2026-04-02T18:17:13.581Z" }, + { url = "https://files.pythonhosted.org/packages/69/48/d550dc2aa6e423ea0bcc1d0ff0699325ffe8a811e2dba156bd80750b86dc/ruff-0.15.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6537f6eed5cda688c81073d46ffdfb962a5f29ecb6f7e770b2dc920598997ed", size = 10594998, upload-time = "2026-04-02T18:16:46.369Z" }, + { url = "https://files.pythonhosted.org/packages/63/47/321167e17f5344ed5ec6b0aa2cff64efef5f9e985af8f5622cfa6536043f/ruff-0.15.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6d3fcbca7388b066139c523bda744c822258ebdcfbba7d24410c3f454cc9af71", size = 10359769, upload-time = "2026-04-02T18:17:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/67/5e/074f00b9785d1d2c6f8c22a21e023d0c2c1817838cfca4c8243200a1fa87/ruff-0.15.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:058d8e99e1bfe79d8a0def0b481c56059ee6716214f7e425d8e737e412d69677", size = 10850236, upload-time = "2026-04-02T18:16:48.749Z" }, + { url = "https://files.pythonhosted.org/packages/76/37/804c4135a2a2caf042925d30d5f68181bdbd4461fd0d7739da28305df593/ruff-0.15.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8e1ddb11dbd61d5983fa2d7d6370ef3eb210951e443cace19594c01c72abab4c", size = 11358343, upload-time = "2026-04-02T18:16:55.068Z" }, + { url = "https://files.pythonhosted.org/packages/88/3d/1364fcde8656962782aa9ea93c92d98682b1ecec2f184e625a965ad3b4a6/ruff-0.15.9-py3-none-win32.whl", hash = "sha256:bde6ff36eaf72b700f32b7196088970bf8fdb2b917b7accd8c371bfc0fd573ec", size = 10583382, upload-time = "2026-04-02T18:17:04.261Z" }, + { url = "https://files.pythonhosted.org/packages/4c/56/5c7084299bd2cacaa07ae63a91c6f4ba66edc08bf28f356b24f6b717c799/ruff-0.15.9-py3-none-win_amd64.whl", hash = "sha256:45a70921b80e1c10cf0b734ef09421f71b5aa11d27404edc89d7e8a69505e43d", size = 11744969, upload-time = "2026-04-02T18:16:59.611Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/76704c4f312257d6dbaae3c959add2a622f63fcca9d864659ce6d8d97d3d/ruff-0.15.9-py3-none-win_arm64.whl", hash = "sha256:0694e601c028fd97dc5c6ee244675bc241aeefced7ef80cd9c6935a871078f53", size = 11005870, upload-time = "2026-04-02T18:17:15.773Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "virtualenv" +version = "21.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/92/58199fe10049f9703c2666e809c4f686c54ef0a68b0f6afccf518c0b1eb9/virtualenv-21.2.0.tar.gz", hash = "sha256:1720dc3a62ef5b443092e3f499228599045d7fea4c79199770499df8becf9098", size = 5840618, upload-time = "2026-03-09T17:24:38.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl", hash = "sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f", size = 5825084, upload-time = "2026-03-09T17:24:35.378Z" }, +] From 030ecbfe99e60c923c67b495439bec3490627c37 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Mon, 6 Apr 2026 23:32:00 -0700 Subject: [PATCH 04/19] =?UTF-8?q?feat:=20add=20value=20types=20=E2=80=94?= =?UTF-8?q?=20Direction,=20Script,=20Language,=20Feature,=20Variation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expose harfrust's configuration types as Python classes with string parsing, properties, equality, and hashing. Script validates input as 4-letter ISO 15924 tags rather than inheriting harfrust's permissive parsing. All error messages include the offending input. --- python/pyharfrust/__init__.py | 18 ++- src/lib.rs | 3 + src/types.rs | 235 ++++++++++++++++++++++++++++++++++ tests/test_types.py | 213 ++++++++++++++++++++++++++++++ 4 files changed, 467 insertions(+), 2 deletions(-) create mode 100644 src/types.rs create mode 100644 tests/test_types.py diff --git a/python/pyharfrust/__init__.py b/python/pyharfrust/__init__.py index 3f2f340..22c39a8 100644 --- a/python/pyharfrust/__init__.py +++ b/python/pyharfrust/__init__.py @@ -1,3 +1,17 @@ -from pyharfrust._pyharfrust import __version__ +from pyharfrust._pyharfrust import ( + __version__, + Direction, + Feature, + Language, + Script, + Variation, +) -__all__ = ["__version__"] +__all__ = [ + "__version__", + "Direction", + "Feature", + "Language", + "Script", + "Variation", +] diff --git a/src/lib.rs b/src/lib.rs index 8fe709b..ca97e5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,10 @@ use pyo3::prelude::*; +mod types; + #[pymodule] fn _pyharfrust(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add("__version__", env!("CARGO_PKG_VERSION"))?; + types::register(m)?; Ok(()) } diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..1988c90 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,235 @@ +use std::str::FromStr; + +use harfrust::{Direction, Feature, Language, Script, Variation}; +use pyo3::prelude::*; + +// --------------------------------------------------------------------------- +// Direction +// --------------------------------------------------------------------------- + +#[pyclass(name = "Direction", frozen, eq, hash, from_py_object)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct PyDirection(pub(crate) Direction); + +#[pymethods] +impl PyDirection { + #[new] + fn new(s: &str) -> PyResult { + Direction::from_str(s).map(PyDirection).map_err(|_| { + pyo3::exceptions::PyValueError::new_err(format!("invalid direction: {s:?}")) + }) + } + + #[classattr] + const LTR: PyDirection = PyDirection(Direction::LeftToRight); + + #[classattr] + const RTL: PyDirection = PyDirection(Direction::RightToLeft); + + #[classattr] + const TTB: PyDirection = PyDirection(Direction::TopToBottom); + + #[classattr] + const BTT: PyDirection = PyDirection(Direction::BottomToTop); + + fn __repr__(&self) -> &'static str { + match self.0 { + Direction::LeftToRight => "Direction.LTR", + Direction::RightToLeft => "Direction.RTL", + Direction::TopToBottom => "Direction.TTB", + Direction::BottomToTop => "Direction.BTT", + Direction::Invalid => "Direction()", + } + } + + fn __str__(&self) -> &'static str { + match self.0 { + Direction::LeftToRight => "ltr", + Direction::RightToLeft => "rtl", + Direction::TopToBottom => "ttb", + Direction::BottomToTop => "btt", + Direction::Invalid => "invalid", + } + } +} + +// --------------------------------------------------------------------------- +// Script +// --------------------------------------------------------------------------- + +#[pyclass(name = "Script", frozen, eq, hash, from_py_object)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct PyScript(pub(crate) Script); + +#[pymethods] +impl PyScript { + #[new] + fn new(s: &str) -> PyResult { + if s.len() != 4 || !s.bytes().all(|b| b.is_ascii_alphabetic()) { + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "expected a 4-letter ISO 15924 script tag, got {s:?}" + ))); + } + Script::from_str(s) + .map(PyScript) + .map_err(pyo3::exceptions::PyValueError::new_err) + } + + #[getter] + fn tag(&self) -> String { + self.0.tag().to_string() + } + + fn __repr__(&self) -> String { + format!("Script(\"{}\")", self.0.tag()) + } + + fn __str__(&self) -> String { + self.0.tag().to_string() + } +} + +// --------------------------------------------------------------------------- +// Language +// --------------------------------------------------------------------------- + +#[pyclass(name = "Language", frozen, eq, hash, from_py_object)] +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct PyLanguage(pub(crate) Language); + +#[pymethods] +impl PyLanguage { + #[new] + fn new(s: &str) -> PyResult { + Language::from_str(s).map(PyLanguage).map_err(|_| { + pyo3::exceptions::PyValueError::new_err(format!("invalid language: {s:?}")) + }) + } + + fn __repr__(&self) -> String { + format!("Language(\"{}\")", self.0.as_str()) + } + + fn __str__(&self) -> String { + self.0.as_str().to_string() + } +} + +// --------------------------------------------------------------------------- +// Feature +// --------------------------------------------------------------------------- + +#[pyclass(name = "Feature", frozen, eq, hash, from_py_object)] +#[derive(Clone, Copy, PartialEq, Hash)] +pub struct PyFeature(pub(crate) Feature); + +#[pymethods] +impl PyFeature { + #[new] + fn new(s: &str) -> PyResult { + Feature::from_str(s) + .map(PyFeature) + .map_err(|_| pyo3::exceptions::PyValueError::new_err(format!("invalid feature: {s:?}"))) + } + + #[getter] + fn tag(&self) -> String { + self.0.tag.to_string() + } + + #[getter] + fn value(&self) -> u32 { + self.0.value + } + + #[getter] + fn start(&self) -> u32 { + self.0.start + } + + #[getter] + fn end(&self) -> u32 { + self.0.end + } + + fn __repr__(&self) -> String { + let tag = self.0.tag; + let val = self.0.value; + let start = self.0.start; + let end = self.0.end; + if start == 0 && end == u32::MAX { + if val == 1 { + format!("Feature(\"+{tag}\")") + } else if val == 0 { + format!("Feature(\"-{tag}\")") + } else { + format!("Feature(\"{tag}={val}\")") + } + } else { + format!("Feature(\"{tag}[{start}:{end}]={val}\")") + } + } + + fn __str__(&self) -> String { + let tag = self.0.tag; + let val = self.0.value; + let start = self.0.start; + let end = self.0.end; + if start == 0 && end == u32::MAX { + if val == 1 { + format!("+{tag}") + } else if val == 0 { + format!("-{tag}") + } else { + format!("{tag}={val}") + } + } else { + format!("{tag}[{start}:{end}]={val}") + } + } +} + +// --------------------------------------------------------------------------- +// Variation +// --------------------------------------------------------------------------- + +#[pyclass(name = "Variation", frozen, eq, from_py_object)] +#[derive(Clone, Copy, PartialEq)] +pub struct PyVariation(pub(crate) Variation); + +#[pymethods] +impl PyVariation { + #[new] + fn new(s: &str) -> PyResult { + Variation::from_str(s).map(PyVariation).map_err(|_| { + pyo3::exceptions::PyValueError::new_err(format!("invalid variation: {s:?}")) + }) + } + + #[getter] + fn tag(&self) -> String { + self.0.tag.to_string() + } + + #[getter] + fn value(&self) -> f32 { + self.0.value + } + + fn __repr__(&self) -> String { + format!("Variation(\"{}={}\")", self.0.tag, self.0.value) + } + + fn __str__(&self) -> String { + format!("{}={}", self.0.tag, self.0.value) + } +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + Ok(()) +} diff --git a/tests/test_types.py b/tests/test_types.py new file mode 100644 index 0000000..ab47cb5 --- /dev/null +++ b/tests/test_types.py @@ -0,0 +1,213 @@ +import pytest + +from pyharfrust import Direction, Feature, Language, Script, Variation + + +# --------------------------------------------------------------------------- +# Direction +# --------------------------------------------------------------------------- + + +class TestDirection: + def test_from_string(self): + assert Direction("ltr") == Direction.LTR + assert Direction("rtl") == Direction.RTL + assert Direction("ttb") == Direction.TTB + assert Direction("btt") == Direction.BTT + + def test_str(self): + assert str(Direction.LTR) == "ltr" + assert str(Direction.RTL) == "rtl" + assert str(Direction.TTB) == "ttb" + assert str(Direction.BTT) == "btt" + + def test_repr(self): + assert repr(Direction.LTR) == "Direction.LTR" + assert repr(Direction.RTL) == "Direction.RTL" + + def test_equality(self): + assert Direction("ltr") == Direction("ltr") + assert Direction("ltr") != Direction("rtl") + + def test_hash(self): + s = {Direction.LTR, Direction.RTL, Direction.LTR} + assert len(s) == 2 + + def test_invalid_raises(self): + with pytest.raises(ValueError, match="invalid direction"): + Direction("xyz") + + def test_case_insensitive(self): + assert Direction("LTR") == Direction.LTR + assert Direction("Rtl") == Direction.RTL + + +# --------------------------------------------------------------------------- +# Script +# --------------------------------------------------------------------------- + + +class TestScript: + def test_common_scripts(self): + s = Script("Latn") + assert s.tag == "Latn" + + def test_arabic(self): + s = Script("Arab") + assert s.tag == "Arab" + + def test_str(self): + s = Script("Latn") + assert str(s) == "Latn" + + def test_repr(self): + s = Script("Latn") + assert repr(s) == 'Script("Latn")' + + def test_equality(self): + assert Script("Latn") == Script("Latn") + assert Script("Latn") != Script("Arab") + + def test_hash(self): + s = {Script("Latn"), Script("Arab"), Script("Latn")} + assert len(s) == 2 + + def test_unknown_script_explicit(self): + s = Script("Zzzz") + assert s.tag == "Zzzz" + + def test_invalid_raises(self): + with pytest.raises(ValueError, match="4-letter ISO 15924"): + Script("XXXXX") + with pytest.raises(ValueError, match="4-letter ISO 15924"): + Script("") + with pytest.raises(ValueError, match="4-letter ISO 15924"): + Script("1234") + + def test_case_normalization(self): + assert Script("latn").tag == "Latn" + + +# --------------------------------------------------------------------------- +# Language +# --------------------------------------------------------------------------- + + +class TestLanguage: + def test_basic(self): + lang = Language("en") + assert str(lang) == "en" + + def test_subtag(self): + lang = Language("en-US") + assert str(lang) == "en-us" + + def test_repr(self): + lang = Language("en") + assert repr(lang) == 'Language("en")' + + def test_equality(self): + assert Language("en") == Language("en") + assert Language("en") != Language("ar") + + def test_hash(self): + s = {Language("en"), Language("ar"), Language("en")} + assert len(s) == 2 + + def test_invalid_raises(self): + with pytest.raises(ValueError, match="invalid language"): + Language("") + + +# --------------------------------------------------------------------------- +# Feature +# --------------------------------------------------------------------------- + + +class TestFeature: + def test_enable(self): + f = Feature("+kern") + assert f.tag == "kern" + assert f.value == 1 + + def test_disable(self): + f = Feature("-liga") + assert f.tag == "liga" + assert f.value == 0 + + def test_with_value(self): + f = Feature("kern=2") + assert f.tag == "kern" + assert f.value == 2 + + def test_with_range(self): + f = Feature("kern[3:5]=2") + assert f.tag == "kern" + assert f.start == 3 + assert f.end == 5 + assert f.value == 2 + + def test_global_range(self): + f = Feature("+kern") + assert f.start == 0 + assert f.end == 2**32 - 1 + + def test_str_enabled(self): + assert str(Feature("+kern")) == "+kern" + + def test_str_disabled(self): + assert str(Feature("-liga")) == "-liga" + + def test_repr(self): + assert repr(Feature("+kern")) == 'Feature("+kern")' + + def test_equality(self): + assert Feature("+kern") == Feature("+kern") + assert Feature("+kern") != Feature("-kern") + + def test_hash(self): + s = {Feature("+kern"), Feature("-liga"), Feature("+kern")} + assert len(s) == 2 + + def test_invalid_raises(self): + with pytest.raises(ValueError, match="invalid feature"): + Feature("") + + +# --------------------------------------------------------------------------- +# Variation +# --------------------------------------------------------------------------- + + +class TestVariation: + def test_basic(self): + v = Variation("wght=700") + assert v.tag == "wght" + assert v.value == 700.0 + + def test_float_value(self): + v = Variation("wdth=85.5") + assert v.tag == "wdth" + assert abs(v.value - 85.5) < 0.01 + + def test_str(self): + v = Variation("wght=700") + assert "wght" in str(v) + assert "700" in str(v) + + def test_repr(self): + v = Variation("wght=700") + assert "wght" in repr(v) + assert "700" in repr(v) + + def test_equality(self): + assert Variation("wght=700") == Variation("wght=700") + assert Variation("wght=700") != Variation("wght=400") + + def test_not_hashable(self): + with pytest.raises(TypeError, match="unhashable"): + hash(Variation("wght=700")) + + def test_invalid_raises(self): + with pytest.raises(ValueError, match="invalid variation"): + Variation("") From fca25760321658ece806b4709a739284ac8de1f8 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Wed, 8 Apr 2026 22:01:26 -0700 Subject: [PATCH 05/19] fix(types) Polish error handling in script from_str --- src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.rs b/src/types.rs index 1988c90..094d052 100644 --- a/src/types.rs +++ b/src/types.rs @@ -72,7 +72,7 @@ impl PyScript { } Script::from_str(s) .map(PyScript) - .map_err(pyo3::exceptions::PyValueError::new_err) + .map_err(|_| pyo3::exceptions::PyValueError::new_err(format!("invalid script: {s:?}"))) } #[getter] From d00fa647e660b8ac84329e51f70bc1b907ce030e Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Wed, 15 Apr 2026 20:53:18 -0700 Subject: [PATCH 06/19] Add high-level shape() and run_from_args() Python bindings (Phase 3) Wrap hr_shape::shape() and hr_shape::run_from_args() as Python functions, stripping trailing newlines from hr-shape output. Add .tests file regression infrastructure with bundled test fonts and 6 verified test cases covering Latin, RTL, Indic, variable fonts, and mark attachment. --- Cargo.lock | 146 ++++++++++++++++++++ Cargo.toml | 1 + python/pyharfrust/__init__.py | 4 + src/lib.rs | 2 + src/shape.rs | 22 +++ tests/conftest.py | 53 +++++++ tests/data/bundled.tests | 7 + tests/fonts/LaBelleAurore.ttf | Bin 0 -> 59364 bytes tests/fonts/NotoSansMalayalam.subset1.ttf | Bin 0 -> 10552 bytes tests/fonts/OpenSans.subset1.ttf | Bin 0 -> 3196 bytes tests/fonts/PT_Sans-Caption-Web-Regular.ttf | Bin 0 -> 387192 bytes tests/test_shaping.py | 58 ++++++++ 12 files changed, 293 insertions(+) create mode 100644 src/shape.rs create mode 100644 tests/conftest.py create mode 100644 tests/data/bundled.tests create mode 100644 tests/fonts/LaBelleAurore.ttf create mode 100644 tests/fonts/NotoSansMalayalam.subset1.ttf create mode 100644 tests/fonts/OpenSans.subset1.ttf create mode 100644 tests/fonts/PT_Sans-Caption-Web-Regular.ttf create mode 100644 tests/test_shaping.py diff --git a/Cargo.lock b/Cargo.lock index 05decba..158ef91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,56 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + [[package]] name = "bitflags" version = "2.11.0" @@ -28,6 +78,52 @@ dependencies = [ "syn", ] +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "core_maths" version = "0.1.1" @@ -65,6 +161,22 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hr-shape" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f915467aa5ab450c6d5474df0b35be7609b543b273db540dd41a49a9c21c0576" +dependencies = [ + "clap", + "harfrust", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "libc" version = "0.2.184" @@ -83,6 +195,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "portable-atomic" version = "1.13.1" @@ -103,6 +221,7 @@ name = "pyharfrust" version = "0.1.0" dependencies = [ "harfrust", + "hr-shape", "pyo3", ] @@ -190,6 +309,12 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.117" @@ -212,3 +337,24 @@ name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml index dfbe8c8..0787ae5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,5 @@ crate-type = ["cdylib"] [dependencies] harfrust = { version = "0.5.2", features = ["std"] } +hr-shape = { version = "0.5.2" } pyo3 = { version = "0.28.3", features = ["extension-module"] } diff --git a/python/pyharfrust/__init__.py b/python/pyharfrust/__init__.py index 22c39a8..394762b 100644 --- a/python/pyharfrust/__init__.py +++ b/python/pyharfrust/__init__.py @@ -5,6 +5,8 @@ Language, Script, Variation, + run_from_args, + shape, ) __all__ = [ @@ -14,4 +16,6 @@ "Language", "Script", "Variation", + "run_from_args", + "shape", ] diff --git a/src/lib.rs b/src/lib.rs index ca97e5b..45b7f9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,12 @@ use pyo3::prelude::*; +mod shape; mod types; #[pymodule] fn _pyharfrust(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add("__version__", env!("CARGO_PKG_VERSION"))?; types::register(m)?; + shape::register(m)?; Ok(()) } diff --git a/src/shape.rs b/src/shape.rs new file mode 100644 index 0000000..d7355d5 --- /dev/null +++ b/src/shape.rs @@ -0,0 +1,22 @@ +use pyo3::prelude::*; + +#[pyfunction] +#[pyo3(signature = (font_path, text, options=""))] +fn shape(font_path: &str, text: &str, options: &str) -> PyResult { + hr_shape::shape(font_path, text, options) + .map(|s| s.trim_end().to_string()) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e)) +} + +#[pyfunction] +fn run_from_args(args: Vec) -> PyResult { + hr_shape::run_from_args(args) + .map(|s| s.trim_end().to_string()) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e)) +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(shape, m)?)?; + m.add_function(wrap_pyfunction!(run_from_args, m)?)?; + Ok(()) +} diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..c61451c --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,53 @@ +import os + + +BUNDLED_DATA = os.path.join(os.path.dirname(__file__), "data") +BUNDLED_FONTS = os.path.join(os.path.dirname(__file__), "fonts") +HARFRUST_SOURCE = os.environ.get("HARFRUST_SOURCE") + + +def parse_tests_file(path): + """Parse a harfbuzz-format .tests file into test cases.""" + tests_dir = os.path.dirname(path) + cases = [] + for line in open(path): + line = line.strip() + if not line or line.startswith("#") or line.startswith("@"): + continue + parts = line.split(";") + if len(parts) != 4: + continue + fontfile, options, unicodes, expected = parts + if expected == "*" or not expected: + continue + font_path = os.path.normpath(os.path.join(tests_dir, fontfile)) + if not os.path.exists(font_path): + continue + text = "".join( + chr(int(u.strip()[2:], 16)) for u in unicodes.split(",") if u.strip() + ) + cases.append((font_path, text, options, expected)) + return cases + + +def collect_tests_files(): + """Collect .tests files from bundled data and optional external source.""" + cases = [] + if os.path.isdir(BUNDLED_DATA): + for f in sorted(os.listdir(BUNDLED_DATA)): + if f.endswith(".tests"): + cases.extend(parse_tests_file(os.path.join(BUNDLED_DATA, f))) + if HARFRUST_SOURCE: + ext_tests = os.path.join(HARFRUST_SOURCE, "harfrust", "tests", "custom") + if os.path.isdir(ext_tests): + for f in sorted(os.listdir(ext_tests)): + if f.endswith(".tests"): + cases.extend(parse_tests_file(os.path.join(ext_tests, f))) + return cases + + +def pytest_generate_tests(metafunc): + if "tests_case" in metafunc.fixturenames: + cases = collect_tests_files() + ids = [f"{os.path.basename(c[0])}:{c[1][:20]}" for c in cases] + metafunc.parametrize("tests_case", cases, ids=ids) diff --git a/tests/data/bundled.tests b/tests/data/bundled.tests new file mode 100644 index 0000000..550e201 --- /dev/null +++ b/tests/data/bundled.tests @@ -0,0 +1,7 @@ +# Bundled test cases for pyharfrust — expected output generated by hr-shape 0.5.2 +../fonts/PT_Sans-Caption-Web-Regular.ttf;;U+0048,U+0065,U+006C,U+006C,U+006F;[H=0+733|e=1+598|l=2+336|l=3+336|o=4+631] +../fonts/PT_Sans-Caption-Web-Regular.ttf;--direction rtl;U+0041,U+0042;[B=1+641|A=0+645] +../fonts/PT_Sans-Caption-Web-Regular.ttf;;U+1EA4,U+006E;[Acircumflex=0+645|uniF401=0+0|n=1+641] +../fonts/OpenSans.subset1.ttf;--variations=wght=500,wdth=80;U+0065;[gid0=0+1218] +../fonts/NotoSansMalayalam.subset1.ttf;;U+0D38,U+0D4D,U+0D25;[gid7=0+1891] +../fonts/LaBelleAurore.ttf;;U+006B,U+0065,U+031D;[k=0+479|e=1+343|.notdef=1@-172,-59+0] diff --git a/tests/fonts/LaBelleAurore.ttf b/tests/fonts/LaBelleAurore.ttf new file mode 100644 index 0000000000000000000000000000000000000000..df0b59ef27b8bb8e315cbf558a395469109038d6 GIT binary patch literal 59364 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

=9c3d*15!5%9>3qrZ%I=n1zCs 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 literal 0 HcmV?d00001 diff --git a/tests/fonts/NotoSansMalayalam.subset1.ttf b/tests/fonts/NotoSansMalayalam.subset1.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fb867b868acbc4b516faeda745810b138d99626f GIT binary patch literal 10552 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 literal 0 HcmV?d00001 diff --git a/tests/fonts/PT_Sans-Caption-Web-Regular.ttf b/tests/fonts/PT_Sans-Caption-Web-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2ec436065830eae1f32bdad0746bd75f1d794ebd GIT binary patch literal 387192 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�^FXVuTCm({E4HT8>r&zJn3Cyl>s{FJ%6?l*r$ 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%0Ae@&P^?ZmM-Uw@kRF16&4S986!)7~={kL0;! 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 Date: Wed, 15 Apr 2026 21:45:29 -0700 Subject: [PATCH 07/19] Fix clippy warnings and add external test marker - Drop redundant closures in shape.rs to satisfy clippy -D warnings. - Use a context manager for .tests file reading. Scaffold a pytest 'external' marker so Tier 2 cases (from HARFRUST_SOURCE) can be filtered via -m external / -m "not external" once the Tier 2 parser lands. --- pyproject.toml | 3 +++ src/shape.rs | 4 ++-- tests/conftest.py | 56 ++++++++++++++++++++++++++++------------------- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e016d14..63e8451 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,3 +35,6 @@ reportMissingModuleSource = false [tool.pytest.ini_options] testpaths = ["tests"] +markers = [ + "external: tests that require an external harfrust checkout via HARFRUST_SOURCE", +] diff --git a/src/shape.rs b/src/shape.rs index d7355d5..146c742 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -5,14 +5,14 @@ use pyo3::prelude::*; fn shape(font_path: &str, text: &str, options: &str) -> PyResult { hr_shape::shape(font_path, text, options) .map(|s| s.trim_end().to_string()) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e)) + .map_err(pyo3::exceptions::PyRuntimeError::new_err) } #[pyfunction] fn run_from_args(args: Vec) -> PyResult { hr_shape::run_from_args(args) .map(|s| s.trim_end().to_string()) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e)) + .map_err(pyo3::exceptions::PyRuntimeError::new_err) } pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/tests/conftest.py b/tests/conftest.py index c61451c..07fab62 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ import os +import pytest BUNDLED_DATA = os.path.join(os.path.dirname(__file__), "data") BUNDLED_FONTS = os.path.join(os.path.dirname(__file__), "fonts") @@ -10,44 +11,53 @@ def parse_tests_file(path): """Parse a harfbuzz-format .tests file into test cases.""" tests_dir = os.path.dirname(path) cases = [] - for line in open(path): - line = line.strip() - if not line or line.startswith("#") or line.startswith("@"): - continue - parts = line.split(";") - if len(parts) != 4: - continue - fontfile, options, unicodes, expected = parts - if expected == "*" or not expected: - continue - font_path = os.path.normpath(os.path.join(tests_dir, fontfile)) - if not os.path.exists(font_path): - continue - text = "".join( - chr(int(u.strip()[2:], 16)) for u in unicodes.split(",") if u.strip() - ) - cases.append((font_path, text, options, expected)) + with open(path) as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or line.startswith("@"): + continue + parts = line.split(";") + if len(parts) != 4: + continue + fontfile, options, unicodes, expected = parts + if expected == "*" or not expected: + continue + font_path = os.path.normpath(os.path.join(tests_dir, fontfile)) + if not os.path.exists(font_path): + continue + text = "".join( + chr(int(u.strip()[2:], 16)) for u in unicodes.split(",") if u.strip() + ) + cases.append((font_path, text, options, expected)) return cases def collect_tests_files(): - """Collect .tests files from bundled data and optional external source.""" + """Yield (case, is_external) pairs for every bundled and external test case.""" cases = [] if os.path.isdir(BUNDLED_DATA): for f in sorted(os.listdir(BUNDLED_DATA)): if f.endswith(".tests"): - cases.extend(parse_tests_file(os.path.join(BUNDLED_DATA, f))) + for case in parse_tests_file(os.path.join(BUNDLED_DATA, f)): + cases.append((case, False)) if HARFRUST_SOURCE: ext_tests = os.path.join(HARFRUST_SOURCE, "harfrust", "tests", "custom") if os.path.isdir(ext_tests): for f in sorted(os.listdir(ext_tests)): if f.endswith(".tests"): - cases.extend(parse_tests_file(os.path.join(ext_tests, f))) + for case in parse_tests_file(os.path.join(ext_tests, f)): + cases.append((case, True)) return cases def pytest_generate_tests(metafunc): if "tests_case" in metafunc.fixturenames: - cases = collect_tests_files() - ids = [f"{os.path.basename(c[0])}:{c[1][:20]}" for c in cases] - metafunc.parametrize("tests_case", cases, ids=ids) + params = [ + pytest.param( + case, + marks=(pytest.mark.external,) if is_external else (), + id=f"{os.path.basename(case[0])}:{case[1][:20]}", + ) + for case, is_external in collect_tests_files() + ] + metafunc.parametrize("tests_case", params) From fc142e0c9f1e6a911208088ece6b3e31917ff745 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Thu, 16 Apr 2026 00:46:59 -0700 Subject: [PATCH 08/19] Add Tier 2 regression suite against external harfrust corpus Introduces an opt-in regression mode that shapes every test case in a local harfrust checkout through pyharfrust.shape and compares against the expected output embedded in harfrust's own tests. Enabled by setting HARFRUST_SOURCE; default-denied in pytest config so local dev and the main-branch CI stay fast. CI runs Tier 2 only on push/PR targeting the release branch, against harfrust pinned at commit efdae31 (0.5.2) to match the hr-shape dep. --- .github/workflows/ci.yml | 45 ++++++++++++++++++++++++--- pyproject.toml | 1 + tests/conftest.py | 66 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 102 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb7f113..941998d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [main] + branches: [main, release] pull_request: - branches: [main] + branches: [main, release] workflow_dispatch: env: @@ -18,7 +18,7 @@ jobs: ci: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: @@ -26,7 +26,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - - uses: astral-sh/setup-uv@v4 + - uses: astral-sh/setup-uv@v6 with: enable-cache: true cache-dependency-glob: uv.lock @@ -54,3 +54,40 @@ jobs: - name: Python tests run: uv run pytest + + tier2: + # Tier 2: external regression against the full harfrust shaping corpus. + # Only runs on the release branch, where we care about version-drift signal. + if: github.ref == 'refs/heads/release' || github.base_ref == 'release' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Check out harfrust at 0.5.2 + uses: actions/checkout@v6 + with: + repository: harfbuzz/harfrust + # Pinned to the 0.5.2 commit so the test corpus matches the hr-shape + # version declared in Cargo.toml. Bump this alongside the dep. + ref: efdae31 + path: harfrust-external + + - uses: dtolnay/rust-toolchain@stable + + - uses: Swatinem/rust-cache@v2 + + - uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-dependency-glob: uv.lock + + - name: Install Python deps + run: uv sync --locked + + - name: Build extension + run: uv run maturin develop + + - name: Tier 2 shaping regression + env: + HARFRUST_SOURCE: ${{ github.workspace }}/harfrust-external + run: uv run pytest -m external diff --git a/pyproject.toml b/pyproject.toml index 63e8451..a2a7e81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ reportMissingModuleSource = false [tool.pytest.ini_options] testpaths = ["tests"] +addopts = "-m 'not external'" markers = [ "external: tests that require an external harfrust checkout via HARFRUST_SOURCE", ] diff --git a/tests/conftest.py b/tests/conftest.py index 07fab62..e5418be 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import os +import re import pytest @@ -6,6 +7,20 @@ BUNDLED_FONTS = os.path.join(os.path.dirname(__file__), "fonts") HARFRUST_SOURCE = os.environ.get("HARFRUST_SOURCE") +MIN_EXTERNAL_CASES = 5000 + +_RS_TEST_RE = re.compile( + r"shape\(\s*" + r'"((?:\\.|[^"\\])*)"\s*,\s*' + r'"((?:\\.|[^"\\])*)"\s*,\s*' + r'"((?:\\.|[^"\\])*)"\s*,?\s*' + r"\)\s*,\s*" + r'"((?:\\.|[^"\\])*)"', + re.DOTALL, +) +_RS_U_ESC = re.compile(r"\\u\{([0-9A-Fa-f]+)\}") +_RS_LINE_CONT = re.compile(r"\\\n\s*") + def parse_tests_file(path): """Parse a harfbuzz-format .tests file into test cases.""" @@ -32,6 +47,25 @@ def parse_tests_file(path): return cases +def parse_rs_file(path, font_root): + """Extract test cases from a harfrust-generated tests/shaping/*.rs file. + + Font paths inside the file are relative to the harfrust crate root + (e.g. "tests/fonts/in-house/X.ttf"); ``font_root`` is that root. + """ + cases = [] + with open(path) as f: + content = f.read() + for font_rel, text_lit, options, expected in _RS_TEST_RE.findall(content): + text = _RS_LINE_CONT.sub("", text_lit) + text = _RS_U_ESC.sub(lambda m: chr(int(m.group(1), 16)), text) + font_path = os.path.normpath(os.path.join(font_root, font_rel)) + if not os.path.exists(font_path): + continue + cases.append((font_path, text, options, expected)) + return cases + + def collect_tests_files(): """Yield (case, is_external) pairs for every bundled and external test case.""" cases = [] @@ -41,12 +75,32 @@ def collect_tests_files(): for case in parse_tests_file(os.path.join(BUNDLED_DATA, f)): cases.append((case, False)) if HARFRUST_SOURCE: - ext_tests = os.path.join(HARFRUST_SOURCE, "harfrust", "tests", "custom") - if os.path.isdir(ext_tests): - for f in sorted(os.listdir(ext_tests)): - if f.endswith(".tests"): - for case in parse_tests_file(os.path.join(ext_tests, f)): - cases.append((case, True)) + external_cases = _collect_external_cases(HARFRUST_SOURCE) + if len(external_cases) < MIN_EXTERNAL_CASES: + raise RuntimeError( + f"HARFRUST_SOURCE is set to {HARFRUST_SOURCE!r} but only " + f"{len(external_cases)} Tier 2 cases were discovered " + f"(expected at least {MIN_EXTERNAL_CASES}). " + "Either the checkout is missing tests/shaping/*.rs or the parser " + "is out of sync with harfrust's test generator." + ) + cases.extend((c, True) for c in external_cases) + return cases + + +def _collect_external_cases(harfrust_source: str): + cases = [] + harfrust_root = os.path.join(harfrust_source, "harfrust") + shaping_dir = os.path.join(harfrust_root, "tests", "shaping") + if os.path.isdir(shaping_dir): + for f in sorted(os.listdir(shaping_dir)): + if f.endswith(".rs") and f != "main.rs": + cases.extend(parse_rs_file(os.path.join(shaping_dir, f), harfrust_root)) + custom_dir = os.path.join(harfrust_root, "tests", "custom") + if os.path.isdir(custom_dir): + for f in sorted(os.listdir(custom_dir)): + if f.endswith(".tests"): + cases.extend(parse_tests_file(os.path.join(custom_dir, f))) return cases From bdaa43f1eceb2af18887ca11cad146c44172d5a6 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Thu, 16 Apr 2026 10:14:27 -0700 Subject: [PATCH 09/19] fix: exclude .tests source files from external test collection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `_collect_external_cases()` function was incorrectly parsing `.tests` files from harfrust's `tests/custom/` directory. These files are source inputs for harfrust's test generator (`gen-shaping-tests.py`), not actual test cases to run. They contain expected values that may intentionally differ from harfrust's current behavior, as noted in the files themselves: "the expected values for the shaping process will be ignored and sometimes wrong." This caused false test failures, such as the `--language=pl` BigCaslon test which expects `cacute.polish` but harfrust outputs `cacute` (confirmed by running hr-shape CLI directly). Now we only parse the generated `.rs` files in `tests/shaping/`, which represent the actual tests that `cargo test` runs in harfrust. Test count: 6145 → 6139 external cases (removed 6 invalid cases from .tests) --- tests/conftest.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index e5418be..e48687a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -89,6 +89,14 @@ def collect_tests_files(): def _collect_external_cases(harfrust_source: str): + """Collect test cases from harfrust's generated .rs test files. + + Note: We only parse .rs files in tests/shaping/, NOT the .tests files in + tests/custom/. The .tests files are source files for harfrust's test + generator (gen-shaping-tests.py) and may contain tests that are + intentionally excluded from the generated .rs files (e.g., macOS-only + tests, tests with known different expected values, etc.). + """ cases = [] harfrust_root = os.path.join(harfrust_source, "harfrust") shaping_dir = os.path.join(harfrust_root, "tests", "shaping") @@ -96,11 +104,6 @@ def _collect_external_cases(harfrust_source: str): for f in sorted(os.listdir(shaping_dir)): if f.endswith(".rs") and f != "main.rs": cases.extend(parse_rs_file(os.path.join(shaping_dir, f), harfrust_root)) - custom_dir = os.path.join(harfrust_root, "tests", "custom") - if os.path.isdir(custom_dir): - for f in sorted(os.listdir(custom_dir)): - if f.endswith(".tests"): - cases.extend(parse_tests_file(os.path.join(custom_dir, f))) return cases From 5a387ecb4da8055e147c0dc945148fca08ae6189 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Tue, 21 Apr 2026 17:59:58 -0700 Subject: [PATCH 10/19] docs: fix stale package name and plan link Update ROADMAP.md to use the pyharfrust package name (python/pyharfrust/ and pyharfrust.shape()) and point README.md at ROADMAP.md instead of the renamed PLAN.md. --- README.md | 2 +- ROADMAP.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b21e1e3..082b5b6 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ HarfBuzz is the industry-standard text shaping engine used by Firefox, Chrome, A ## Status -This project is in early development. See the [development plan](https://github.com/nickkuk/harfrust-python/blob/main/PLAN.md) for the phased roadmap. +This project is in early development. See the [development plan](https://github.com/nickkuk/harfrust-python/blob/main/ROADMAP.md) for the phased roadmap. ## License diff --git a/ROADMAP.md b/ROADMAP.md index 67546f6..a568f22 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ ## Phase 1 — Project Skeleton -Buildable Python package that imports successfully. Set up `Cargo.toml` (PyO3 cdylib), `pyproject.toml` (maturin backend), a minimal `src/lib.rs` with a version function, and `python/harfrust/__init__.py` that re-exports it. Include an MIT `LICENSE` file and a GitHub Actions CI workflow (`maturin develop` + `pytest`). +Buildable Python package that imports successfully. Set up `Cargo.toml` (PyO3 cdylib), `pyproject.toml` (maturin backend), a minimal `src/lib.rs` with a version function, and `python/pyharfrust/__init__.py` that re-exports it. Include an MIT `LICENSE` file and a GitHub Actions CI workflow (`maturin develop` + `pytest`). **Done when:** `maturin develop && pytest` passes with a single `test_import` test, LICENSE exists, and CI is green. @@ -16,7 +16,7 @@ Expose `Direction`, `Script`, `Language`, `Feature`, and `Variation` as Python c Wrap `hr_shape::shape()` and `hr_shape::run_from_args()` as top-level Python functions. Set up a `.tests` regression suite with bundled test fonts and data, plus optional discovery of an external harfrust checkout via `HARFRUST_SOURCE`. -**Done when:** `harfrust.shape(font_path, text, options)` returns a serialized glyph string and bundled `.tests` cases pass. +**Done when:** `pyharfrust.shape(font_path, text, options)` returns a serialized glyph string and bundled `.tests` cases pass. ## Phase 4 — Buffer Class From 4af6756aa7c775997205a1b5dc72dd3384b41dc4 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Wed, 22 Apr 2026 11:54:32 -0700 Subject: [PATCH 11/19] feat: Buffer class wrapping UnicodeBuffer (Phase 4) Pythonic Buffer class backed by Option, which establishes the consumption pattern for Phase 5 shape() without yet exposing a consumer. Covers the operations needed to drive shaping end-to-end: text accumulation, direction/script/language properties, guess_segment_properties, context setters, and clear. --- python/pyharfrust/__init__.py | 2 + src/buffer.rs | 158 ++++++++++++++++++++++++++++ src/lib.rs | 2 + tests/test_buffer.py | 191 ++++++++++++++++++++++++++++++++++ 4 files changed, 353 insertions(+) create mode 100644 src/buffer.rs create mode 100644 tests/test_buffer.py diff --git a/python/pyharfrust/__init__.py b/python/pyharfrust/__init__.py index 394762b..aa2f295 100644 --- a/python/pyharfrust/__init__.py +++ b/python/pyharfrust/__init__.py @@ -1,5 +1,6 @@ from pyharfrust._pyharfrust import ( __version__, + Buffer, Direction, Feature, Language, @@ -11,6 +12,7 @@ __all__ = [ "__version__", + "Buffer", "Direction", "Feature", "Language", diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000..fb0217b --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,158 @@ +use harfrust::UnicodeBuffer; +use pyo3::exceptions::PyValueError; +use pyo3::prelude::*; + +use crate::types::{PyDirection, PyLanguage, PyScript}; + +#[pyclass(name = "Buffer", unsendable)] +pub struct PyBuffer { + pub(crate) inner: Option, +} + +impl PyBuffer { + fn consumed_err() -> PyErr { + PyValueError::new_err("Buffer has been consumed by shape()") + } + + fn as_ref_buf(&self) -> PyResult<&UnicodeBuffer> { + self.inner.as_ref().ok_or_else(Self::consumed_err) + } + + fn as_mut_buf(&mut self) -> PyResult<&mut UnicodeBuffer> { + self.inner.as_mut().ok_or_else(Self::consumed_err) + } + + #[allow(dead_code)] + pub(crate) fn take_inner(&mut self) -> PyResult { + self.inner.take().ok_or_else(Self::consumed_err) + } + + #[allow(dead_code)] + pub(crate) fn restore(&mut self, buf: UnicodeBuffer) { + self.inner = Some(buf); + } +} + +#[pymethods] +impl PyBuffer { + #[new] + fn new() -> Self { + PyBuffer { + inner: Some(UnicodeBuffer::new()), + } + } + + fn __len__(&self) -> PyResult { + Ok(self.as_ref_buf()?.len()) + } + + fn add_str(&mut self, s: &str) -> PyResult<()> { + self.as_mut_buf()?.push_str(s); + Ok(()) + } + + #[pyo3(signature = (codepoint, cluster=0))] + fn add(&mut self, codepoint: u32, cluster: u32) -> PyResult<()> { + let ch = char::from_u32(codepoint).ok_or_else(|| { + PyValueError::new_err(format!("invalid unicode codepoint: U+{codepoint:04X}")) + })?; + self.as_mut_buf()?.add(ch, cluster); + Ok(()) + } + + fn clear(&mut self) -> PyResult<()> { + self.as_mut_buf()?.clear(); + Ok(()) + } + + fn reset_clusters(&mut self) -> PyResult<()> { + self.as_mut_buf()?.reset_clusters(); + Ok(()) + } + + fn guess_segment_properties(&mut self) -> PyResult<()> { + self.as_mut_buf()?.guess_segment_properties(); + Ok(()) + } + + fn reserve(&mut self, size: usize) -> PyResult { + Ok(self.as_mut_buf()?.reserve(size)) + } + + fn set_pre_context(&mut self, s: &str) -> PyResult<()> { + self.as_mut_buf()?.set_pre_context(s); + Ok(()) + } + + fn set_post_context(&mut self, s: &str) -> PyResult<()> { + self.as_mut_buf()?.set_post_context(s); + Ok(()) + } + + fn set_not_found_variation_selector_glyph(&mut self, glyph: u32) -> PyResult<()> { + self.as_mut_buf()? + .set_not_found_variation_selector_glyph(glyph); + Ok(()) + } + + #[getter] + fn direction(&self) -> PyResult { + Ok(PyDirection(self.as_ref_buf()?.direction())) + } + + #[setter] + fn set_direction(&mut self, d: PyDirection) -> PyResult<()> { + self.as_mut_buf()?.set_direction(d.0); + Ok(()) + } + + #[getter] + fn script(&self) -> PyResult { + Ok(PyScript(self.as_ref_buf()?.script())) + } + + #[setter] + fn set_script(&mut self, s: PyScript) -> PyResult<()> { + self.as_mut_buf()?.set_script(s.0); + Ok(()) + } + + #[getter] + fn language(&self) -> PyResult> { + Ok(self.as_ref_buf()?.language().map(PyLanguage)) + } + + #[setter] + fn set_language(&mut self, l: PyLanguage) -> PyResult<()> { + self.as_mut_buf()?.set_language(l.0); + Ok(()) + } + + fn __repr__(&self) -> PyResult { + let buf = self.as_ref_buf()?; + let dir = match buf.direction() { + harfrust::Direction::LeftToRight => "Direction.LTR", + harfrust::Direction::RightToLeft => "Direction.RTL", + harfrust::Direction::TopToBottom => "Direction.TTB", + harfrust::Direction::BottomToTop => "Direction.BTT", + harfrust::Direction::Invalid => "Direction()", + }; + let script = buf.script().tag().to_string(); + let lang = buf + .language() + .map(|l| format!("\"{}\"", l.as_str())) + .unwrap_or_else(|| "None".to_string()); + Ok(format!( + "Buffer(len={}, direction={}, script=\"{}\", language={})", + buf.len(), + dir, + script, + lang, + )) + } +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 45b7f9e..bbbc035 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ use pyo3::prelude::*; +mod buffer; mod shape; mod types; @@ -7,6 +8,7 @@ mod types; fn _pyharfrust(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add("__version__", env!("CARGO_PKG_VERSION"))?; types::register(m)?; + buffer::register(m)?; shape::register(m)?; Ok(()) } diff --git a/tests/test_buffer.py b/tests/test_buffer.py new file mode 100644 index 0000000..9b2602e --- /dev/null +++ b/tests/test_buffer.py @@ -0,0 +1,191 @@ +import pytest + +from pyharfrust import Buffer, Direction, Language, Script + + +# --------------------------------------------------------------------------- +# Construction / length +# --------------------------------------------------------------------------- + + +class TestCreation: + def test_empty(self): + buf = Buffer() + assert len(buf) == 0 + + def test_add_str_ascii(self): + buf = Buffer() + buf.add_str("Hello") + assert len(buf) == 5 + + def test_add_str_multi_bmp(self): + buf = Buffer() + buf.add_str("العربية") + assert len(buf) == 7 + + def test_add_str_astral(self): + buf = Buffer() + buf.add_str("A\U0001f600B") + assert len(buf) == 3 + + def test_add_str_empty(self): + buf = Buffer() + buf.add_str("") + assert len(buf) == 0 + + def test_add_str_multiple_calls_append(self): + buf = Buffer() + buf.add_str("Hel") + buf.add_str("lo") + assert len(buf) == 5 + + def test_add_codepoint(self): + buf = Buffer() + buf.add(0x0041) + buf.add(0x0042, cluster=7) + assert len(buf) == 2 + + +# --------------------------------------------------------------------------- +# Clear / reset +# --------------------------------------------------------------------------- + + +class TestClear: + def test_clear_empties(self): + buf = Buffer() + buf.add_str("hello") + buf.clear() + assert len(buf) == 0 + + def test_clear_allows_reuse(self): + buf = Buffer() + buf.add_str("one") + buf.clear() + buf.add_str("two") + assert len(buf) == 3 + + def test_reset_clusters(self): + buf = Buffer() + buf.add_str("abc") + buf.reset_clusters() + assert len(buf) == 3 + + +# --------------------------------------------------------------------------- +# Direction / Script / Language properties +# --------------------------------------------------------------------------- + + +class TestProperties: + def test_set_get_direction(self): + buf = Buffer() + buf.direction = Direction.RTL + assert buf.direction == Direction.RTL + buf.direction = Direction.LTR + assert buf.direction == Direction.LTR + + def test_set_get_script(self): + buf = Buffer() + buf.script = Script("Arab") + assert buf.script == Script("Arab") + buf.script = Script("Latn") + assert buf.script == Script("Latn") + + def test_set_get_language(self): + buf = Buffer() + buf.language = Language("en") + assert buf.language == Language("en") + buf.language = Language("ar") + assert buf.language == Language("ar") + + def test_language_none_when_unset(self): + buf = Buffer() + assert buf.language is None + + def test_direction_setter_rejects_nondirection(self): + buf = Buffer() + with pytest.raises(TypeError): + buf.direction = "ltr" + + def test_script_setter_rejects_nonscript(self): + buf = Buffer() + with pytest.raises(TypeError): + buf.script = "Latn" + + def test_language_setter_rejects_nonlanguage(self): + buf = Buffer() + with pytest.raises(TypeError): + buf.language = "en" + + +# --------------------------------------------------------------------------- +# Context setters +# --------------------------------------------------------------------------- + + +class TestContext: + def test_set_pre_context(self): + buf = Buffer() + buf.set_pre_context("abc") + # no getter in upstream API; just verify the call succeeds + + def test_set_post_context(self): + buf = Buffer() + buf.set_post_context("xyz") + + +# --------------------------------------------------------------------------- +# guess_segment_properties +# --------------------------------------------------------------------------- + + +class TestGuessSegmentProperties: + def test_arabic_guesses_rtl(self): + buf = Buffer() + buf.add_str("العربية") + buf.guess_segment_properties() + assert buf.direction == Direction.RTL + assert buf.script == Script("Arab") + + def test_latin_guesses_ltr(self): + buf = Buffer() + buf.add_str("Hello") + buf.guess_segment_properties() + assert buf.direction == Direction.LTR + assert buf.script == Script("Latn") + + +# --------------------------------------------------------------------------- +# reserve +# --------------------------------------------------------------------------- + + +class TestReserve: + def test_reserve_returns_bool(self): + buf = Buffer() + result = buf.reserve(128) + assert isinstance(result, bool) + assert result is True + + def test_reserve_does_not_change_length(self): + buf = Buffer() + buf.add_str("hi") + buf.reserve(1024) + assert len(buf) == 2 + + +# --------------------------------------------------------------------------- +# repr +# --------------------------------------------------------------------------- + + +class TestRepr: + def test_repr_mentions_class(self): + buf = Buffer() + assert "Buffer" in repr(buf) + + def test_repr_shows_length(self): + buf = Buffer() + buf.add_str("hi") + assert "2" in repr(buf) From d75419d021db920e9948ccdf7d8a8893b220f712 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Wed, 22 Apr 2026 12:15:27 -0700 Subject: [PATCH 12/19] Test set_not_found_variation_selector_glyph and flag repr duplication Adds a Buffer test covering set_not_found_variation_selector_glyph, which was exposed but previously unexercised. Marks the Direction label match inside Buffer.__repr__ with a comment pointing at the equivalent (private) match in PyDirection::__repr__, so the next edit to either keeps them in sync. Expands the TestContext header to spell out why pre/post context tests only assert that the call doesn't raise. --- src/buffer.rs | 3 +++ tests/test_buffer.py | 10 +++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/buffer.rs b/src/buffer.rs index fb0217b..4d4e4c1 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -130,6 +130,9 @@ impl PyBuffer { fn __repr__(&self) -> PyResult { let buf = self.as_ref_buf()?; + // NOTE: duplicated from PyDirection::__repr__ in types.rs (private there). + // If Direction's repr labels ever change, update both sites — or promote + // this match to a shared helper. let dir = match buf.direction() { harfrust::Direction::LeftToRight => "Direction.LTR", harfrust::Direction::RightToLeft => "Direction.RTL", diff --git a/tests/test_buffer.py b/tests/test_buffer.py index 9b2602e..656596b 100644 --- a/tests/test_buffer.py +++ b/tests/test_buffer.py @@ -121,6 +121,10 @@ def test_language_setter_rejects_nonlanguage(self): # --------------------------------------------------------------------------- # Context setters +# +# harfrust exposes only setters for pre/post context (no getters), so these +# tests can only confirm the calls don't raise. The real regression signal +# will come from Phase 5 shaping tests where context changes affect output. # --------------------------------------------------------------------------- @@ -128,12 +132,16 @@ class TestContext: def test_set_pre_context(self): buf = Buffer() buf.set_pre_context("abc") - # no getter in upstream API; just verify the call succeeds def test_set_post_context(self): buf = Buffer() buf.set_post_context("xyz") + def test_set_not_found_variation_selector_glyph(self): + buf = Buffer() + buf.set_not_found_variation_selector_glyph(0) + buf.set_not_found_variation_selector_glyph(42) + # --------------------------------------------------------------------------- # guess_segment_properties From 5c97674eb932454dab72030e2e1ee7e38a43eba5 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Sat, 25 Apr 2026 03:20:01 -0700 Subject: [PATCH 13/19] docs: update development plan link in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 082b5b6..b6f5d90 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ HarfBuzz is the industry-standard text shaping engine used by Firefox, Chrome, A ## Status -This project is in early development. See the [development plan](https://github.com/nickkuk/harfrust-python/blob/main/ROADMAP.md) for the phased roadmap. +This project is in early development. See the [development plan](https://github.com/hasanzakeri/harfrust-python/blob/main/ROADMAP.md) for the phased roadmap. ## License From 3d721e12131829ffe233e82eb411479e83e84f1e Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Sat, 25 Apr 2026 14:47:52 -0700 Subject: [PATCH 14/19] feat: Introduce Font, GlyphBuffer, GlyphInfo, and GlyphPosition classes - Added `Font` class for loading and managing font data, including methods for creating instances from file paths and byte arrays. - Introduced `GlyphBuffer`, `GlyphInfo`, and `GlyphPosition` classes to handle glyph shaping and positioning, providing a structured way to access glyph information. - Updated `__init__.py` to include new classes in the module's public API. - Enhanced `Buffer` class with methods for handling glyph data and serialization. - Added comprehensive tests for the new font and glyph functionalities, ensuring correct behavior and integration with existing components. --- python/pyharfrust/__init__.py | 8 + src/buffer.rs | 2 +- src/font.rs | 178 +++++++++++++++++ src/glyph.rs | 180 ++++++++++++++++++ src/lib.rs | 4 + tests/test_font.py | 346 ++++++++++++++++++++++++++++++++++ 6 files changed, 717 insertions(+), 1 deletion(-) create mode 100644 src/font.rs create mode 100644 src/glyph.rs create mode 100644 tests/test_font.py diff --git a/python/pyharfrust/__init__.py b/python/pyharfrust/__init__.py index aa2f295..f65e703 100644 --- a/python/pyharfrust/__init__.py +++ b/python/pyharfrust/__init__.py @@ -3,6 +3,10 @@ Buffer, Direction, Feature, + Font, + GlyphBuffer, + GlyphInfo, + GlyphPosition, Language, Script, Variation, @@ -15,6 +19,10 @@ "Buffer", "Direction", "Feature", + "Font", + "GlyphBuffer", + "GlyphInfo", + "GlyphPosition", "Language", "Script", "Variation", diff --git a/src/buffer.rs b/src/buffer.rs index 4d4e4c1..5cfde80 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -14,7 +14,7 @@ impl PyBuffer { PyValueError::new_err("Buffer has been consumed by shape()") } - fn as_ref_buf(&self) -> PyResult<&UnicodeBuffer> { + pub(crate) fn as_ref_buf(&self) -> PyResult<&UnicodeBuffer> { self.inner.as_ref().ok_or_else(Self::consumed_err) } diff --git a/src/font.rs b/src/font.rs new file mode 100644 index 0000000..84bb964 --- /dev/null +++ b/src/font.rs @@ -0,0 +1,178 @@ +use std::str::FromStr; + +use harfrust::{Feature, FontRef, Shaper, ShaperData, ShaperInstance, Variation}; +use pyo3::exceptions::{PyRuntimeError, PyTypeError, PyValueError}; +use pyo3::prelude::*; + +use crate::buffer::PyBuffer; +use crate::glyph::PyGlyphBuffer; +use crate::types::{PyFeature, PyVariation}; + +#[pyclass(name = "Font", unsendable)] +pub struct PyFont { + data: Vec, + face_index: u32, + shaper_data: ShaperData, + instance: Option, + point_size: Option, +} + +impl PyFont { + fn build_from_bytes(data: Vec, face_index: u32) -> PyResult { + // Validate the font and build ShaperData up front so subsequent calls + // don't need to re-parse. ShaperData is 'static — it borrows FontRef + // only during construction. + let shaper_data = { + let font = FontRef::from_index(&data, face_index) + .map_err(|e| PyRuntimeError::new_err(format!("failed to parse font: {e}")))?; + ShaperData::new(&font) + }; + Ok(Self { + data, + face_index, + shaper_data, + instance: None, + point_size: None, + }) + } + + pub(crate) fn font_ref(&self) -> PyResult> { + FontRef::from_index(&self.data, self.face_index) + .map_err(|e| PyRuntimeError::new_err(format!("failed to parse font: {e}"))) + } + + pub(crate) fn build_shaper<'a>(&'a self, font: &FontRef<'a>) -> Shaper<'a> { + self.shaper_data + .shaper(font) + .instance(self.instance.as_ref()) + .point_size(self.point_size) + .build() + } +} + +#[pymethods] +impl PyFont { + #[new] + #[pyo3(signature = (path, face_index=0))] + fn new(path: &str, face_index: u32) -> PyResult { + let data = std::fs::read(path) + .map_err(|e| PyRuntimeError::new_err(format!("failed to read {path:?}: {e}")))?; + Self::build_from_bytes(data, face_index) + } + + #[staticmethod] + #[pyo3(signature = (data, face_index=0))] + fn from_bytes(data: Vec, face_index: u32) -> PyResult { + Self::build_from_bytes(data, face_index) + } + + #[getter] + fn face_index(&self) -> u32 { + self.face_index + } + + #[getter] + fn units_per_em(&self) -> PyResult { + let font = self.font_ref()?; + Ok(self.build_shaper(&font).units_per_em()) + } + + fn set_variations(&mut self, variations: &Bound<'_, PyAny>) -> PyResult<()> { + let vars = parse_variations(variations)?; + if vars.is_empty() { + self.instance = None; + return Ok(()); + } + // Direct field access (rather than self.font_ref()) so the borrow + // checker can split self.data (immutable) from self.instance (mutable). + let font = FontRef::from_index(&self.data, self.face_index) + .map_err(|e| PyRuntimeError::new_err(format!("failed to parse font: {e}")))?; + match &mut self.instance { + Some(inst) => inst.set_variations(&font, vars), + None => { + self.instance = Some(ShaperInstance::from_variations(&font, vars)); + } + } + Ok(()) + } + + #[pyo3(signature = (size))] + fn set_point_size(&mut self, size: Option) { + self.point_size = size; + } + + #[pyo3(signature = (buffer, features=None))] + fn shape( + &self, + buffer: &mut PyBuffer, + features: Option<&Bound<'_, PyAny>>, + ) -> PyResult { + let feats = match features { + Some(any) => parse_features(any)?, + None => Vec::new(), + }; + // Reject Invalid direction up front — harfrust panics on it otherwise, + // surfacing as PanicException in Python instead of a clean error. + if buffer.as_ref_buf()?.direction() == harfrust::Direction::Invalid { + return Err(PyValueError::new_err( + "buffer direction is unset; call buffer.guess_segment_properties() \ + or assign buffer.direction before shaping", + )); + } + let inner = buffer.take_inner()?; + let font = self.font_ref()?; + let shaper = self.build_shaper(&font); + Ok(PyGlyphBuffer::wrap(shaper.shape(inner, &feats))) + } + + fn __repr__(&self) -> String { + format!( + "Font(face_index={}, bytes={})", + self.face_index, + self.data.len() + ) + } +} + +fn parse_features(any: &Bound<'_, PyAny>) -> PyResult> { + if let Ok(s) = any.extract::<&str>() { + return parse_csv(s, "feature", Feature::from_str); + } + if let Ok(seq) = any.extract::>() { + return Ok(seq.into_iter().map(|f| f.0).collect()); + } + Err(PyTypeError::new_err( + "expected a sequence of Feature objects or a string", + )) +} + +fn parse_variations(any: &Bound<'_, PyAny>) -> PyResult> { + if let Ok(s) = any.extract::<&str>() { + return parse_csv(s, "variation", Variation::from_str); + } + if let Ok(seq) = any.extract::>() { + return Ok(seq.into_iter().map(|v| v.0).collect()); + } + Err(PyTypeError::new_err( + "expected a sequence of Variation objects or a string", + )) +} + +fn parse_csv(s: &str, label: &str, parse: impl Fn(&str) -> Result) -> PyResult> { + let mut out = Vec::new(); + for piece in s.split(',') { + let piece = piece.trim(); + if piece.is_empty() { + continue; + } + let value = parse(piece) + .map_err(|_| PyValueError::new_err(format!("invalid {label}: {piece:?}")))?; + out.push(value); + } + Ok(out) +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + Ok(()) +} diff --git a/src/glyph.rs b/src/glyph.rs new file mode 100644 index 0000000..2e2b621 --- /dev/null +++ b/src/glyph.rs @@ -0,0 +1,180 @@ +use harfrust::{GlyphBuffer, GlyphInfo, GlyphPosition, SerializeFlags}; +use pyo3::exceptions::{PyIndexError, PyValueError}; +use pyo3::prelude::*; + +use crate::buffer::PyBuffer; +use crate::font::PyFont; + +// --------------------------------------------------------------------------- +// GlyphInfo +// --------------------------------------------------------------------------- + +#[pyclass(name = "GlyphInfo", frozen, from_py_object)] +#[derive(Clone, Copy)] +pub struct PyGlyphInfo { + #[pyo3(get)] + glyph_id: u32, + #[pyo3(get)] + cluster: u32, + #[pyo3(get)] + unsafe_to_break: bool, + #[pyo3(get)] + unsafe_to_concat: bool, + #[pyo3(get)] + safe_to_insert_tatweel: bool, +} + +impl PyGlyphInfo { + fn from_info(info: &GlyphInfo) -> Self { + Self { + glyph_id: info.glyph_id, + cluster: info.cluster, + unsafe_to_break: info.unsafe_to_break(), + unsafe_to_concat: info.unsafe_to_concat(), + safe_to_insert_tatweel: info.safe_to_insert_tatweel(), + } + } +} + +#[pymethods] +impl PyGlyphInfo { + fn __repr__(&self) -> String { + format!( + "GlyphInfo(glyph_id={}, cluster={})", + self.glyph_id, self.cluster + ) + } +} + +// --------------------------------------------------------------------------- +// GlyphPosition +// --------------------------------------------------------------------------- + +#[pyclass(name = "GlyphPosition", frozen, from_py_object)] +#[derive(Clone, Copy)] +pub struct PyGlyphPosition { + #[pyo3(get)] + x_advance: i32, + #[pyo3(get)] + y_advance: i32, + #[pyo3(get)] + x_offset: i32, + #[pyo3(get)] + y_offset: i32, +} + +impl PyGlyphPosition { + fn from_pos(pos: &GlyphPosition) -> Self { + Self { + x_advance: pos.x_advance, + y_advance: pos.y_advance, + x_offset: pos.x_offset, + y_offset: pos.y_offset, + } + } +} + +#[pymethods] +impl PyGlyphPosition { + fn __repr__(&self) -> String { + format!( + "GlyphPosition(x_advance={}, y_advance={}, x_offset={}, y_offset={})", + self.x_advance, self.y_advance, self.x_offset, self.y_offset + ) + } +} + +// --------------------------------------------------------------------------- +// GlyphBuffer +// --------------------------------------------------------------------------- + +#[pyclass(name = "GlyphBuffer", unsendable)] +pub struct PyGlyphBuffer { + inner: Option, +} + +impl PyGlyphBuffer { + pub(crate) fn wrap(buf: GlyphBuffer) -> Self { + Self { inner: Some(buf) } + } + + fn consumed_err() -> PyErr { + PyValueError::new_err("GlyphBuffer has been consumed by clear()") + } + + fn as_ref_buf(&self) -> PyResult<&GlyphBuffer> { + self.inner.as_ref().ok_or_else(Self::consumed_err) + } +} + +#[pymethods] +impl PyGlyphBuffer { + fn __len__(&self) -> PyResult { + Ok(self.as_ref_buf()?.len()) + } + + #[getter] + fn glyph_infos(&self) -> PyResult> { + Ok(self + .as_ref_buf()? + .glyph_infos() + .iter() + .map(PyGlyphInfo::from_info) + .collect()) + } + + #[getter] + fn glyph_positions(&self) -> PyResult> { + Ok(self + .as_ref_buf()? + .glyph_positions() + .iter() + .map(PyGlyphPosition::from_pos) + .collect()) + } + + // Implementing __getitem__ + __len__ makes the buffer iterable in Python + // via the legacy iteration protocol, so `for info, pos in gbuf:` works + // without a separate iterator class. + fn __getitem__(&self, index: isize) -> PyResult<(PyGlyphInfo, PyGlyphPosition)> { + let buf = self.as_ref_buf()?; + let len = buf.len() as isize; + let idx = if index < 0 { index + len } else { index }; + if idx < 0 || idx >= len { + return Err(PyIndexError::new_err("glyph index out of range")); + } + let i = idx as usize; + Ok(( + PyGlyphInfo::from_info(&buf.glyph_infos()[i]), + PyGlyphPosition::from_pos(&buf.glyph_positions()[i]), + )) + } + + fn clear(&mut self) -> PyResult { + let inner = self.inner.take().ok_or_else(Self::consumed_err)?; + Ok(PyBuffer { + inner: Some(inner.clear()), + }) + } + + fn serialize(&self, font: PyRef<'_, PyFont>) -> PyResult { + let buf = self.as_ref_buf()?; + let font_ref = font.font_ref()?; + let shaper = font.build_shaper(&font_ref); + Ok(buf.serialize(&shaper, SerializeFlags::default())) + } + + fn __repr__(&self) -> String { + match self.inner.as_ref() { + Some(b) => format!("GlyphBuffer(len={})", b.len()), + None => "GlyphBuffer()".to_string(), + } + } +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index bbbc035..ea63651 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ use pyo3::prelude::*; mod buffer; +mod font; +mod glyph; mod shape; mod types; @@ -9,6 +11,8 @@ fn _pyharfrust(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add("__version__", env!("CARGO_PKG_VERSION"))?; types::register(m)?; buffer::register(m)?; + glyph::register(m)?; + font::register(m)?; shape::register(m)?; Ok(()) } diff --git a/tests/test_font.py b/tests/test_font.py new file mode 100644 index 0000000..e3eb65a --- /dev/null +++ b/tests/test_font.py @@ -0,0 +1,346 @@ +import os + +import pytest + +from pyharfrust import ( + Buffer, + Feature, + Font, + GlyphBuffer, + GlyphInfo, + GlyphPosition, + Variation, + shape, +) + +FONTS_DIR = os.path.join(os.path.dirname(__file__), "fonts") +PT_SANS = os.path.join(FONTS_DIR, "PT_Sans-Caption-Web-Regular.ttf") +OPEN_SANS = os.path.join(FONTS_DIR, "OpenSans.subset1.ttf") + + +def _shape_str(font, text, features=None): + buf = Buffer() + buf.add_str(text) + buf.guess_segment_properties() + return font.shape(buf, features) if features is not None else font.shape(buf) + + +# --------------------------------------------------------------------------- +# Construction +# --------------------------------------------------------------------------- + + +class TestConstruction: + def test_from_path(self): + font = Font(PT_SANS) + assert font.face_index == 0 + assert font.units_per_em > 0 + + def test_face_index_kw(self): + font = Font(PT_SANS, face_index=0) + assert font.face_index == 0 + + def test_missing_file_raises(self): + with pytest.raises(RuntimeError): + Font("/nonexistent/font.ttf") + + def test_from_bytes(self): + with open(PT_SANS, "rb") as f: + data = f.read() + font = Font.from_bytes(data) + assert font.units_per_em > 0 + + def test_from_bytes_invalid(self): + with pytest.raises(RuntimeError): + Font.from_bytes(b"not a font file") + + +# --------------------------------------------------------------------------- +# Shaping basics +# --------------------------------------------------------------------------- + + +class TestShape: + def test_returns_glyph_buffer(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "Hello") + assert isinstance(gbuf, GlyphBuffer) + assert len(gbuf) == 5 + + def test_glyph_infos_and_positions(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "Hi") + infos = gbuf.glyph_infos + positions = gbuf.glyph_positions + assert len(infos) == len(positions) == len(gbuf) + assert all(isinstance(i, GlyphInfo) for i in infos) + assert all(isinstance(p, GlyphPosition) for p in positions) + + def test_glyph_info_fields(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "AB") + info = gbuf.glyph_infos[0] + assert isinstance(info.glyph_id, int) and info.glyph_id > 0 + assert info.cluster == 0 + assert isinstance(info.unsafe_to_break, bool) + assert isinstance(info.unsafe_to_concat, bool) + assert isinstance(info.safe_to_insert_tatweel, bool) + + def test_glyph_position_fields(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "A") + pos = gbuf.glyph_positions[0] + assert pos.x_advance > 0 + assert pos.y_advance == 0 + assert isinstance(pos.x_offset, int) + assert isinstance(pos.y_offset, int) + + +# --------------------------------------------------------------------------- +# Iteration / indexing +# --------------------------------------------------------------------------- + + +class TestIteration: + def test_iter_yields_pairs(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "Hi") + items = list(gbuf) + assert len(items) == 2 + for info, pos in items: + assert isinstance(info, GlyphInfo) + assert isinstance(pos, GlyphPosition) + + def test_indexing(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "AB") + first = gbuf[0] + last = gbuf[-1] + assert first[0].cluster == 0 + assert last[0].cluster == 1 + + def test_index_out_of_range(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "A") + with pytest.raises(IndexError): + gbuf[5] + + +# --------------------------------------------------------------------------- +# Serialize parity with shape() string function +# --------------------------------------------------------------------------- + + +class TestSerializeParity: + def test_matches_shape_string(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "Hello") + obj_result = gbuf.serialize(font).strip() + str_result = shape(PT_SANS, "Hello", "").strip() + assert obj_result == str_result + + def test_matches_shape_with_features(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "AB", features=[Feature("+kern")]) + obj_result = gbuf.serialize(font).strip() + str_result = shape(PT_SANS, "AB", "--features=+kern").strip() + assert obj_result == str_result + + +# --------------------------------------------------------------------------- +# Buffer consumption + recycling +# --------------------------------------------------------------------------- + + +class TestBufferConsumption: + def test_buffer_consumed_after_shape(self): + font = Font(PT_SANS) + buf = Buffer() + buf.add_str("Test") + buf.guess_segment_properties() + font.shape(buf) + with pytest.raises(ValueError, match="consumed"): + len(buf) + + def test_shape_twice_raises(self): + font = Font(PT_SANS) + buf = Buffer() + buf.add_str("Test") + buf.guess_segment_properties() + font.shape(buf) + with pytest.raises(ValueError, match="consumed"): + font.shape(buf) + + def test_shape_with_invalid_direction_raises(self): + font = Font(PT_SANS) + buf = Buffer() + buf.add_str("Test") # direction left at default Invalid + with pytest.raises(ValueError, match="direction"): + font.shape(buf) + + +class TestBufferRecycle: + def test_clear_returns_reusable_buffer(self): + font = Font(PT_SANS) + buf = Buffer() + buf.add_str("First") + buf.guess_segment_properties() + gbuf = font.shape(buf) + buf2 = gbuf.clear() + assert isinstance(buf2, Buffer) + assert len(buf2) == 0 + buf2.add_str("Second") + buf2.guess_segment_properties() + gbuf2 = font.shape(buf2) + assert len(gbuf2) == 6 + + def test_glyph_buffer_consumed_after_clear(self): + font = Font(PT_SANS) + buf = Buffer() + buf.add_str("X") + buf.guess_segment_properties() + gbuf = font.shape(buf) + gbuf.clear() + with pytest.raises(ValueError, match="consumed"): + len(gbuf) + + def test_clear_twice_raises(self): + font = Font(PT_SANS) + buf = Buffer() + buf.add_str("X") + buf.guess_segment_properties() + gbuf = font.shape(buf) + gbuf.clear() + with pytest.raises(ValueError, match="consumed"): + gbuf.clear() + + +# --------------------------------------------------------------------------- +# Features +# --------------------------------------------------------------------------- + + +class TestFeatures: + def test_features_list(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "AB", features=[Feature("+kern")]) + assert len(gbuf) == 2 + + def test_features_string(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "AB", features="+kern,-liga") + assert len(gbuf) == 2 + + def test_features_empty_list(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "AB", features=[]) + assert len(gbuf) == 2 + + def test_features_invalid_string(self): + font = Font(PT_SANS) + buf = Buffer() + buf.add_str("AB") + with pytest.raises(ValueError, match="invalid feature"): + font.shape(buf, features="bogus[junk") + + def test_features_wrong_type(self): + font = Font(PT_SANS) + buf = Buffer() + buf.add_str("AB") + with pytest.raises(TypeError): + font.shape(buf, features=42) + + +# --------------------------------------------------------------------------- +# Variations +# --------------------------------------------------------------------------- + + +class TestVariations: + def test_set_variations_list(self): + font = Font(OPEN_SANS) + font.set_variations([Variation("wght=700")]) + + def test_set_variations_string(self): + font = Font(OPEN_SANS) + font.set_variations("wght=500,wdth=80") + + def test_variations_affect_output(self): + # OpenSans.subset1 is variable; weight should change x_advance. + baseline = Font(OPEN_SANS) + bold = Font(OPEN_SANS) + bold.set_variations([Variation("wght=900")]) + + a = _shape_str(baseline, "e").glyph_positions[0].x_advance + b = _shape_str(bold, "e").glyph_positions[0].x_advance + assert a != b + + def test_set_variations_empty_resets(self): + font = Font(OPEN_SANS) + font.set_variations([Variation("wght=900")]) + bold_adv = _shape_str(font, "e").glyph_positions[0].x_advance + + font.set_variations([]) + default_adv = _shape_str(font, "e").glyph_positions[0].x_advance + assert default_adv != bold_adv + + def test_set_variations_invalid(self): + font = Font(OPEN_SANS) + with pytest.raises(ValueError, match="invalid variation"): + font.set_variations("garbage~~~") + + def test_set_variations_wrong_type(self): + font = Font(OPEN_SANS) + with pytest.raises(TypeError): + font.set_variations(42) + + +# --------------------------------------------------------------------------- +# Point size +# --------------------------------------------------------------------------- + + +class TestPointSize: + def test_set_then_clear(self): + font = Font(PT_SANS) + font.set_point_size(12.0) + font.set_point_size(None) + + def test_shape_runs_with_point_size(self): + font = Font(PT_SANS) + font.set_point_size(24.0) + gbuf = _shape_str(font, "Hi") + assert len(gbuf) == 2 + + +# --------------------------------------------------------------------------- +# Repr +# --------------------------------------------------------------------------- + + +class TestRepr: + def test_font_repr(self): + font = Font(PT_SANS) + assert "Font" in repr(font) + + def test_glyph_buffer_repr(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "Hi") + r = repr(gbuf) + assert "GlyphBuffer" in r and "2" in r + + def test_glyph_buffer_repr_after_clear(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "X") + gbuf.clear() + assert "consumed" in repr(gbuf) + + def test_glyph_info_repr(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "A") + assert "GlyphInfo" in repr(gbuf.glyph_infos[0]) + + def test_glyph_position_repr(self): + font = Font(PT_SANS) + gbuf = _shape_str(font, "A") + assert "GlyphPosition" in repr(gbuf.glyph_positions[0]) From 970305b6c1d0176a952cc47d12307c410cb1bfac Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Sat, 25 Apr 2026 23:08:20 -0700 Subject: [PATCH 15/19] Enhance glyph information tests and add byte comparison test - Updated `test_glyph_info_fields` to assert additional properties of glyph information, including cluster values for multiple glyphs. - Introduced `test_from_bytes_matches_from_path` to verify that font data loaded from bytes matches that loaded from a file path. - Adjusted error handling in `test_features_wrong_type` to ensure proper feature validation. --- tests/test_font.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/tests/test_font.py b/tests/test_font.py index e3eb65a..f0b42fa 100644 --- a/tests/test_font.py +++ b/tests/test_font.py @@ -79,12 +79,14 @@ def test_glyph_infos_and_positions(self): def test_glyph_info_fields(self): font = Font(PT_SANS) gbuf = _shape_str(font, "AB") - info = gbuf.glyph_infos[0] - assert isinstance(info.glyph_id, int) and info.glyph_id > 0 - assert info.cluster == 0 - assert isinstance(info.unsafe_to_break, bool) - assert isinstance(info.unsafe_to_concat, bool) - assert isinstance(info.safe_to_insert_tatweel, bool) + infos = gbuf.glyph_infos + assert isinstance(infos[0].glyph_id, int) and infos[0].glyph_id > 0 + assert infos[0].cluster == 0 + assert infos[1].cluster == 1 + for info in infos: + assert isinstance(info.unsafe_to_break, bool) + assert isinstance(info.unsafe_to_concat, bool) + assert isinstance(info.safe_to_insert_tatweel, bool) def test_glyph_position_fields(self): font = Font(PT_SANS) @@ -146,6 +148,14 @@ def test_matches_shape_with_features(self): str_result = shape(PT_SANS, "AB", "--features=+kern").strip() assert obj_result == str_result + def test_from_bytes_matches_from_path(self): + font_path = Font(PT_SANS) + with open(PT_SANS, "rb") as f: + font_bytes = Font.from_bytes(f.read()) + a = _shape_str(font_path, "Hello").serialize(font_path).strip() + b = _shape_str(font_bytes, "Hello").serialize(font_bytes).strip() + assert a == b + # --------------------------------------------------------------------------- # Buffer consumption + recycling @@ -241,7 +251,7 @@ def test_features_invalid_string(self): buf = Buffer() buf.add_str("AB") with pytest.raises(ValueError, match="invalid feature"): - font.shape(buf, features="bogus[junk") + font.shape(buf, features="=") def test_features_wrong_type(self): font = Font(PT_SANS) From bebc63491e2f8812b53c58e370b722844e0be4b2 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Wed, 29 Apr 2026 22:04:20 -0700 Subject: [PATCH 16/19] Add PEP 561 type stubs and expand README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship `__init__.pyi` covering every public type and function with property signatures, exception notes, and `Sequence[Feature] | str | None` unions that match the runtime accepted shapes. Add an empty `py.typed` marker so type checkers pick up the stubs from an installed wheel — maturin already includes both files from `python-source` without further config. Suppress pyright on the five negative-type test lines that intentionally pass wrong types to verify TypeError. Rewrite the README around installation, the two-tier API (high-level `shape()` and the `Font` / `Buffer` / `GlyphBuffer` object API), variable fonts, buffer recycling, and the error taxonomy. --- README.md | 122 ++++++++++++++ python/pyharfrust/__init__.pyi | 290 +++++++++++++++++++++++++++++++++ python/pyharfrust/py.typed | 0 tests/test_buffer.py | 6 +- tests/test_font.py | 4 +- 5 files changed, 417 insertions(+), 5 deletions(-) create mode 100644 python/pyharfrust/__init__.pyi create mode 100644 python/pyharfrust/py.typed diff --git a/README.md b/README.md index b6f5d90..4b67ad7 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,128 @@ HarfBuzz is the industry-standard text shaping engine used by Firefox, Chrome, A - **Pythonic interface** — string-based construction for configuration types (`Direction("rtl")`, `Feature("+kern")`, `Variation("wght=700")`), iteration over glyph results, and clear error messages. - **HarfBuzz test compatibility** — ability to run against HarfBuzz's `.tests` regression format, enabling direct comparison between the two engines. +## Installation + +Wheels are not yet published. To build from source you need a Rust toolchain (>= 1.85) and Python (>= 3.11): + +```bash +git clone https://github.com/hasanzakeri/harfrust-python.git +cd harfrust-python +pip install maturin +maturin develop --release +``` + +The package is imported as `pyharfrust`: + +```python +import pyharfrust +print(pyharfrust.__version__) +``` + +PEP 561 type stubs (`__init__.pyi`, `py.typed`) ship with the package, so editors and type checkers see the full API. + +## Quick start + +Two ways to shape a string. Both produce the same output. + +### High-level `shape()` function + +```python +from pyharfrust import shape + +result = shape("path/to/font.ttf", "Hello World", "") +# "[H=0+733|e=1+598|l=2+336|l=3+336|o=4+631|space=5+272|W=6+871|o=7+631|r=8+380|l=9+336|d=10+629]" +``` + +The third argument accepts the same flags as the `hb-shape` CLI: + +```python +shape("font.ttf", "AB", "--features=+kern,-liga --direction=ltr") +``` + +### Object API + +For repeated shaping, font configuration, or access to per-glyph metadata, use the object API: + +```python +from pyharfrust import Buffer, Feature, Font + +font = Font("path/to/font.ttf") + +buf = Buffer() +buf.add_str("Hello World") +buf.guess_segment_properties() # infers direction/script/language + +glyphs = font.shape(buf, features=[Feature("+kern")]) + +for info, pos in glyphs: + print(f"glyph={info.glyph_id} cluster={info.cluster} " + f"advance=({pos.x_advance},{pos.y_advance}) " + f"offset=({pos.x_offset},{pos.y_offset})") +``` + +The `serialize()` method produces the same string format as the high-level `shape()` function: + +```python +print(glyphs.serialize(font)) +``` + +## Configuration types + +All configuration types accept either a string or their structured form. Strings parse with the same syntax as `hb-shape`: + +```python +from pyharfrust import Direction, Feature, Language, Script, Variation + +Direction("ltr") # or Direction.LTR +Script("Latn") # 4-letter ISO 15924 tag +Language("en-US") +Feature("+kern") # enable; "-liga" disables; "kern[3:5]=2" applies a range +Variation("wght=700") # variable-font axis setting +``` + +## Variable fonts + +```python +from pyharfrust import Font, Variation + +font = Font("variable.ttf") +font.set_variations([Variation("wght=700"), Variation("wdth=85")]) +# or +font.set_variations("wght=700,wdth=85") + +# Reset to defaults: +font.set_variations([]) +``` + +## Buffer recycling + +Buffers are consumed by `shape()`. Recycle them via `GlyphBuffer.clear()`: + +```python +from pyharfrust import Buffer, Font + +font = Font("font.ttf") +buf = Buffer() +buf.add_str("First") +buf.guess_segment_properties() +glyphs = font.shape(buf) + +# Reuse the same allocation for a new shaping call: +buf = glyphs.clear() +buf.add_str("Second") +buf.guess_segment_properties() +glyphs = font.shape(buf) +``` + +Reusing a consumed `Buffer` (or `GlyphBuffer`) raises `ValueError`. + +## Errors + +- `RuntimeError` — font cannot be loaded or parsed. +- `ValueError` — invalid string input (`Direction("xyz")`, `Feature("=")`), unset buffer direction at shape time, or use of an already-consumed buffer. +- `TypeError` — wrong argument types (e.g. assigning a string to `Buffer.direction`). + ## Technical Approach - **PyO3 + maturin** — the standard modern toolchain for building Rust extensions for Python. diff --git a/python/pyharfrust/__init__.pyi b/python/pyharfrust/__init__.pyi new file mode 100644 index 0000000..a20bebc --- /dev/null +++ b/python/pyharfrust/__init__.pyi @@ -0,0 +1,290 @@ +from collections.abc import Iterator, Sequence +from typing import ClassVar, final + +__version__: str + +# --------------------------------------------------------------------------- +# Value types +# --------------------------------------------------------------------------- + +@final +class Direction: + """Text direction: left-to-right, right-to-left, top-to-bottom, bottom-to-top.""" + + LTR: ClassVar[Direction] + RTL: ClassVar[Direction] + TTB: ClassVar[Direction] + BTT: ClassVar[Direction] + + def __init__(self, s: str) -> None: + """Parse a direction string ("ltr", "rtl", "ttb", "btt"; case-insensitive). + + Raises ``ValueError`` on an unknown string. + """ + + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __repr__(self) -> str: ... + def __str__(self) -> str: ... + +@final +class Script: + """An ISO 15924 script tag (e.g. "Latn", "Arab", "Deva").""" + + def __init__(self, s: str) -> None: + """Construct from a 4-letter ISO 15924 tag. + + Raises ``ValueError`` if the input is not 4 ASCII letters. + """ + + @property + def tag(self) -> str: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __repr__(self) -> str: ... + def __str__(self) -> str: ... + +@final +class Language: + """A BCP 47 language tag (e.g. "en", "ar", "en-US").""" + + def __init__(self, s: str) -> None: + """Parse a BCP 47 language tag. Raises ``ValueError`` on empty input.""" + + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __repr__(self) -> str: ... + def __str__(self) -> str: ... + +@final +class Feature: + """An OpenType feature setting (e.g. "+kern", "-liga", "kern[3:5]=2").""" + + def __init__(self, s: str) -> None: + """Parse a feature string. Raises ``ValueError`` on invalid syntax.""" + + @property + def tag(self) -> str: ... + @property + def value(self) -> int: ... + @property + def start(self) -> int: ... + @property + def end(self) -> int: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __repr__(self) -> str: ... + def __str__(self) -> str: ... + +@final +class Variation: + """A font variation axis setting (e.g. "wght=700", "wdth=85.5"). + + Not hashable: variation values are floats. + """ + + def __init__(self, s: str) -> None: + """Parse a variation string. Raises ``ValueError`` on invalid syntax.""" + + @property + def tag(self) -> str: ... + @property + def value(self) -> float: ... + def __eq__(self, other: object) -> bool: ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __repr__(self) -> str: ... + def __str__(self) -> str: ... + +# --------------------------------------------------------------------------- +# Buffer +# --------------------------------------------------------------------------- + +@final +class Buffer: + """Mutable text buffer used as input to shaping. + + A ``Buffer`` is consumed when passed to :meth:`Font.shape` — any further + use raises ``ValueError``. Reuse a buffer by recycling it through + :meth:`GlyphBuffer.clear`. + """ + + def __init__(self) -> None: ... + def __len__(self) -> int: ... + def __repr__(self) -> str: ... + def add_str(self, s: str) -> None: + """Append a Unicode string to the buffer.""" + + def add(self, codepoint: int, cluster: int = 0) -> None: + """Append a single Unicode codepoint with an explicit cluster value.""" + + def clear(self) -> None: + """Drop all codepoints and reset segment properties.""" + + def reset_clusters(self) -> None: + """Reset cluster values to be sequential (0, 1, 2, ...).""" + + def guess_segment_properties(self) -> None: + """Guess direction, script, and language from the buffer contents.""" + + def reserve(self, size: int) -> bool: + """Reserve capacity for at least ``size`` items.""" + + def set_pre_context(self, s: str) -> None: + """Set context preceding the buffer for shaping decisions.""" + + def set_post_context(self, s: str) -> None: + """Set context following the buffer for shaping decisions.""" + + def set_not_found_variation_selector_glyph(self, glyph: int) -> None: + """Set the glyph emitted for a variation selector with no match.""" + + @property + def direction(self) -> Direction: ... + @direction.setter + def direction(self, value: Direction) -> None: ... + @property + def script(self) -> Script: ... + @script.setter + def script(self, value: Script) -> None: ... + @property + def language(self) -> Language | None: ... + @language.setter + def language(self, value: Language) -> None: ... + +# --------------------------------------------------------------------------- +# Glyphs +# --------------------------------------------------------------------------- + +@final +class GlyphInfo: + """Read-only information about a shaped glyph. + + Snapshot value type: properties reflect the underlying glyph at the time + the info was retrieved from a :class:`GlyphBuffer`. + """ + + @property + def glyph_id(self) -> int: ... + @property + def cluster(self) -> int: ... + @property + def unsafe_to_break(self) -> bool: ... + @property + def unsafe_to_concat(self) -> bool: ... + @property + def safe_to_insert_tatweel(self) -> bool: ... + def __repr__(self) -> str: ... + +@final +class GlyphPosition: + """Read-only positioning information for a shaped glyph (font units).""" + + @property + def x_advance(self) -> int: ... + @property + def y_advance(self) -> int: ... + @property + def x_offset(self) -> int: ... + @property + def y_offset(self) -> int: ... + def __repr__(self) -> str: ... + +@final +class GlyphBuffer: + """Output of :meth:`Font.shape`: a sequence of glyphs and their positions. + + Iterating yields ``(GlyphInfo, GlyphPosition)`` pairs. The buffer is + consumed by :meth:`clear`, which returns a recycled :class:`Buffer`. + """ + + def __len__(self) -> int: ... + def __getitem__(self, index: int) -> tuple[GlyphInfo, GlyphPosition]: ... + def __iter__(self) -> Iterator[tuple[GlyphInfo, GlyphPosition]]: ... + def __repr__(self) -> str: ... + @property + def glyph_infos(self) -> list[GlyphInfo]: ... + @property + def glyph_positions(self) -> list[GlyphPosition]: ... + def clear(self) -> Buffer: + """Consume this glyph buffer and return its underlying buffer for reuse.""" + + def serialize(self, font: Font) -> str: + """Format the shaped glyphs as a string matching ``shape()`` output.""" + +# --------------------------------------------------------------------------- +# Font +# --------------------------------------------------------------------------- + +@final +class Font: + """A loaded font face used for shaping. + + Owns the font bytes and shaping data. Variations and point size can be + adjusted between shape calls. + """ + + def __init__(self, path: str, face_index: int = 0) -> None: + """Load a font from the filesystem. + + Raises ``RuntimeError`` if the file cannot be read or parsed. + """ + + @staticmethod + def from_bytes(data: bytes, face_index: int = 0) -> Font: + """Load a font from in-memory bytes. + + Raises ``RuntimeError`` if the data cannot be parsed as a font. + """ + + @property + def face_index(self) -> int: ... + @property + def units_per_em(self) -> int: ... + def set_variations(self, variations: Sequence[Variation] | str) -> None: + """Set the active variation axes. An empty sequence resets to defaults. + + Raises ``ValueError`` if a string contains an unparseable variation, + or ``TypeError`` if the argument is neither a string nor a sequence + of :class:`Variation` objects. + """ + + def set_point_size(self, size: float | None) -> None: + """Set the active point size, or clear it with ``None``.""" + + def shape( + self, + buffer: Buffer, + features: Sequence[Feature] | str | None = None, + ) -> GlyphBuffer: + """Shape the given buffer. + + Consumes the buffer; subsequent use of the same buffer raises + ``ValueError``. Direction must be set on the buffer (typically via + :meth:`Buffer.guess_segment_properties`) — otherwise raises + ``ValueError``. + + Raises ``ValueError`` on invalid feature syntax, or ``TypeError`` if + ``features`` is not a string, sequence of :class:`Feature`, or ``None``. + """ + + def __repr__(self) -> str: ... + +# --------------------------------------------------------------------------- +# High-level functions +# --------------------------------------------------------------------------- + +def shape(font_path: str, text: str, options: str = "") -> str: + """Shape ``text`` with the font at ``font_path`` and return the serialized output. + + ``options`` accepts the same flags as the ``hb-shape`` CLI (e.g. + ``"--direction=rtl"``, ``"--features=+kern,-liga"``). + + Raises ``RuntimeError`` if the font cannot be loaded or the options are + invalid. + """ + +def run_from_args(args: Sequence[str]) -> str: + """Run the equivalent of the ``hb-shape`` CLI with ``args``. + + Raises ``RuntimeError`` if argument parsing or shaping fails. + """ diff --git a/python/pyharfrust/py.typed b/python/pyharfrust/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_buffer.py b/tests/test_buffer.py index 656596b..c8a3a2b 100644 --- a/tests/test_buffer.py +++ b/tests/test_buffer.py @@ -106,17 +106,17 @@ def test_language_none_when_unset(self): def test_direction_setter_rejects_nondirection(self): buf = Buffer() with pytest.raises(TypeError): - buf.direction = "ltr" + buf.direction = "ltr" # pyright: ignore[reportAttributeAccessIssue] def test_script_setter_rejects_nonscript(self): buf = Buffer() with pytest.raises(TypeError): - buf.script = "Latn" + buf.script = "Latn" # pyright: ignore[reportAttributeAccessIssue] def test_language_setter_rejects_nonlanguage(self): buf = Buffer() with pytest.raises(TypeError): - buf.language = "en" + buf.language = "en" # pyright: ignore[reportAttributeAccessIssue] # --------------------------------------------------------------------------- diff --git a/tests/test_font.py b/tests/test_font.py index f0b42fa..a57cf99 100644 --- a/tests/test_font.py +++ b/tests/test_font.py @@ -258,7 +258,7 @@ def test_features_wrong_type(self): buf = Buffer() buf.add_str("AB") with pytest.raises(TypeError): - font.shape(buf, features=42) + font.shape(buf, features=42) # pyright: ignore[reportArgumentType] # --------------------------------------------------------------------------- @@ -302,7 +302,7 @@ def test_set_variations_invalid(self): def test_set_variations_wrong_type(self): font = Font(OPEN_SANS) with pytest.raises(TypeError): - font.set_variations(42) + font.set_variations(42) # pyright: ignore[reportArgumentType] # --------------------------------------------------------------------------- From 8f2f7ce4bcee3d74dcc28f1657eccc35bf095bd2 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Thu, 30 Apr 2026 00:11:09 -0700 Subject: [PATCH 17/19] Update README.md to enhance CLI usage documentation and clarify GlyphBuffer behavior. Added example for `run_from_args(argv)` to demonstrate full CLI parity with `hb-shape`. Updated description of `GlyphBuffer.clear()` to specify its consumption behavior and error handling. --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b67ad7..d75d5e9 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,13 @@ The third argument accepts the same flags as the `hb-shape` CLI: shape("font.ttf", "AB", "--features=+kern,-liga --direction=ltr") ``` +For full CLI parity, `run_from_args(argv)` accepts the same argument list as `hb-shape` — `argv[0]` is the program name, the rest are flags: + +```python +from pyharfrust import run_from_args +run_from_args(["hb-shape", "--font-file=font.ttf", "--features=+kern", "Hello"]) +``` + ### Object API For repeated shaping, font configuration, or access to per-glyph metadata, use the object API: @@ -129,7 +136,7 @@ buf.guess_segment_properties() glyphs = font.shape(buf) ``` -Reusing a consumed `Buffer` (or `GlyphBuffer`) raises `ValueError`. +`GlyphBuffer.clear()` consumes the glyph buffer: any further access to the original `glyphs` instance — including a second `clear()` — raises `ValueError`. The same applies to a `Buffer` once it has been passed to `shape()`. ## Errors From a70555bb70cc873269b422f0afe32a43df7191d3 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Thu, 30 Apr 2026 01:27:24 -0700 Subject: [PATCH 18/19] Add release pipeline gated on tip-of-release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New `release.yml`: tag-push (`v*`) builds wheels for Linux x86_64/aarch64, macOS x86_64/aarch64, and Windows x64 across CPython 3.11/3.12/3.13, plus an sdist. The `publish` job uploads to PyPI via OIDC trusted publishing and uses `skip-existing` so re-runs are idempotent. The `gate` job rejects any tag whose SHA isn't the current tip of `release`. Combined with branch protection requiring `ci` to pass on `release`, that turns "tip of release" into a hard guarantee that full CI (Tier 1 + Tier 2) ran on the exact SHA being released — so the release workflow doesn't re-run tests itself. Add a `release-source-gate` job to `ci.yml` that fails any PR targeting `release` whose head ref isn't `main`. GitHub branch protection has no "restrict source branch" setting, so we enforce it as a required check. `RELEASING.md` documents guarantees (branch protection rules, PyPI trusted publisher) and the per-release procedure, plus recovery from a lost tag race. --- .github/workflows/ci.yml | 15 ++++ .github/workflows/release.yml | 143 ++++++++++++++++++++++++++++++++++ RELEASING.md | 57 ++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 RELEASING.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 941998d..96bbd54 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,21 @@ concurrency: cancel-in-progress: true jobs: + release-source-gate: + # PRs targeting `release` are only allowed from `main`. GitHub branch + # protection has no "restrict source branch" setting, so we enforce it + # here as a required check. Make this a required status check on the + # `release` branch so the PR can't merge without it. + if: github.event_name == 'pull_request' && github.base_ref == 'release' + runs-on: ubuntu-latest + steps: + - name: PRs to release must come from main + run: | + if [ "$GITHUB_HEAD_REF" != "main" ]; then + echo "::error::PRs to release must come from main, got '$GITHUB_HEAD_REF'." + exit 1 + fi + ci: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b850f6e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,143 @@ +name: Release + +on: + push: + tags: ['v*'] + workflow_dispatch: + +permissions: + contents: read + +jobs: + gate: + # Enforce that the tag points to the current tip of `release`. The + # release-branch CI (ci.yml) runs Tier 1 + Tier 2 on every push to + # release, so tip-of-release is the SHA on which full CI passed. + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Tag must point to the tip of release + run: | + git fetch origin release --depth=1 + expected=$(git rev-parse FETCH_HEAD) + if [ "$GITHUB_SHA" != "$expected" ]; then + echo "::error::Tag $GITHUB_REF_NAME ($GITHUB_SHA) is not the tip of release ($expected). See RELEASING.md." + exit 1 + fi + + linux: + runs-on: ubuntu-latest + needs: gate + strategy: + fail-fast: false + matrix: + target: [x86_64, aarch64] + steps: + - uses: actions/checkout@v6 + + - uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + manylinux: auto + args: --release --out dist -i python3.11 -i python3.12 -i python3.13 + + - uses: actions/upload-artifact@v4 + with: + name: wheels-linux-${{ matrix.target }} + path: dist + + macos: + needs: gate + strategy: + fail-fast: false + matrix: + target: [x86_64, aarch64] + python-version: ['3.11', '3.12', '3.13'] + include: + - target: x86_64 + runner: macos-13 + - target: aarch64 + runner: macos-14 + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist -i python${{ matrix.python-version }} + + - uses: actions/upload-artifact@v4 + with: + name: wheels-macos-${{ matrix.target }}-py${{ matrix.python-version }} + path: dist + + windows: + runs-on: windows-latest + needs: gate + strategy: + fail-fast: false + matrix: + target: [x64] + python-version: ['3.11', '3.12', '3.13'] + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + architecture: ${{ matrix.target }} + + - uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist + + - uses: actions/upload-artifact@v4 + with: + name: wheels-windows-${{ matrix.target }}-py${{ matrix.python-version }} + path: dist + + sdist: + runs-on: ubuntu-latest + needs: gate + steps: + - uses: actions/checkout@v6 + + - uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + + - uses: actions/upload-artifact@v4 + with: + name: sdist + path: dist + + publish: + name: Publish to PyPI + runs-on: ubuntu-latest + needs: [linux, macos, windows, sdist] + if: startsWith(github.ref, 'refs/tags/v') + environment: + name: pypi + url: https://pypi.org/p/pyharfrust + permissions: + # Required for PyPI trusted publishing (OIDC). Configure the publisher + # at https://pypi.org/manage/account/publishing/ before tagging a release. + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist + skip-existing: true diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..3e49cb2 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,57 @@ +# Releasing + +Releases are cut from the `release` branch. The `ci.yml` workflow runs full +CI (Tier 1 + Tier 2 against the pinned harfrust corpus) on every push to +`release`. The `release.yml` workflow gates on tag-equals-release-tip, so +the SHA being released has demonstrably passed full CI. + +## Guarantees in place + +- **Branch protection on `release`** — direct pushes blocked, all commits + must come through a PR with the `ci` and `release-source-gate` checks + passing, branch must be up to date with base, no force pushes. +- **`release-source-gate` job (`ci.yml`)** — rejects any PR targeting + `release` whose head ref isn't `main`. Required check. +- **`gate` job (`release.yml`)** — rejects any tag whose SHA isn't the + current tip of `release`. Together with the protections above, this + means tip-of-release = "full CI + Tier 2 passed on this exact SHA." +- **PyPI trusted publishing** — the `publish` job uploads via OIDC, no + API tokens in GitHub secrets. + +## Release procedure + +1. Open a PR from `main` to `release`. Wait for CI (including Tier 2) to + go green. Merge. +2. Tag the tip of `release`: + ```bash + git checkout release + git pull --ff-only + git tag -a vX.Y.Z -m "vX.Y.Z" + git push origin vX.Y.Z + ``` +3. The tag push triggers `release.yml`. The `gate` job verifies + `vX.Y.Z` points to the current tip of `release`. If it doesn't, the + whole workflow fails — fix and re-tag. +4. Wheel jobs build for Linux (x86_64, aarch64), macOS (x86_64, aarch64), + Windows (x64) across CPython 3.11/3.12/3.13. The `sdist` job builds + the source distribution. +5. The `publish` job uploads everything to PyPI via OIDC. `skip-existing` + makes re-runs idempotent. + +## Common issues + +**Gate fails: "tag is not the tip of release."** Someone else merged to +`release` between when you fetched and when you pushed the tag, or you +tagged the wrong branch. Delete the tag locally and on origin, pull +`release`, retag. + +```bash +git tag -d vX.Y.Z +git push origin :refs/tags/vX.Y.Z +git checkout release && git pull --ff-only +git tag -a vX.Y.Z -m "vX.Y.Z" && git push origin vX.Y.Z +``` + +**Publish fails on PyPI.** Most often the version in `pyproject.toml` +hasn't been bumped (PyPI rejects re-uploads of an existing version). +Bump the version on `main`, merge to `release`, retag. From 00a22c56edc037f64c3630b17d7deff99464ff65 Mon Sep 17 00:00:00 2001 From: Hasan Zakeri Date: Thu, 30 Apr 2026 03:15:53 -0700 Subject: [PATCH 19/19] Update pre-commit configuration and CI workflow - Modified `.pre-commit-config.yaml` to enforce checks on both `main` and `release` branches. - Updated `.github/workflows/ci.yml` to pin the `harfrust` repository reference to a specific commit for consistency in CI testing. --- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96bbd54..226b310 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,7 +84,7 @@ jobs: repository: harfbuzz/harfrust # Pinned to the 0.5.2 commit so the test corpus matches the hr-shape # version declared in Cargo.toml. Bump this alongside the dep. - ref: efdae31 + ref: efdae3142ab76a2f1524d72cff9e3dfdc5afd7ca path: harfrust-external - uses: dtolnay/rust-toolchain@stable diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 701441f..818965f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: - id: check-added-large-files - id: check-merge-conflict - id: no-commit-to-branch - args: [--branch, main] + args: [--branch, main, --branch, release] - repo: https://github.com/astral-sh/uv-pre-commit rev: 0.11.3