破解字体反爬(二)

介绍

本篇文章描述通过程序解析字库文件中字体的方法。

背景知识

网页上使用的字库文件常用格式有:.ttf,.woff,.eot(认识 Iconfont 以及什么是 .eot、.woff、.ttf、.svg ——简书)。经过测试,以ttf文件格式为准编写的解析程序,其他格式一般也能解析。下面就以解析ttf格式作介绍。

TrueType字体

TrueType字体通常包含在单个TrueType字体文件中,其文件后缀为.TTF。OpenType字体是以类似 于TrueType字体的格式编码的POSTSCRIPT字体。OPENTYPE字体使用.OTF文件后缀。OPENTYPE还允许把多个OPENTYPE字体组合在一个文件中以利于数据共享。这些字体被称为TrueType字体集(TrueType collection),其文件后缀为.TTC

对于TrueType字体文件结构解析的帖子很多,我这里贴出一个格式比较清晰的。ttf文件结构解析.doc,这里对ttf文件结构解析.doc作简单概述。

一个ttf文件中包含了很多个表结构,大概有:

  • head 字体头 字体的全局信息
  • cmap 字符代码到图元的映射 把字符代码映射为图元索引
  • glyf 图元数据 图元轮廓定义以及网格调整指令
  • maxp 最大需求表 字体中所需内存分配情况的汇总数据
  • mmtx 水平规格 图元水平规格
  • loca 位置表索引 把元索引转换为图元的位置
  • name 命名表 版权说明、字体名、字体族名、风格名等等
  • hmtx 水平布局 字体水平布局星系:上高、下高、行间距、最大前进宽度、最小左支撑、最小右支撑
  • kerm 字距调整表 字距调整对的数组
  • post PostScript信息 所有图元的PostScript FontInfo目录项和PostScript名
  • PCLT PCL 5数据 HP PCL 5Printer Language 的字体信息:字体数、宽度、x高度、风格、记号集等等
  • OS/2 OS/2和Windows特有的规格 TrueType字体所需的规格集

其中,图元数据(glyf表)是TrueType字体的核心信息,因此通常它是最大的表。图元,全称为图形输出原语,也就是字体的图形数据。一个图元对应这个一个字体,一个图元有多条轮廓,一条轮廓有多个点。例如:一个字体文件中有字符“0”,那么“0”就对应一个图元,“0”的图元有两条轮廓线,一条是内部的轮廓,另一条是外部的轮廓。,外圈有21个点控制,内圈有11个点控制。这些点控制这轮廓的形状,但不是组成。因为TureType字体中的图元轮廓是用二阶Bezier曲线定义的,有三个点:一个曲线上的点,一个曲线外的点和另一个曲线上的点。多个连续的不在曲线上的点是允许的,但不是用来定义三阶或更高阶的Bezier曲线,而是为了减少控制点的数目。比如,对于on-off-off-on模式的四个点,会加入一个隐含的点使之成为on-off-on-off-on,因此定义的是两段二阶Bezier曲线。如下图,会发现“0”外圈上会超过21个点,内圈上会超过11个。
在这里插入图片描述

fontTools 字体文件解析库

fontTools是python语言编写的字体文件解析程序。上面“ttf文件解析.doc”中使用C语言描述ttf文件内部表的结构如图元头部信息表:

typedef   struct   
{
WORD   numberOfContours;   //contor   number,negative   if   composite  图元轮廓线数量
FWord   xMin;       //Minimum   x   for   coordinate   data. // 图元位置x轴最小值
FWord   yMin;       //Minimum   y   for   coordinate   data. // 图元坐标y轴最小值
FWord   xMax;       //Maximum   x   for   coordinate   data. // 图元坐标x轴最大值
FWord   yMax;       //Maximum   y   for   coordinate   data. // 图元坐标y轴最大值
}GlyphHeader;

使用fontTools工具包会更方便,fontTools可以把字体文件中的表结构及数据以xml的格式保存到文件中。

from fontTools.ttLib import TTFont

#  ttfFile是ttf文件的路径或ttf文件流
font = TTFont(ttfFile)

# path可以是保存xml文件的路径或io流,tables参数指定保存的表,为空则保存全部表。
# keys方法可以查看所有表名['GlyphOrder', 'head', 'hhea', 'maxp', 'OS/2', 'hmtx', 'cmap', 'loca', 'glyf', 'name', 'post', 'GSUB']
font.saveXML(path, tables=['glyf'])

字符“0”以xml格式描述上面C语言结构体为:

<TTGlyph name="uniE575" xMin="0" yMin="-12" xMax="508" yMax="719"></TTGlyph>

fontTools.ttLib.TTFont.getBestCmp方法可以输出字符序列与图元名称之间的映射关系
在这里插入图片描述
因为fontTools会把数据转存为xml,所以我们需要解析xml

xml.dom.minidom xml代码解析库

python提供的xml代码解析库有很多,这里只是随便使用其中的一种

from xml.dom.minidom import parseString
doc = parseString(io.getvalue())
root = doc.documentElement

xml.dom.minidom解析出的xml-dom对象支持基本的dom操作,我们要使用的也就element.getElementsByTag,element.getElementByName,element.getAttribute

matplotlib 绘图工具包

matplotlib让我们能够把ttf中的图元绘制出来。预览一个matplotlib
在这里插入图片描述

pytesseract ocr识别库

pytesseract是python语言编写的tesseract-ocr开发工具包,需要安装tesseract-ocr才能正常使用。Tesseract:开源的OCR识别引擎,初期Tesseract引擎由HP实验室研发,后来贡献给了开源软件业,后经由Google进行改进,消除bug,优化,重新发布。
tesseract-ocr可以把我们绘制的字体图形识别成文字。pytesseract的使用方式很简单:

