如何使matplotlib同时使用宋体和Times New Roman

温馨提示:点击页面下方以展开或折叠目录

摘要:一般论文要求中文宋体、西文Times New Roman,因此为了规范排版,有必要解决它。然而对于matplotlib的绘图网上并没有完美的解决方案,所谓的办法都是清一色的「黑体」,因此本文寻求了一种替代方案作为折中。

文章说明
文章说明:本文首发于2019-12-15@知乎,更新于2020-05-21@GitPages
另请参考:知乎:用Python的matplotlib画图,怎么保证xlabel中中文用宋体,英文用新罗马?
另请参考:知乎:Matplotlib 中英文及公式字体设置——【文章作者:cherichy@知乎
文章作者:如果我可以忘记
创建时间:2019-12-15
更新时间:2020-05-21

写在前面

写在前面:抱着对自己说话负责的态度:此题无解

但是,我们真的对他没辙儿了吗?非也。两种思路:①找到一种字体TimesSong.ttf;②寻找一种替代品,退而求其次。

作为一枚标准处女座,这个问题也困扰了我好久。一般论文中字体中文要求宋体、西文[英文+数字]要求Times New Roman,因此为了规范排版,有必要解决它。

说明:下文配置中的步骤尽量简写,默认为大家知道如何修改配置文件matplotlibrc,大家可以看看我在SegmentFault思否提问的帖子:SegmentFault问答:matplotlib字体配置问题

以下详谈。

不想看的直接拉到末尾>>>>>>>>>>>>>>>

思路一:找到一种字体TimesSong.ttf(随意取名)

这种思路是有根据的,因为经我测试(见下文),matplotlib只能同时支持一种字体,而宋体和Times New Roman是“不兼容”(描述不准,意会即可)的,我们能否将宋体和Times New Roman这两种字体合并为新的字体_TimesSong.ttf_呢?

获得TimesSong.ttf字体后,将其添加到D:\Python3.7.3\Lib\site-packages\matplotlib\mpl-data\fonts\ttf文件夹、写入matplotlibrc文件,保存后删除缓存C:\Users\鴻塵.matplotlib即可。

此法理论可行,但是我不知道怎么合并字体(据说违法,版权什么的…),有兴趣的小伙伴可以试试[其实我试过,但那个软件无法激活]。

思路二:寻找一种替代品:_STSong_

话不多说,先上图看看:
图片

代码如下:

1
2
3
>>> import matplotlib.pyplot as plt  
>>> plt.title(u'今天是2019.12.14,Saturday')
>>> plt.xlabel(r'LaTex公式$+\sum_{n=0}^{\infty}\frac{1}{(2n+1)^2}=\frac{\pi^2}{8}$')

不仅能中英混输,而且连LaTex公式都可以。

同样,人应该对自己说的话负责:

  • ①公式显示我修改过配置文件:mathtext.fontset : stix [备注: Should be ‘dejavusans’ (default), ‘dejavuserif’, ‘cm’ (Computer Modern), ‘stix’, ‘stixsans’ or ‘custom’];
  • ②公式中的西文字体可正常显示,因为stix字体集就是适配Times(可能是Times New Roman,记不清)字体的,但是中文不行,所以我在上图里中文是没有包含在美元符号里的,如果置于$$内将报错:Font ‘rm’ does not have a glyph for ‘\u5417’ [U+5417], substituting with a dummy symbol。理论上,可修改字体集为自定义custom然后修改每一个特定字体,但我偷懒直接改的整个字体集。

回到正文,图中可以看出,STSong字体还是比较符合我们的预期的,所谓“退而求其次”嘛,总比网上清一色的“SimHei”的敷衍式的不负责任的回答要好得多。

所以推荐大家采用和我相同的配置(见下)。

回答完毕,下文填坑。

1. 测试字体列表

由于我修改过配置文件matplotlibrc,所以说行数可能不准确,因此以官网A sample matplotlibrc file为准:matplotlib官方教程

文件169行说:

The font.family property has five values: xx, xx, xx, xx, and xx. Each of these font families has a default list of font names in decreasing order of priority associated with them. When text.usetex is False, font.family may also be one or more concrete font names.

