在 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 的新中文字体的注意事项 [ 链接 ]