import pytesseract
from PIL import Image

image = Image.open(imagePath)
word = pytesseract.image_to_string(image, lang='chi_sim', config='--psm 10')

实现

我们的是方案是:通过fontTools解析ttf文件转存为xml,xml.dom.minidom解析xml,matplotlib绘制字体图形,pytesseract识别字体图形为字符串

Created with Raphaël 2.2.0 开始 FontTools解析字库文件,并转存为xml文件流 xml.dom.minidom解析xml matplotlib绘制字体图像 pytesseract识别字体图像,返回字符 结束

fontTools解析ttf文件转存为xml

from fontTools.ttLib import TTFont
from io import StringIO

class TTFParser:
	ttfFile = ""

	"""parser ttf font file"""
	def __init__(self):
		self.ttfFile = ""

	# 解析字库文件
	def parseFontFile(self, ttfFile):
		self.ttfFile = ttfFile
		try:
			self.font = TTFont(ttfFile)
		except Exception as e:
			raise Exception("a exception occurred during instantiatting TTFParser["+ttfFile+"]("+e.message+")")
			return;
		try:
			io = StringIO()
			self.font.saveXML(io, tables=['glyf'])
			return io
		except Exception as e:
			raise Exception("a exception occurred during saving TTFont["+ttfFile+"] to xml file("+e.message+")")

io的内容为:
在这里插入图片描述

xml.dom.minidom解析xml

from xml.dom.minidom import parseString

doc = parseString(io.getvalue())
root = doc.documentElement

matplotlib绘制字体图形

先贴出核心代码

class TTFPath(Path):
    """docstring for TTFPath."""
    def __init__(self, glyph):
        self.__verts = []
        self.__codes = []
        self.calculatePath(glyph)

        super(TTFPath, self).__init__(self.__verts, self.__codes)

    # 计算path
    def calculatePath(self, glyph):
        contours = glyph.getContours()
        for contour in contours:
            # 画笔移至第一个点
            self.__moveTo(contour.getPoint(0))
            # 遍历轮廓线上的点,从第二个点开始
            for index in range(1,contour.size()):
                point = contour.getPoint(index)
                if(point.onCurve):
                    # onCurve为True标识为贝塞尔曲线的起止点,该点在曲线上
                    self.__lineTo(point)
                    continue
                else:
                    # onCurve为False标识该点为控制点,该点不在曲线上
                    if contour.getPoint(index-1).onCurve:
                        self.__quadTo(point)
                    else:
                        # 连续两个点onCurve为控制点,添加两点中点作为起止点
                        self.__lineTo(self.__mindPoint(point, contour.getPoint(index-1)))
                        self.__quadTo(point)
            # 曲线绘制完成,画笔回到起点
            self.__moveTo(contour.getPoint(0))


    def __moveTo(self, point):
        self.__codes.append(Path.MOVETO)
        self.__verts.append((point.x, point.y))
    # path.append((Path.MOVETO, (point.x, point.y)))
    def __lineTo(self, point):
        self.__codes.append(Path.LINETO)
        self.__verts.append((point.x, point.y))
    # path.append((Path.LINETO, (point.x, point.y)))
    def __quadTo(self, point):
        self.__codes.append(Path.CURVE3)
        self.__verts.append((point.x, point.y))
    # path.append((Path.CURVE3, [(ctrlPoint.x, ctrlPoint.y), (point.x, point.y)]))
    def __closePath(self, point):
        self.__codes.append(Path.CLOSEPOLY)
        self.__verts.append((point.x, point.y))
    def __mindPoint(self, pointA, pointB):
        return Point(pointA.x+(pointB.x-pointA.x)/2, pointA.y+(pointB.y-pointA.y)/2)
class TTFRender:
    """docstring for TTFRender."""
    color = 'black'

    def __init__(self):
        self.fig, self.ax = plt.subplots()

    def draw(self, glyph):
        path = TTFPath(glyph)
        patch = PathPatch(path, facecolor=self.color)
        plt.cla()
        # plt.clf()
        self.ax.add_patch(patch)
        # 设置坐标系
        self.ax.grid()
        # self.ax.axis('equal')
        self.centralize(glyph.getXmin(),glyph.getXmax(),glyph.getYmin(),glyph.getYmax())
        # 隐藏坐标轴刻度
        plt.xticks([])
        plt.yticks([])
        plt.axis('off')

    # 使图像居中。应对逗号,句号,小数点等特殊字体
    def centralize(self, xMin,xMax,yMin,yMax):
        width = 640
        height = 480
        self.ax.axis(xmin=int(xMin-width/2),xmax=int(xMax+width/2),ymin=int(yMin-height/2),ymax=int(yMax+height/2))

    def getBufferImage(self, width=80, height=60):
        io = BytesIO()
        self.fig.savefig(io)
        image = Image.open(io)
        return image.resize((width,height))

pytesseract识别字体图形为字符串

# encoding: utf-8

import pytesseract
from PIL import Image

class Ocr:
	"""docstring for Ocr"""
	def __init__(self, image = None):
		self.image = image
		self.lang = 'chi_sim'
		self.psm = '10'
	def setImagePath(self, imagePath):
		self.image = Image.open(imagePath)
	def  setImageFile(self, imageFile):
		self.image = imageFile
	def setLang(self, lang):
		self.lang = lang
	def getWords(self):
		return pytesseract.image_to_string(self.image, lang=self.lang, config='--psm '+self.psm)
	def recoginze(self, image):
		return pytesseract.image_to_string(image, lang=self.lang, config='--psm '+self.psm)
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值