What did you do?
Drew text with stroke_width using a CJK font. In glyphs containing small enclosed counters formed by thin, closely-spaced stems (e.g. 描, 画), the stroke inside those counters is much thinner than the outer stroke and the counter leaks the background colour.
from PIL import Image, ImageDraw, ImageFont
# Noto Sans JP (Google Fonts, OFL); also reproduces with Noto Sans CJK / other CJK fonts
font = ImageFont.truetype("NotoSansJP-Regular.otf", 100)
img = Image.new("RGB", (600, 200), "white")
ImageDraw.Draw(img).text(
(20, 40), "世描画国", font=font,
fill="blue", stroke_fill="black", stroke_width=12,
)
img.save("repro.png")
What did you expect to happen?
The stroke around the small inner counters to have a width consistent with the outer stroke.
What actually happened?
Inside the small cells, opposing strokes do not meet: the stroke is much thinner there (in places almost absent) and the background shows through, while the outer stroke is uniform.
Analysis
Since #8701 ("Only use outside border of stroke in text()"), ImageDraw.text() already renders the stroke with getmask2(..., stroke_filled=True), i.e. using only FreeType's outside border. The defect above is still present in this outside-border-only output (verified on Pillow 12.2.0 / FreeType 2.14.3), so it is the outside border itself that breaks down here — not the inside border that #8701 removed.
This appears to be exactly the case the FreeType maintainer anticipated in #8701:
The inside border is ill-defined [when] the stroker radius exceeds the stem width or the dot radius ... This could happen to the outside border too but much less frequently.
In CJK glyphs many thin stems sit close together, so the "stroker radius exceeds stem width" condition holds for the outside border in many places at once, making this frequent rather than rare.
This is a different failure mode from #8697: that issue was a gap between the fill and the stroke on i/j tittles (an inside-border problem, fixed by #8701). It does not affect glyphs like o whose counter is large enough that the opposing outside borders never collide. Here it is the stroke itself that is thin between opposing strokes inside small, tightly-bounded counters.
Versions
Pillow 12.2.0
Python 3.12.6
--- FREETYPE2 support ok, loaded 2.14.3
What did you do?
Drew text with
stroke_widthusing a CJK font. In glyphs containing small enclosed counters formed by thin, closely-spaced stems (e.g. 描, 画), the stroke inside those counters is much thinner than the outer stroke and the counter leaks the background colour.What did you expect to happen?
The stroke around the small inner counters to have a width consistent with the outer stroke.
What actually happened?
Inside the small cells, opposing strokes do not meet: the stroke is much thinner there (in places almost absent) and the background shows through, while the outer stroke is uniform.
Analysis
Since #8701 ("Only use outside border of stroke in text()"),
ImageDraw.text()already renders the stroke withgetmask2(..., stroke_filled=True), i.e. using only FreeType's outside border. The defect above is still present in this outside-border-only output (verified on Pillow 12.2.0 / FreeType 2.14.3), so it is the outside border itself that breaks down here — not the inside border that #8701 removed.This appears to be exactly the case the FreeType maintainer anticipated in #8701:
In CJK glyphs many thin stems sit close together, so the "stroker radius exceeds stem width" condition holds for the outside border in many places at once, making this frequent rather than rare.
This is a different failure mode from #8697: that issue was a gap between the fill and the stroke on
i/jtittles (an inside-border problem, fixed by #8701). It does not affect glyphs likeowhose counter is large enough that the opposing outside borders never collide. Here it is the stroke itself that is thin between opposing strokes inside small, tightly-bounded counters.Versions