在 windows 上想用 PingFang 字体看看效果怎么办?直接拖过来却不好用?你需要的是重新提取。
1 、 PingFang 字体相关
苹方(英语:PingFang)是苹果公司委托威锋数位为 iOS 9 及 OS X El Capitan 所开发的专有字体家族,属于无衬线黑体。此字体最早作为 iOS 9 的默认中文字体于 2015 年 6 月 8 日在旧金山的 WWDC 2015 上公布,支持简体中文、繁体中文、日文和韩文,其中繁体中文包括香港字形和台湾字形两个版本,且提供了七种字重,取代了从 iOS 4 以来一脉相传的华文系黑体。
https://zh.wikipedia.org/wiki/%E8%8B%B9%E6%96%B9
请注意:PingFang 字体版权为 Apple 公司,非授权情况下使用即为违规
2 、前提准备
本文优先使用 Linux 系统,Python2.7 版本
3 、提取 PingFang 字体
3.1 、找到 PingFang 字体
在 MacOS 系统中,你需要到下面这个目录中找到 PingFang(需要 OS X 10.11 El Capitan 或更高版本的系統)
/System/Library/Fonts/
找到后将文件通过任意方法复制到 Linux 系统上。
3.2 、解构 PingFang 字体文件
首先你需要下载一个别人制作的 Python 脚本 [ 链接 ]
脚本详细内容如下
# otc2otf.py v1.0 Jan 14 2014 __help__ = """ AFDKOPython otc2otf.py [-w] <font.ttc> example: AFDKOPython otc2otf.py -w temp.ttc Report tags and offsets. Optionally extract all OpenType fonts from the parent OpenType Collection font. -w Optional. Extract and write all sfnt fonts as separate font files. """ import sys import os import struct class OTCError(TypeError): pass class FontEntry: def __init__(self, sfntType, searchRange, entrySelector, rangeShift): self.sfntType = sfntType self.searchRange = searchRange self.entrySelector = entrySelector self.rangeShift = rangeShift self.tableList = [] self.fileName = "temp.tmp.otf" self.psName = "PSNameUndefined" def append(self, tableEntry): self.tableList.append(tableEntry) class TableEntry: def __init__(self, tag, checkSum, length): self.tag = tag self.checksum = checkSum self.length = length self.data = None self.offset = None self.isPreferred = False ttcHeaderFormat = ">4sLL" ttcHeaderFormatDoc = """ > # big endian TTCTag: 4s # "ttcf" Version: L # 0x00010000 or 0x00020000 numFonts: L # number of fonts # OffsetTable[numFonts]: L # array with offsets from beginning of file # ulDsigTag: L # version 2.0 only # ulDsigLength: L # version 2.0 only # ulDsigOffset: L # version 2.0 only """ ttcHeaderSize = struct.calcsize(ttcHeaderFormat) offsetFormat = ">L" offsetSize = struct.calcsize(">L") sfntDirectoryFormat = ">4sHHHH" sfntDirectoryFormatDoc = """ > # big endian sfntVersion: 4s numTables: H # number of tables searchRange: H # (max2 <= numTables)*16 entrySelector: H # log2(max2 <= numTables) rangeShift: H # numTables*16-searchRange """ sfntDirectorySize = struct.calcsize(sfntDirectoryFormat) sfntDirectoryEntryFormat = ">4sLLL" sfntDirectoryEntryFormatDoc = """ > # big endian tag: 4s checkSum: L offset: L length: L """ sfntDirectoryEntrySize = struct.calcsize(sfntDirectoryEntryFormat) nameRecordFormat = ">HHHHHH" nameRecordFormatDoc = """ > # big endian platformID: H platEncID: H langID: H nameID: H length: H offset: H """ nameRecordSize = struct.calcsize(nameRecordFormat) def parseArgs(args): writeOTF = False fontPath = None argn = len(args) i = 0 while i < argn: arg = args[i] i += 1 if arg[0] != '-': fontPath = arg elif arg == "-w": writeOTF = True elif (arg == "-u") or (arg == "-h"): print __help__ raise OTCError() else: raise OTCError("Unknown option '%s'." % (arg)) if fontPath == None: raise OTCError("You must specify an OpenType Collection font as the input file..") allOK = True if not os.path.exists(fontPath): raise OTCError("Cannot find '%s'." % (fontPath)) fp = file(fontPath, "rb") data = fp.read(4) fp.close() if data != 'ttcf': raise OTCErrr("File is not an OpenType Collection file: '%s'." % (fontPath)) return fontPath, writeOTF def getPSName(data): format, n, stringOffset = struct.unpack(">HHH", data[:6]) expectedStringOffset = 6 + n * nameRecordSize if stringOffset != expectedStringOffset: # XXX we need a warn function print "Warning: 'name' table stringOffset incorrect.", print "Expected: %s; Actual: %s" % (expectedStringOffset, stringOffset) stringData = data[stringOffset:] data = data[6:] psName = "PSNameUndefined" for i in range(n): if len(data) < 12: # compensate for buggy font break platformID, platEncID, langID, nameID, length, offset = struct.unpack(nameRecordFormat, data[:nameRecordSize]) data = data[nameRecordSize:] if not ((platformID, platEncID, langID, nameID) == (1, 0, 0, 6)): continue psName = stringData[offset:offset+length] assert len(psName) == length return psName def readFontFile(fontOffset, data): sfntType, numTables, searchRange, entrySelector, rangeShift = struct.unpack(sfntDirectoryFormat, data[fontOffset:fontOffset + sfntDirectorySize]) fontEntry = FontEntry(sfntType, searchRange, entrySelector, rangeShift) entryData = data[fontOffset + sfntDirectorySize:] i = 0 seenGlyf = False while i < numTables: tag, checksum, offset, length = struct.unpack(sfntDirectoryEntryFormat, entryData[:sfntDirectoryEntrySize]) tableEntry = TableEntry(tag, checksum, length) tableEntry.offset = offset tableEntry.data = data[offset:offset+length] fontEntry.tableList.append(tableEntry) if tag == "name": fontEntry.psName = getPSName(tableEntry.data) elif tag == "glyf": seenGlyf = True entryData = entryData[sfntDirectoryEntrySize:] i += 1 if seenGlyf: fontEntry.fileName = fontEntry.psName + ".ttf" else: fontEntry.fileName = fontEntry.psName + ".otf" print "%s" % (fontEntry.psName) for tableEntry in fontEntry.tableList: print "\t%s checksum: 0x%08X, offset: 0x%08X, length: 0x%08X" % (tableEntry.tag, tableEntry.checksum, tableEntry.offset, tableEntry.length) return fontEntry def writeOTFFont(fontEntry): dataList = [] numTables = len(fontEntry.tableList) # Build the SFNT header data = struct.pack(sfntDirectoryFormat, fontEntry.sfntType, numTables, fontEntry.searchRange, fontEntry.entrySelector, fontEntry.rangeShift) dataList.append(data) fontOffset = sfntDirectorySize + numTables*sfntDirectoryEntrySize print fontOffset # Set the offsets in the tables. for tableEntry in fontEntry.tableList: tableEntry.offset = fontOffset fontOffset += tableEntry.length # build table entries in sfnt directory for tableEntry in fontEntry.tableList: tableData = struct.pack(sfntDirectoryEntryFormat, tableEntry.tag, tableEntry.checksum, tableEntry.offset, tableEntry.length) dataList.append(tableData) # add table data to font. for tableEntry in fontEntry.tableList: tableData = tableEntry.data dataList.append(tableData) fontData = "".join(dataList) fp = file(fontEntry.fileName, "wb") fp.write(fontData) fp.close() return def run(args): fontPath, writeOTF = parseArgs(args) print "Input font:", fontPath fp = file(fontPath, "rb") data = fp.read() fp.close() TTCTag, version, numFonts = struct.unpack(ttcHeaderFormat, data[:ttcHeaderSize]) offsetdata = data[ttcHeaderSize:] print "Version: %s. numFonts: %s." % (version, numFonts) fontList = [] i = 0 while i < numFonts: offset = struct.unpack(offsetFormat, offsetdata[:offsetSize])[0] print "font %s offset: %s/0x%08X." % (i, offset,offset), fontEntry = readFontFile(offset, data) fontList.append(fontEntry) offsetdata = offsetdata[offsetSize:] i += 1 if writeOTF: for fontEntry in fontList: writeOTFFont(fontEntry) print "Done" if __name__ == "__main__": try: run(sys.argv[1:]) except (OTCError), e: print e.message
将此文件保存到 Linux 系统中,并运行如下命令
python2.7 otc2otf.py -w PingFang.ttc
此时运行后,将解构 PingFang.ttc 并生成 18 个字体文件和一个字体简介文件(PSNameUndefined.otf)
之后我们用不到这个字体简介文件,在此处删掉即可。
操作完毕后我们即可得到 Linux 可以使用的大陆简体 SC,台湾繁体 TC,香港繁体 HK 三类字体文件
PingFangHK-Light.otf PingFangSC-Light.otf PingFangTC-Light.otf PingFangHK-Medium.otf PingFangSC-Medium.otf PingFangTC-Medium.otf PingFangHK-Regular.otf PingFangSC-Regular.otf PingFangTC-Regular.otf PingFangHK-Semibold.otf PingFangSC-Semibold.otf PingFangTC-Semibold.otf PingFangHK-Thin.otf PingFangSC-Thin.otf PingFangTC-Thin.otf PingFangHK-Ultralight.otf PingFangSC-Ultralight.otf PingFangTC-Ultralight.otf
3.3 、重置 Cmap 表
如果你这时候拿到这些字体文件,试图在 Windows 上安装时,会发现无法识别。
这是由于 PingFang 的 CMap 表仅有两个 platformID="0" 的子表(Unicode 平台的 CMap),但是没有 platformID="3" 的子表(Windows 平台的 CMap)而造成的。因此无法被 Windows 识别。
但是由于这两类 CMap 的编码方式完全相同,所以我们可以直接取巧替换掉 platformID="0" 为 platformID="3"
在替换之前,你需要安装 Fonttools 这个工具
yum install python-pip && pip install fonttools
安装好后你可以在字体文件目录中直接运行下面的命令,如果觉得一条条执行麻烦可以统一写在一个 .sh 文件中执行
# PingFangSC ttx -t cmap PingFangSC-Light.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangSC-Light.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangSC-Light.ttx ttx -b -m PingFangSC-Light.otf PingFangSC-Light.ttx ttx -t cmap PingFangSC-Medium.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangSC-Medium.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangSC-Medium.ttx ttx -b -m PingFangSC-Medium.otf PingFangSC-Medium.ttx ttx -t cmap PingFangSC-Regular.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangSC-Regular.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangSC-Regular.ttx ttx -b -m PingFangSC-Regular.otf PingFangSC-Regular.ttx ttx -t cmap PingFangSC-Semibold.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangSC-Semibold.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangSC-Semibold.ttx ttx -b -m PingFangSC-Semibold.otf PingFangSC-Semibold.ttx ttx -t cmap PingFangSC-Thin.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangSC-Thin.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangSC-Thin.ttx ttx -b -m PingFangSC-Thin.otf PingFangSC-Thin.ttx ttx -t cmap PingFangSC-Ultralight.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangSC-Ultralight.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangSC-Ultralight.ttx ttx -b -m PingFangSC-Ultralight.otf PingFangSC-Ultralight.ttx # PingFangTC ttx -t cmap PingFangTC-Light.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangTC-Light.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangTC-Light.ttx ttx -b -m PingFangTC-Light.otf PingFangTC-Light.ttx ttx -t cmap PingFangTC-Medium.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangTC-Medium.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangTC-Medium.ttx ttx -b -m PingFangTC-Medium.otf PingFangTC-Medium.ttx ttx -t cmap PingFangTC-Regular.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangTC-Regular.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangTC-Regular.ttx ttx -b -m PingFangTC-Regular.otf PingFangTC-Regular.ttx ttx -t cmap PingFangTC-Semibold.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangTC-Semibold.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangTC-Semibold.ttx ttx -b -m PingFangTC-Semibold.otf PingFangTC-Semibold.ttx ttx -t cmap PingFangTC-Thin.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangTC-Thin.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangTC-Thin.ttx ttx -b -m PingFangTC-Thin.otf PingFangTC-Thin.ttx ttx -t cmap PingFangTC-Ultralight.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangTC-Ultralight.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangTC-Ultralight.ttx ttx -b -m PingFangTC-Ultralight.otf PingFangTC-Ultralight.ttx # PingFangHK ttx -t cmap PingFangHK-Light.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangHK-Light.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangHK-Light.ttx ttx -b -m PingFangHK-Light.otf PingFangHK-Light.ttx ttx -t cmap PingFangHK-Medium.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangHK-Medium.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangHK-Medium.ttx ttx -b -m PingFangHK-Medium.otf PingFangHK-Medium.ttx ttx -t cmap PingFangHK-Regular.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangHK-Regular.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangHK-Regular.ttx ttx -b -m PingFangHK-Regular.otf PingFangHK-Regular.ttx ttx -t cmap PingFangHK-Semibold.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangHK-Semibold.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangHK-Semibold.ttx ttx -b -m PingFangHK-Semibold.otf PingFangHK-Semibold.ttx ttx -t cmap PingFangHK-Thin.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangHK-Thin.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangHK-Thin.ttx ttx -b -m PingFangHK-Thin.otf PingFangHK-Thin.ttx ttx -t cmap PingFangHK-Ultralight.otf sed -i '''s/platformID="0" platEncID="3"/platformID="3" platEncID="1"/g' PingFangHK-Ultralight.ttx sed -i '''s/platformID="0" platEncID="4"/platformID="3" platEncID="10"/g' PingFangHK-Ultralight.ttx ttx -b -m PingFangHK-Ultralight.otf PingFangHK-Ultralight.ttx
命令执行需要一段时间,运行完毕后你可以看到当前文件夹多了 ttx 文件 和带有 #1 后缀的 otf 文件
这些带有 #1 后缀的 otf 文件就是可以在 Windows 上安装的字体文件了
4 、参考文章
蘋方移植 [ 链接 ]
CMap 表相关修改技术简要指南 [ 链接 ]
介绍和构建 OpenType 集合(OTC)[ 链接 ]
关于使用 OS X 10.11 的新中文字体的注意事项 [ 链接 ]