Repository navigation
[Feature]: Expose ascent, descent, matrix and rendered_advance on TextChar #2926
Description
Problem Statement
TextChar in Rust carries ascent, descent, matrix and rendered_advance, but the Python bindings and the C API expose none of them.
Without them, bbox is the only vertical information about a glyph, and bbox always runs from the baseline up by the font size. Fonts with very different descents end up with identical boxes. Rotated text gets an axis-aligned box with no way to rebuild the glyph's actual orientation.
There's a second gap: when a font has no /Descent (or no FontDescriptor at all), the library falls back to the standard-14 metrics or to −0.35. A caller can't tell a real descent from that default.
With 0.3.78:
import pdf_oxide
b = pdf_oxide.DocumentBuilder()
page = b.letter_page().font("Helvetica", 10).at(72, 100).text("Ag")
page.done()
doc = pdf_oxide.PdfDocument.from_bytes(b.build())
g = doc.extract_chars(0)[1]
print([a for a in dir(g) if not a.startswith("_")])
# ['advance_width', 'bbox', 'char', 'color', 'font_name', 'font_size', 'font_weight',
# 'is_italic', 'is_monospace', 'mcid', 'origin_x', 'origin_y', 'rotation_degrees']
print(g.char, g.origin_y, g.bbox)
# g 100.0 (78.67, 100.0, 5.56, 10.0) <- box starts at the baseline; the descender of "g" is outside it
print(hasattr(g, "descent"), hasattr(g, "ascent"), hasattr(g, "matrix"), hasattr(g, "rendered_advance"))
# False False False False
Proposed Solution
Expose on TextChar in Python and through the C API (pdf_oxide_char_get_*):
ascentanddescent: the values already computed, in user space (scaled by the effective font size).font_ascentandfont_descent: the font's own/Ascentand/Descentas written in itsFontDescriptor(or the standard-14 metrics), as a fraction of the em, orNonewhen the font gives none and the library used its default.matrix: the full text rendering matrix (text matrix × CTM) for the glyph, six numbers.rendered_advance: the advance to the next glyph's origin, including character and word spacing.
Alternatives Considered
Re-parsing the font dictionaries with a second library to read /Descent. That doubles the parsing work, can disagree with pdf_oxide on which font a glyph used, and is still blind to the library's default when no descent is present. Rotation can be estimated from rotation_degrees, but without the matrix the glyph's actual corners, skew and direction can't be rebuilt.
Feature Area
Text Extraction
Priority
Would be helpful
Use Case
- Glyph boxes under other conventions: pdfminer/pdfplumber (descent to descent + font size), the ascent-to-descent band used by MuPDF.
- Baseline-relative layout: grouping glyphs of different fonts and sizes on one visual line.
- Real corners for rotated or skewed text, to place and group it correctly.
- Character and word spacing: knowing where glyph spacing came from (rendered_advance vs advance_width).
Example Usage (Optional)
for c in doc.extract_chars(0):
a, b, cc, d, e, f = c.matrix
bottom = c.origin_y + c.descent
top = bottom + c.font_size
print(c.char, c.font_descent, c.descent, c.rendered_advance, (bottom, top))Additional Context
Tested on pdf_oxide 0.3.78 (PyPI wheel, Python 3.12, Linux x86_64).
Checklist
- I have searched existing issues to make sure this is not a duplicate
- I have provided a clear description of the feature
- I understand a maintainer must accept this issue and agree on the approach before I write any implementation code
- I am willing to contribute to the implementation (optional)
Activity
yfedoseev commentedon Oct 18, 2026
Accepted for 0.3.79, approach as proposed:
ascentanddescent(already computed, user space) exposed.font_ascentandfont_descent: the font's own/Ascent//Descent(or standard-14 metrics) as a fraction of the em,Nonewhen the font gives none and the library used its default — so a caller can tell a real value from the fallback.matrix: the glyph's text rendering matrix (text matrix × CTM), six numbers.rendered_advance: the advance to the next glyph's origin, character and word spacing included.bboxkeeps its current convention.Scope: every binding. Rust already carries 1, 3 and 4; the C API gains
pdf_oxide_char_get_ascent/descent/font_ascent/font_descent/matrix/rendered_advance(header ininclude/), and every binding over it exposes them: Python, WASM/JS, Go, Java/Kotlin/Scala/Clojure (JNI), C#, C++, Dart, Elixir, Julia, Objective-C, PHP, R, Ruby, Swift, Zig. Values are tested in Rust with synthetic PDFs (a font with and without/Descent, a rotated run); each binding gets a surface test that the accessors exist and return the Rust values.yfedoseev commentedon Oct 25, 2026
Progress on release/v0.3.79 (not yet released): Python TextChar now has ascent, descent, rendered_advance and matrix, and the C API has pdf_oxide_char_get_ascent, _descent, _rendered_advance and _matrix (tests/test_ffi_a_character_gives_its_extent_advance_and_matrix.rs, python/tests/test_api_coverage.py). Still open: font_ascent and font_descent as the font declares them (None when the library used its default), which needs the font's declared metrics kept on FontInfo.