如何使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文件
  • 上述办法只算是蒙混过关,不算解决方案,后续若有更好的再来更新;
  • 知乎首答,比较混乱,有很多重复,大家能看懂就行。