ManimCommunity / ManimCommunity/ManimPango

Uneven letter spacing in `Text` at small sizes

Open
#130 1 comment 0 reactions 0 assignees View on GitHub

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.

Image

## 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.