翻译:font.family属性有五个值。这些字体系列中的每一个都有一个默认的字体名称列表,按照与其相关联的优先级的降序排列。当text.usetex属性为False时,font.family也可以是一个或多个具体的字体名称。

这段话告诉我们,指定font.family的属性值(一般为sans-serif<默认>或serif)后,就可以将209行font.serif或210行font.sans-serif对应的字体名默认列表default list of font names调整顺序(优先级priority),使得优先显示前面的字体。但是这段话也误导了我:是不是意味着当第一个字体失败时自动使用第二种字体呢?

插句题外话,根据百度百科的解释,Sans-serif是专指西文中没有衬线的字体,与汉字字体中的黑体相对应。

原来如此,这么看就能对应上了。

首先,将198行的_#font.family : sans-serif_改为_font.family : serif_(没有行内代码很难受),根据百科的话中文应为衬线字体

然后,将209行_#font.serif : DejaVu Serif, Bitstream Vera Serif, Computer Modern Roman, New Century Schoolbook, Century Schoolbook L, Utopia, ITC Bookman, Bookman, Nimbus Roman No9 L, Times New Roman, Times, Palatino, Charter, serif_(看到没,本来就有Times New Roman的)改为_font.serif :_ STSong, SimSun, Times New Roman, Times,_DejaVu Serif, Bitstream Vera Serif, Computer Modern Roman, New Century Schoolbook, Century Schoolbook L, Utopia, ITC Bookman, Bookman, Nimbus Roman No9 L, Palatino, Charter, serif_,调整顺序嘛,STSong优先级最高,应为它是唯一能最大程度满足要求的字体。

注意:修改配置文件是一劳永逸的办法,启动Python即可用,如果在Python里以

1
>>> plt.rcParams['font.serif'] = ['STSong']

的方式修改,只能影响当前环境,退出Python即失效。

经我测试,无法使两种字体同时生效,但优先级确实可以解决部分问题。

  • 单个字体:①Times New Roman;②Times;③SimSun;④STSong

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    >>> plt.rcParams['font.serif'] = ['Times New Roman']  
    >>> plt.title(u'字体Python2019')
    >>> # 不报错,但中文显示异常(显示为□□),西文正常且符合要求

    >>> plt.rcParams['font.serif'] = ['Times']
    >>> plt.title(u'字体Python2019')
    >>> # 报错:findfont: Font family ['serif'] not found. Falling back to DejaVu Sans.
    >>> # 中英文显示均异常,不属于上述四种之一(Times字体?不认识,有点像加粗的黑体)
    >>> # 第二次也没有报错,不知为何

    >>> plt.rcParams['font.serif'] = ['SimSun']
    >>> plt.title(u'字体Python2019')
    >>> # 不报错,中英文皆为宋体

    >>> plt.rcParams['font.serif'] = ['STSong']
    >>> plt.title(u'字体Python2019')
    >>> # 不报错,中英文皆为STSong
    >>> # 此为最佳方案
  • 两种字体:①Times New Roman+SimSun;②Times New Roman+STSong;③Times+SimSun;④Times+STSong。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    >>> plt.rcParams['font.serif'] = ['Times New Roman','SimSun']  
    >>> plt.title(u'字体Python2019')
    >>> # 第一次报错:RuntimeWarning: Glyph 20307 missing from current font.
    >>> # 第二次没有报错,两次显示效果一样
    >>> # 但中文显示异常(显示为□□),西文正常为Times New Roman

    >>> plt.rcParams['font.serif'] = ['Times New Roman','STSong']
    >>> plt.title(u'字体Python2019')
    >>> # 不报错,中文异常、西文正常

    >>> plt.rcParams['font.serif'] = ['Times','SimSun']
    >>> plt.title(u'字体Python2019')
    >>> # 不报错,中英文皆为宋体

    >>> plt.rcParams['font.serif'] = ['Times','STSong']
    >>> plt.title(u'字体Python2019')
    >>> # 不报错,中英文皆为STSong

    由上测试可知,不论是两种字体还是一种字体,好像都取决于列表中的第一个。但是上述两种字体的后两种测试又似乎说明了“优先级”的问题:单[‘Times’]时报错了一次,双[‘Times’,’SimSun/STSong’]时按第二个。能否理解为Times失效继而采用SimSun/STSong呢?

