Metadata-Version: 2.4
Name: pyharfrust2
Version: 0.1.0
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Text Processing :: Fonts
License-File: LICENSE
Summary: Step-by-step shaping trace for HarfRust (per-lookup/per-stage), aligned with uharfbuzz output.
Keywords: shaping,harfbuzz,harfrust,opentype,typography,ligatures,text
Author: Kushim-Jiang
License-Expression: MIT
Requires-Python: >=3.11
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/Kushim-Jiang/pyharfrust2
Project-URL: Issues, https://github.com/Kushim-Jiang/pyharfrust2/issues
Project-URL: Repository, https://github.com/Kushim-Jiang/pyharfrust2

# pyharfrust2 — HarfRust Trace Binding

Step-by-step (per-lookup / per-stage) shaping trace for
[HarfRust](https://github.com/harfbuzz/harfrust) — the HarfBuzz Rust port —
exposed through a Python (PyO3) binding.

The trace output is aligned with HarfBuzz's buffer message output (as produced
by uharfbuzz), so consumers that already render HarfBuzz traces (e.g.
BabelMap's OpenType Test timeline) can render it without changes.

## Relationship to HarfRust

This repository is **built on top of**
[harfrust](https://github.com/harfbuzz/harfrust) — HarfBuzz's official pure-Rust
port — and does not reimplement any shaping. All glyph substitution and
positioning comes straight from HarfRust:

- [`crates/harfrust-fork`](crates/harfrust-fork) is a *snapshot* of upstream
  harfrust **v0.13.3** (which tracks HarfBuzz v14.3.1), vendored as a workspace
  member. The only additions are an optional `ShapeOptions::trace` callback, a
  `lookup_map_t.feature_tag` field (which upstream HarfBuzz already has), and
  `src/hb/trace.rs`. With no callback the fork behaves exactly like upstream.
- [`crates/hr-trace`](crates/hr-trace) is our thin layer: it runs HarfRust
  shaping with the trace callback installed and collects the per-step event
  sequence (`TraceStep { event, glyphs }`).
- [`crates/pyharfrust2`](crates/pyharfrust2) +
  [`python/pyharfrust2`](python/pyharfrust2) is the PyO3 Python binding
  exposing this as `pyharfrust2.shape_trace`.

Keeping the fork small and isolated means it can be refreshed from upstream and
the trace patch re-applied, or dropped entirely once upstream gains its own
trace support — `hr-trace`/`pyharfrust2` would then simply point at the
`harfrust` crate instead.

## Layout

```
Cargo.toml                        # workspace
crates/
  harfrust-fork/                  # upstream harfrust v0.13.3 + minimal trace hooks
  hr-trace/                       # thin wrapper: shape_trace() -> event sequence
  pyharfrust2/                  # PyO3 cdylib: Python binding
python/pyharfrust2/             # Python package (maturin layout)
tests/                            # Python tests + test font generator
pyproject.toml                    # maturin backend
```

The fork adds an optional trace callback to three shaping sites
(`ot_shape.rs`, `ot_layout.rs`, `ot_shape_normalize.rs`) plus a
`lookup_map_t.feature_tag` field (matching upstream HarfBuzz) and a
`ShapeOptions::trace` option. With no callback the behaviour is identical to
upstream and there is no overhead.

## Python API

```python
import pyharfrust2

stages = pyharfrust2.shape_trace(
    font_bytes,  # bytes of a TTF/OTF font
    "ffi",  # text to shape
    direction="auto",  # auto | ltr | rtl | ttb | btt
    script="",  # ISO 15924, e.g. "Arab"
    language="",
    features=None,  # "+kern,-liga" (hb-shape syntax)
    variations=None,  # "wght=400" (comma separated)
    face_index=0,
)

for stage in stages:
    print(stage["m"], stage["glyphs"])
```

Each stage:

```python
{
    "m": "start lookup 5 feature 'liga'",
    "glyphs": [
        {"g": gid, "cl": cluster, "dx": dx, "dy": dy, "ax": ax, "ay": ay, "flags": flags},
        ...,
    ],
    "depth": 0,
    "effective": True,
}
```

## Build & test

```bash
uv venv .venv --python 3.13
uv pip install --python .venv maturin uharfbuzz pytest
.venv/Scripts/maturin build --release       # produces a wheel
uv pip install --python .venv <wheel path>
.venv/Scripts/python -m pytest tests/
```

Rust side:

```bash
cargo test -p harfrust-fork --lib   # upstream tests still pass (no-trace unchanged)
cargo test -p hr-trace              # trace event model tests
cargo run -p hr-trace --example dump_trace -- ffi
```

## Platforms

Pure Rust with no system libraries. The Python wheel is built with
`abi3-py311` (`cp311-abi3`), so a single wheel per OS covers Python 3.11+.
CI builds and tests on Windows, Linux and macOS, and publishes a macOS
`universal2` (x86_64 + arm64) wheel. The `harfrust-fork` crate also compiles
without `std`:

```bash
cargo check -p harfrust-fork --no-default-features --features libm
```

## Quality gates

The same checks run in CI (`.github/workflows/build.yml`, `lint` job):

```bash
.venv/Scripts/ruff check . && .venv/Scripts/ruff format --check .   # Python lint/format
npx pyright@latest                                                   # Pylance type check
cargo fmt --check                                                    # Rust format
cargo clippy --workspace --all-targets -- -D warnings                # Rust lint
cargo test --workspace                                               # Rust tests
.venv/Scripts/python -m pytest tests/                                # Python tests
```

## Generate the test font

```bash
python tests/generate_test_font.py   # -> tests/data/test_liga.ttf
```

## License

MIT. The `harfrust-fork` crate is upstream harfrust (MIT) with a small,
upstreamable trace patch.

