3b1b / manim

Animation engine for explanatory math videos
MIT License
61.92k stars 5.76k forks source link

`TexText` Bug: Some Chinese characters get hollowed #2146

Open osMrPigHead opened 1 month ago

osMrPigHead commented 1 month ago

English

Describe the bug

Some of the Chinese characters in TexText get hollowed, even when using SVGMoject with compiled SVG files (nothing wrong with SVG files). I thought it was a rendering bug of ManimGL.

Code:

from manimlib import *

class TestScene(Scene):
    def construct(self):
        self.add(TexText("TexText: 始终").to_edge(UP),
                 Text("Text: 始终").to_edge(DOWN))
        self.wait()

Wrong display or Error traceback:

Image_1721129240464

Additional context

Someone else (@起床王王王 in Manim Kindergarten's QQ group) found the same problem in Feb this year. He solved by changing typeface, but other characters can also get a display error in other typefaces on my computer. This seemed to have different effects on different computers. E.g. “边” and “代” got hollowed on @起床王王王's computer, but it worked well for me. And I got hollowed “始” and “终” in Song typeface, deformed “奇” in Kai typeface.

The picture shows @起床王王王's result.

86D97D238DC8AAF49E37F270A20FE243

中文

描述

TexText 中有些汉字显示为空心,哪怕把相应的 SVG 放到 SVGMobject 里去也是同样效果,但编译出的 SVG 没有任何异常,我觉得应该是 ManimGL 渲染的问题。

图片和源码见上文

附加信息

今年 2 月的时候 mk 群里面也有人(群昵称 @起床王王王)发现了同样问题,他当时是用更换字体的方式解决的,但我这里更换了字体就会有其他汉字显示异常。这个问题貌似在不同电脑上表现不同,在 @起床王王王 的电脑上“边”“代”有问题,但在我的电脑上显示正常,在我的电脑上用宋体会导致“始”“终”空心,用楷体会导致“奇”字形扭曲。

图(见上文)为 @起床王王王 的显示效果。

osMrPigHead commented 1 month ago

English

ShowCreation can visualize how the character is "drawed" by SVG paths. It shows that “始” and “终” gets their filling not in the border of strokes, but in the gap between the strokes, such as the space in “口”, a component of “始”.

So I tried changing the order of different closed path in the SVG file (in the attribute d of the path element, between one "Z" and another is a closed path). The process of ShowCreation sure changed, however as long as I don't delete the inner or outer frame of “口”, I still get the wrong result.

https://github.com/user-attachments/assets/60db7804-70a7-4be1-9347-af1ed1b841fe

中文

ShowCreation 展现了SVG中汉字的“绘制”过程,可以发现,在我电脑上只有边框而没有填充的汉字(“始”“终”)在 ShowCreation 的创建过程中被错误地填充了像“口”这样的笔画之间的区域。

所以我调换了一下 SVG 里的绘制顺序(path 元素 d 属性中两个“Z”之间的部分是一个闭合路径),看能不能借此改变 ManimGL 对汉字的渲染,改了之后 ShowCreation 的绘制顺序确实是变了,但只要“始”里面那个“口”还是完整的,渲染就不正常,去掉“口”的外边框或内边框才能让“始”的其他部分渲染正常。

视频见上文

osMrPigHead commented 1 month ago

English

Seems that the problem is caused by SVG fill-rule. ManimGL takes the nonzero fill-rule (see Understanding the SVG fill-rule Property). However, when both counterclockwise and clockwise curves exist in one SVG graph, ManimGL doesn't fill the area inside a clockwise curve even if the area isn't surrounded by a counterclockwise curve. And some characters got clockwise outer edges from dvisvgm, therefore they can't be filled.

Here's an example:

<!--a.svg-->
<?xml version='1.0' encoding='UTF-8'?>
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='16pt' height='16pt' viewBox='-4 -4 12 12'>
<defs>
<path id='test' d='M 0 0 L 0 7 L 7 7 L 7 0 L 0 0 Z M 1 1 L 6 1 L 6 6 L 1 6 L 1 1 Z M 2 2 L 5 2 L 5 5 L 2 5 L 2 2 Z M 3 3 L 3 4 L 4 4 L 4 3 L 3 3 Z'/>
</defs>
<g>
<use x='0' y='0' xlink:href='#test'/>
</g>
</svg>
# scene.py
from manimlib import *

class TestScene(Scene):
    def construct(self):
        self.play(ShowCreation(SVGMobject("a.svg", color="white"), run_time=10))

I created 4 squares in a.svg. The directions of them are (from the largest to the smallest) counterclockwise, clockwise, clockwise, counterclockwise.

This is what a.svg displays in Chrome: image But in ManimGL, it's filled incorrectly: image Notice that the background color in Chrome is white and that in ManimGL is black.

P.S. Interestingly, ShowCreation gave different drawing sequence to different directions in the SVG file. After I swapped the directions of the 2 smallest rectangles, ShowCreation replaced from-smallest-to-largest sequence with from-largest-to-smallest sequence.

中文

这个问题应该是 SVG 的 fill-rule 导致的,ManimGL 用的是 nonzero(参考 Understanding the SVG fill-rule Property)。但只要顺时针线和逆时针线存在于同一张图中,ManimGL 就不会填充处在顺时针线内的区域(哪怕这个区域的外面没有逆时针线),我这里没有填充的字外边缘都是顺时针方向,就出现了空心的问题。

例:

<!--a.svg-->
<?xml version='1.0' encoding='UTF-8'?>
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='16pt' height='16pt' viewBox='-4 -4 12 12'>
<defs>
<path id='test' d='M 0 0 L 0 7 L 7 7 L 7 0 L 0 0 Z M 1 1 L 6 1 L 6 6 L 1 6 L 1 1 Z M 2 2 L 5 2 L 5 5 L 2 5 L 2 2 Z M 3 3 L 3 4 L 4 4 L 4 3 L 3 3 Z'/>
</defs>
<g>
<use x='0' y='0' xlink:href='#test'/>
</g>
</svg>
# scene.py
from manimlib import *

class TestScene(Scene):
    def construct(self):
        self.play(ShowCreation(SVGMobject("a.svg", color="white"), run_time=10))

a.svg 里面有四个嵌套的绕行方向不同的正方形,从外到内的绕行方向分别是逆、顺、顺、逆时针。

Chrome 中显示效果是这样的: image ManimGL 中内部的两个正方形没被填充: image 两张图片颜色是相反的,Chrome 中是白色背景黑色前景,ManimGL 中是黑色背景白色前景

注:只改变绕行方向也会影响 ShowCreation 展示的绘制顺序,比如我这里 a.svg 的绘制顺序是从外到内,但如果把绕行方向改成逆、顺、逆、顺,绘制顺序就会变成从内到外。