2.LaTex公式

文件264行说:

The following settings allow you to select the fonts in math mode. They map from a TeX font name to a fontconfig font pattern. These settings are only used if mathtext.fontset is ‘custom’.

翻译:以下设置允许你在数学模式中选择字体,它们从TeX字体名称映射到fontconfig字体模式。这些设置仅在mathtext.fontset为“自定义”时使用

这段话告诉我们,最好别乱改以下这几个属性(含义参考LaTeX 各种命令,符号):

cal=手写体;rm=罗马体;tt=打字机字体;it=意大利斜体;bf=正粗体;sf=无衬线字体。

1
2
3
4
5
6
#mathtext.cal : cursive  
#mathtext.rm : sans
#mathtext.tt : monospace
#mathtext.it : sans:italic
#mathtext.bf : sans:bold
#mathtext.sf : sans

若非必要,请勿更改,直接将fontset改为stix即可。

3.其他

上文中的STSong字体即华文宋体,有知友有没有一种组合字体,中文是宋体,英文是times new roman? - 知乎用户的回答说:

华文宋体、方正书宋、方正新书宋、思源宋体、Adobe宋体,这些都是原生搭配好的。

经我在Word里测试,与“宋体+Times New Roman”最接近的只有华文宋体(注意:由于电脑没有安装方正宋体、方正新书宋和思源宋体,故以黑体代替并填充绿色;另Adobe宋体名称为Adobe宋体StdL)。

除上述修改外,文件329行的axes.unicode_minus属性应改为False以正常显示符号。

4.总结

  1. 退出Python,删除用户目录下的缓存文件(C:\Users\鴻塵.matplotlib整个文件夹);
  2. 修改配置文件matplotlibrc(D:\Python3.7.3\Lib\site-packages\matplotlib\mpl-data\matplotlibrc);
  3. 第198行:原文_#font.family : sans-serif_,修改_font.family : serif_
  4. 第209行:原文_#font.serif : DejaVu Serif, Bitstream Vera Serif, Computer Modern Roman, New Century Schoolbook, Century Schoolbook L, Utopia, ITC Bookman, Bookman, Nimbus Roman No9 L, Times New Roman, Times, Palatino, Charter, serif_,修改_font.serif :_ STSong, SimSun, Times New Roman, Times, _DejaVu Serif, Bitstream Vera Serif, Computer Modern Roman, New Century Schoolbook, Century Schoolbook L, Utopia, ITC Bookman, Bookman, Nimbus Roman No9 L, Palatino, Charter, serif_
  5. 第275行:原文_#mathtext.fontset : dejavusans_,修改_mathtext.fontset : stix_
  6. 第329行:原文_#axes.unicode_minus : True_,修改_axes.unicode_minus : False_
  7. 保存退出(上述四个位置修改时切记去掉注释符)。

说明:

  • 此方法修改是基于配置文件matplotlibrc的,一经修改永久生效且影响全局字体,但仍可在具体应用时plt.rcParams[‘xxx’]=[‘xxx’]动态修改;
  • 公式字体是独立的,不受其他设置影响,公式字体不支持中文(stix集不支持),应用时若有需要可以将中文放在美元号外,若中文必须在$$内,可尝试mathtext.fontset = custom后再修改类似于_mathtext.rm : sans_的相关属性(根据报错提示修改);
  • 除修改matplotlibrc文件外,我还拷了Times New Roman、宋体等好几种字体到D:\Python3.7.3\Lib\site-packages\matplotlib\mpl-data\fonts\ttf文件夹下,但似乎这一步不需要(未经考证);
  • matplotlib版本为3.1.0,Python版本为3.7.3,有知友说需要修改D:\Python3.7.3\Lib\site-packages\matplotlib\font_manager.py文件
  • 上述办法只算是蒙混过关,不算解决方案,后续若有更好的再来更新;
  • 知乎首答,比较混乱,有很多重复,大家能看懂就行。