harfbuzz / harfbuzz/harfrust

Plan for supporting hb_ot_layout_* baselines in the C FFI

Open
#467 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
301
Forks
26
Avg merge
10h 11m
Merged PRs (30d)
43

Description

# Implement the `hb_ot_layout_*` baseline C API against harfrust

## Goal

Provide the five baseline entry points directly from Rust. `read-fonts`
covers the table reading and the script mapping; what remains is the C
surface, the scaling, and the fallback synthesis.

## Background

`read-fonts` gained a `BASE` reading layer (branch `tables/base`). Three
facts shape the design:

- **`BASE` carries its own item variation store**, unlike `MATH`. A
`VariationIndex` on a coordinate genuinely resolves, so a caller supplies
only normalized coordinates and the table finds the store itself.
- **A coordinate takes at most one adjustment**, and the two kinds land in
different units. A variation delta is in design units and is folded into
the value; a `Device` table adjusts by whole pixels and is returned
separately, because scaling it needs upem.
- **Language is not part of a baseline lookup.** `BASE` varies its minimum
and maximum extents by language, but not its baselines. HarfBuzz's
`Axis::get_baseline` takes a `language_tag` and never reads it.

## What read-fonts provides

```rust
enum BaseAxis { Horizontal, Vertical } // writing direction
struct BaseValue { value: F48Dot16, delta_px: i32 }

Base::axis(BaseAxis) -> Option
Axis::base_script(script_tag) -> Option // falls back to DFLT
Axis::baseline_coord(baseline_tag, script_tag) -> Option
BaseCoord::device() -> Option // format 3 only

BaseInstance::new(base)
BaseInstance::with_coords(base, coords: &[F2Dot14]) // Deref to Base
.baseline(baseline_tag, axis, script_tag) -> Option
.baseline_for_ppem(baseline_tag, axis, script_tag, ppem) -> Option
.coord(&BaseCoord) / .coord_for_ppem(&BaseCoord, ppem)

mod baseline_tags { ROMAN, HANGING, IDEO_FACE_BOTTOM_OR_LEFT, ... } // all 8
fn horizontal_baseline_tag_for_script(script: Tag) -> Tag
```

## Entry points

| function | status |
| --- | --- |
| `hb_ot_layout_get_horizontal_baseline_tag_for_script` | `horizontal_baseline_tag_for_script`, done |
| `hb_ot_layout_get_baseline` | `BaseInstance::baseline_for_ppem` plus scaling |
| `hb_ot_layout_get_baseline2` | same, taking `hb_script_t` / `hb_language_t` |
| `hb_ot_layout_get_baseline_with_fallback` | needs synthesis, see below |
| `hb_ot_layout_get_baseline_with_fallback2` | same |
| `hb_ot_layout_baseline_tag_t` | `baseline_tags` constants |

## Conversions the FFI owns

- **A coordinate at a size**, on the axis the coordinate lies on:

```
em_scale(value) + delta_px * scale / ppem
```

A baseline is a position across the writing direction, so horizontal text
uses the y scale and y ppem, and vertical text uses x. Pass the matching
`ppem` to `baseline_for_ppem`.
- **Direction to axis.** `HB_DIRECTION_IS_VERTICAL(direction)` selects
`BaseAxis::Vertical`.
- **`None` means not found**, which is `false` from the C function with
`*coord` untouched.

## The fallback variants

`hb_ot_layout_get_baseline_with_fallback` synthesizes a baseline when the
table places none. This needs font extents and glyph metrics, so it belongs
above `read-fonts` rather than in it. HarfBuzz's switch is the reference,
and its comment "keep in sync with
`hb_ot_layout_get_horizontal_baseline_tag_for_script`" matters: the script
list in that function and the fallback arms are two halves of one decision.

## Gotchas

1. **Script codes, not OpenType tags.**
`horizontal_baseline_tag_for_script` takes a Unicode script code —
`Deva`, `Hani` — while `baseline` takes an OpenType script tag. They are
different namespaces and the FFI sits between them.
2. **`Hang` is not `hang`.** The script `Hang` is Hangul and takes the
ideographic baseline; the baseline tag `hang` is the hanging baseline.
Same letters, different case, opposite answers.
3. **Sorted order is load bearing.** Both the baseline tag list and the
script record list are binary searched, matching HarfBuzz. A font that
does not keep them in the alphabetical order the spec requires hides the
entries that are out of place, in both implementations alike.
4. **`DFLT` fallback happens once.** If a script has its own record but
places no baselines, that is the answer — the lookup does not then fall
back to `DFLT`.
5. **`BaseCoordFormat2` is a TODO in HarfBuzz.** It names a glyph and a
contour point to take the position from, and nothing reads them: the
plain coordinate is returned, exactly as format 1. `read-fonts` matches
this. Implementing it properly would be a divergence, not a fix.

## Not covered

`Axis::get_min_max` and the `BaseLangSysRecord` machinery are the only part
of `BASE` that uses language. HarfBuzz has the code but exposes no public
function for it, so nothing needs it yet. It is also the only reason a
baseline lookup would ever take a language.

## Suggested order

1. Font plumbing: a `BaseInstance` from the font's coordinates, plus scale
and ppem per axis.
2. `hb_ot_layout_get_horizontal_baseline_tag_for_script` — pure data, no
font, good first landing.
3. `hb_ot_layout_get_baseline` and `_baseline2`.
4. The two `_with_fallback` forms, keeping the synthesis in sync with the
script list.

## Testing

Differential against HarfBuzz: for each font, every registered baseline tag
across both axes and a spread of scripts, at several ppem, with and without
variation coordinates.

Worth covering: a font with a real `BASE` table and a `DFLT` script (most
CJK and Indic fonts), a variable font whose `BASE` carries an item variation
store, and a font with device tables on its baseline coordinates to exercise
the ppem path. Also check a script with no record at all, to confirm the
`DFLT` fallback, and one with a record but no `BaseValues`, to confirm the
fallback does not then re-trigger.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reading the listed read-fonts BASE APIs and the existing FFI entry points, then land the pure horizontal-baseline script mapping before the font-backed lookups. Use HarfBuzz as the reference for scaling, fallback synthesis, and script handling. Done means the baseline entry points and tag constants work across axes, ppem, variation coordinates, and fallback cases, with differential coverage against HarfBuzz.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, rust
Domain
api, backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.