ManimCommunity / ManimCommunity/ManimPango
Uneven letter spacing in `Text` at small sizes
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 62
- Forks
- 22
- PR merge metrics
- No merged PRs in 30d
Description
Related to ManimCommunity/manim#2844, open since 2022 without a root cause.
## What's happening
Text spacing looks uneven, and the smaller the `font_size`, the worse it gets. I dug into it and it's two things stacking up:
- Manim renders the SVG at `font_size / 4.8`, so a `Text(font_size=22)` is laid out by Pango at about 4.6 pt, with glyphs 2 to 3 px wide.
- Pango rounds every glyph position to a whole pixel by default (`round_glyph_positions`, on since Pango 1.44), and ManimPango never turns it off. Half a pixel on a 2.5 px glyph is a 20 % spacing error.
That's why the usual workaround (render big, then `.scale()` down) works: same rounding, 4× smaller relative to the glyph.
## Repro
Four cases at font_size 10–16, hitting every code path that depends on glyph positions:
```python
from manim import *
class Edge(Scene):
def construct(self):
a = Text("the ghost line is indistinguishable", font_size=10,
t2c={"ghost": RED, "indistinguishable": YELLOW}).scale(3).to_edge(UP)
b = Text("filter efficiency, flux and inflow", font_size=14).scale(2).next_to(a, DOWN, buff=0.4)
b[0:6].set_color(GREEN)
c = Paragraph("If the Shannon condition is not met, the spectrum of the filtered signal",
"overlaps with the copy folded around Fe. No low-pass filter can",
"recover the original signal.", font_size=16).next_to(b, DOWN, buff=0.4)
d = MarkupText('ghost line and '
'aliasing', font_size=12).scale(2.5).next_to(c, DOWN, buff=0.4)
self.add(a, b, c, d)
```
`edge_before_after.png`, top = original build, bottom = patched. Spacing becomes regular in all four; glyph counts are unchanged (25 / 21 / 3 lines / 23), so `t2c`, slicing and the gradient still land on the same letters.
## Fix
Turn rounding off on the layout's context, in both `text2svg()` and `MarkupUtils.text2svg()`. Wrapped with `PANGO_VERSION_CHECK(1,44,0)` like `set_line_width` already is, so older Pango compiles to a no-op.
`pango.pxd`, in the existing `cdef extern from *` block:
```c
#if PANGO_VERSION_CHECK(1,44,0)
int set_round_glyph_positions(PangoLayout *layout, gboolean r)
{ pango_context_set_round_glyph_positions(pango_layout_get_context(layout), r); return 1; }
#else
int set_round_glyph_positions(PangoLayout *layout, gboolean r){ return 0; }
#endif
```
with the matching Cython declaration `int set_round_glyph_positions(PangoLayout* layout, bint r)`, and in `cmanimpango.pyx`:
```diff
layout = pango_cairo_create_layout(cr)
+ set_round_glyph_positions(layout, False)
```
```diff
layout = pango_cairo_create_layout(context)
+ set_round_glyph_positions(layout, False)
```
## Cost
None I could measure: 300 `text2svg` calls on an 80-char string take 0.30–0.32 s with or without the patch (Pango 1.52.1, Linux). It removes a rounding step, it doesn't add one.
Happy to open a PR if this looks right.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the Edge scene, then inspect pango.pxd and cmanimpango.pyx at text2svg() and MarkupUtils.text2svg(). Verify the change works across colored text, slicing, Paragraph, and MarkupText, preserves glyph counts, and compiles as a no-op with older Pango versions.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- computer-graphics
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100