Freetype字体引擎分析与指南(中文版翻译)

主要观看下文第 3个全部的步骤说明,就可以使用freetype来编程操作,即以下简要步骤:

  • 初始化库
  • 通过创建一个新的face对象来打开一个字体文件
  • 以点或者像素的形式选择一个字符大小
  • 装载一个字形(glyph)图像,并把它转换为位图
  • 渲染一个简单的字符串
  • 渲染一个宣传的字符串



1.FreeType字形约定


1.1基本印刷概念


1.1.1字体文件、格式和信息

     字体是一组可以被显示和打印的多样的字符映像, 在单个字体中共享一些共有的特性, 包括外表、风格、衬线等。按印刷领域的说法,它必须区别一个字体家族和多种字体外观,后者通常是从同样的模板而来,但是风格不同。例如, Palatino Regular 和 Palatino Italic是两种不同的外观,但是属于同样的家族 Palatino。
    单个字体术语根据上下文既可以指家族也可指外观。例如,大多文字处理器的用户用字体指不同的字体家族,然而,大多这些家族根据它们的格式会通过多个数据文件实现。对于 TrueType 来讲,通常是每个外观一个文件(arial.ttf对应Arial Regular外观,ariali.ttf对应Arial Italic 外观)这个文件也叫字体,但是实际上只是一个字体外观。
    数字字体是一个可以包含一个和多个字体外观的数据文件,它们每个都包含字符映像、字符度量,以及其他各种有关文本布局和特定字符编码的重要信息。对有些难用的格式,像 Adobe的Type1,一个字体外观由几个文件描述(一个包含字符映象,一个包含字符度量等) 。在这里我们忽略这种情况,只考虑一个外观一个文件的情况,不过在 FT2.0中,能够处理多文件字体。
    为了方便说明,一个包含多个外观的字体文件我们叫做字体集合,这种情况不多见,但是多数亚洲字体都是如此,它们会包含两种或多种表现形式的映像,例如横向和纵向布局。


1.1.2字符映象和图

    字符映象叫做字形,根据书写、用法和上下文,单个字符能够有多个不同的映象,即多个字形。多个字符也可以有一个字形(例如 Roman)。字符和字形之间的关系可能是非常复杂,本文不多述。而且,多数字体格式都使用不太难用的方案存储和访问字形。为了清晰的原因,当说明 FT时,保持下面的观念

  • 一个字体文件包含一组字形,每个字形可以存成位图、向量表示或其他结构(更可缩放的格式使用一种数学表示和控制数据 /程序的结合方式)。这些字形可以以任意顺序存在字体文件中,通常通过一个简单的字形索引访问。

  • 字体文件包含一个或多个表,叫做字符图,用来为某种字符编码将字符码转换成字形索引,例如 ASCII、Unicode、Big5等等。单个字体文件可能包含多个字符图, 例如大多 TrueType字体文件都会包含一个 Apple特定的字符图和 Unicode字符图,使它在 Mac和Windows平台都可以使用。


1.1.3字符和字体度量

    每个字符映象都关联多种度量,被用来在渲染文本时,描述如何放置和管理它们。在后面会有详述,它们和字形位置、光标步进和文本布局有关。它们在渲染一个文本串时计算文本流时非常重要。
    每个可缩放的字体格式也包含一些全局的度量,用概念单位表示,描述同一种外观的所有字形的一些特性,例如最大字形外框,字体的上行字符、下行字符和文本高度等。
    虽然这些度量也会存在于一些不可缩放格式,但它们只应用于一组指定字符维度和分辨率,并且通常用象素表示。



1.2字形轮廓


1.2.1象素、点和设备解析度

    当处理计算机图形程序时,指定象素的物理尺寸不是正方的。通常,输出设备是屏幕或打印机,在水平和垂直方向都有多种分辨率,当渲染文本是要注意这些情况。
    定义设备的分辨率通常使用用 dpi(每英寸点 (dot)数)表示的两个数,例如,一个打印机的分辨率为300x600dpi表示在水平方向,每英寸有 300 个象素,在垂直方向有 600个象素。一个典型的计算机显示器根据它的大小,分辨率不同( 15’’和17’’显示器对 640x480象素大小不同),当然图形模式分辨率也不一样。
    所以,文本的大小通常用点( point)表示,而不是用设备特定的象素。点是一种简单的物理单位,在数字印刷中,一点等于 1/72英寸。例如,大多罗马书籍使用 10到14点大小印刷文字内容。
    可以用点数大小来计算象素数,公式如下: 象素数 = 点数*分辨率/72
    分辨率用dpi表示,因为水平和垂直分辨率可以不同,单个点数通常定义不同象素文本宽度和高度。



1.2.2向量表示

    字体轮廓的源格式是一组封闭的路径,叫做轮廓线。每个轮廓线划定字形的外部或内部区域,它们可以是线段或是 Bezier曲线。
    曲线通过控制点定义, 根据字体格式,可以是二次(conic Beziers)或三次(cubic Beziers)多项式。在文献中,conic Bezier通常称为 quadratic Beziers。因此,轮廓中每个点都有一个标志表示它的类型是一般还是控制点,缩放这些点将缩放整个轮廓。
    每个字形最初的轮廓点放置在一个不可分割单元的网格中,点通常在字体文件中以 16位整型网格坐标存储,网格的原点在 (0,0),它的范围是-16384到-16383(虽然有的格式如 Type1使用浮点型,但为简便起见,我们约定用整型分析)。
    网格的方向和传统数学二维平面一致, x轴从左到右, y轴从下到上。
    在创建字形轮廓时,一个字体设计者使用一个假想的正方形,叫做 EM正方形。他可以想象成一个画字符的平面。正方形的大小,即它边长的网格单元是很重要的,原因是 :

  • 它是用来将轮廓缩放到指定文本尺寸的参考, 例如在300x300dpi中的12pt大小对应 12*300/72=50象素。从网格单元缩放到象素可以使用下面的公式
    象素数 =点数 ×分辨率/72
    象素坐标= 网格坐标*象素数/EM大小
  • EM尺寸越大,可以达到更大的分辨率,例如一个极端的例子,一个 4单元的EM,只有25个点位置,显然不够,通常 TrueType字体之用 2048单元的EM;Type1 PostScript字体有一个固定 1000网格单元的EM,但是点坐标可以用浮点值表示。

    注意,字形可以自由超出 EM正方形。网格单元通常交错字体单元或 EM单元。上边的象素数并不是指实际字符的大小,而是 EM正方形显示的大小,所以不同字体,虽然同样大小,但是它们的高度可能不同。


1.2.3Hinting和位图渲染

    存储在一个字体文件中的轮廓叫“主”轮廓,它的点坐标用字体单元表示,在它被转换成一个位图时,它必须缩放至指定大小。这通过一个简单的转换完成,但是总会产生一些不想要的副作用,例如像字母 E和H,它们主干的宽度和高度会不相同。
    所以,优秀的字形渲染过程在缩放 “点”是,需要通过一个网格对齐 (grid-fitting)的操作(通常叫hinting),将它们对齐到目标设备的象素网格。这主要目的之一是为了确保整个字体中,重要的宽度和高度能够一致。例如对于字符 I和T来说,它们那个垂直笔划要保持同样象素宽度。另外,它的目的还有管理如 stem和overshoot的特性,这在小象素字体会引起一些问题。
    有若干种方式来处理网格对齐,多数可缩放格式中,每种字形轮廓都有一些控制数据和程序。

  • 显式网格对齐
    TrueType格式定义了一个基于栈的虚拟机( VM),可以借助多于 200中操作码(大多是几何操作)来编写程序,每个字形都由一个轮廓和一个控制程序组成, 后者可以处理实际的网格对齐, 他由字体设计者定义。采用显式的方式,质量上 --对小字体有很好的结果,这对屏幕显示非常重要;速度上 --如果程序很复杂,解释字节码很慢;一致性上 --所有渲染器产生同样的字形位图; 大小上--字形程序会很长;技术难度上 --编写优秀的hinting程序非常难,没有好的工具支持。
  • 隐式网格对齐(也叫 hinting)
    Type1格式有一个更简单的方式,每个字形由一个轮廓以及若干叫 hints的片断组成,后者用来描述字形的某些重要特性, 例如主干的存在、 某些宽度匀称性等诸如此类。 没有多少种 hint,要看渲染器如何解释 hint来产生一个对齐的轮廓。大小: Hint通常比显式字形程序小的多;质量:小字体不好,最后结合反走样;速度:网格对齐会非常快 不一致:不同渲染器结果不同,甚至同一引擎不同版本也不同。
  • 自动网格对齐
    有些格式很简单,没有包括控制信息,将字体度量如步进、宽度和高度分开。要靠渲染器来猜测轮廓的一些特性来实现得体的网格对齐。大小:不需要控制信息,导致更小的字体文件;质量:小字体不好,最后结合反走样;速度:依赖对齐算法,通常比显式对快。 速度:依赖算法;不一致:不同渲染器结果不同,甚至同一引擎不同版本也不同。

 


1.3字形度量


1.3.1基线(baseline)、笔(pen)和布局(layout)

    基线是一个假想的线,用来在渲染文本时知道字形,它可以是水平(如 Roman)和是垂直的(如中文)。而且,为了渲染文本,在基线上有一个虚拟的点,叫做笔位置( pen position)或原点(origin),他用来定位字形。每种布局使用不同的规约来放置字形:

  • 对水平布局,字形简单地搁在基线上,通过增加笔位置来渲染文本,既可以向右也可以向左增加。 两个相邻笔位置之间的距离是根据字形不同的,叫做步进宽度( advance width)。注意这个值总是正数,即使是从右往左的方向排列字符,如 Arabic。这和文本渲染的方式有些不同。 笔位置总是放置在基线上。
  • 对垂直布局,字形在基线上居中放置:


1.3.2印刷度量和边界框

    在指定字体中,定义了多种外观度量。

  • 上行高度(ascent)。从基线到放置轮廓点最高 /上的网格坐标,因为 Y轴方向是向上的,所以它是一个正值。
  • 下行高度(descent)。从基线到放置轮廓点最低 /下的网格坐标,因为 Y轴方向是向上的,所以它是一个负值。
  • 行距(linegap)。两行文本间必须的距离,基线到基线的距离应该计算成:上行高度 -下行高度 +行距
  • 边界框(bounding box,bbox)。这是一个假想的框子,他尽可能紧密的装入字形。通过四个值来表示,叫做xMin、yMin、xMax、yMax,对任何轮廓都可以计算,它们可以是字体单元(测量原始轮廓)或者整型象素单元(测量已缩放的轮廓) 。注意,如果不是为了网格对齐,你无需知道这个框子的这个值,只需知道它的大小即可。但为了正确渲染一个对齐的字形,需要保存每个字形在基线上转换、放置的重要对齐。
  • 内部leading。这个概念从传统印刷业而来, 他表示字形出了 EM正方形空间数量,通常计算如下:internalleading = ascent – descent – EM_size
  • 外部leading。行距的别名。



1.3.3跨距(bearing)和步进

    每个字形都有叫跨距和步进的距离,它们的定义是常量,但是它们的值依赖布局,同样的字形可以用来渲染横向或纵向文字。

  • 左跨距或 bearingX。从当前笔位置到字形左 bbox边界的水平距离,对水平布局是正数,对垂直布局大多是负值。
  • 上跨距或 bearingY。从基线到 bbox上边界的垂直距离,对水平布局是正值,对垂直布局是负值。
  • 步进宽度或 advanceX。当处理文本渲染一个字形后,笔位置必须增加(从左向右)或减少(从右向左)的水平距离。对水平布局总是正值,垂直布局为 null。
  • 步进高度或 advanceY。当每个字形渲染后,笔位置必须减少的垂直距离。对水平布局为 null,对垂直布局总是正值。
  • 字形宽度。字形的水平长度。对未缩放的字体坐标,它是 bbox.xMax-bbox.xMin,对已缩放字形,它的计算要看特定情况,视乎不同的网格对齐而定。
  • 字形高度。字形的垂直长度。对未缩放的字体坐标,它是 bbox.yMax-bbox.yMin,对已缩放字形,它的计算要看特定情况,视乎不同的网格对齐而定。
  • 右跨距。只用于水平布局,描述从 bbox右边到步进宽度的距离,通常是一个非负值。advance_width – left_side_bearing – (xMax-xMin)

    下图是水平布局所有的度量


    下图是垂直布局的度量:

 


1.3.4网格对齐的效果

    因为hinting将字形的控制点对齐到象素网格, 这个过程将稍稍修改字符映象的尺寸, 和简单的缩放有所区别。例如,小写字母 m的映象在主网格中有时是一个正方形, 但是为了使它在小象素大小情况下可以辨别,hinting试图扩大它已缩放轮廓,以让它三条腿区分开来,这将导致一个更大的字符位图。
    字形度量也会受网格对齐过程的影响:

  • 映象的宽度和高度改变了,即使只是一个象素,对于小象素大小字形区别都很大;
  • 映象的边界框改变了,也改变了跨距;
  • 步进必须更改,例如如果被 hint的位图比缩放的位图大时,必须增加步进宽度,来反映扩大的字形宽度。

    这有一些含义如下,

  • 因为hinting,简单缩放字体上行或下行高度可能不会有正确的结果,一个可能的方法时保持被缩放上行高度的顶和被缩放下行高度的底。
  • 没有容易的方法去 hint一个范围内字形并步进它们宽度,因为 hinting对每个轮廓工作都不一样。唯一的方法时单独 hint每个字形,并记录返回值。有些格式,如 TrueType,包含一些表对一些通用字符预先计算出它们的象素大小。
  • hinting依赖最终字符宽度和高度的象素值,意味着它非常依赖分辨率,这个特性使得正确的所见即所得布局非常难以实现。

    在FT 中,对字形轮廓处理 2D变换很简单,但是对一个已 hint的轮廓,需要注意专有地使用整型象素距离(意味FT_Outline_Translate() 函数的参数应该都乘以 64,因为点坐标都是 26.6固定浮点格式),否则,变换将破坏 hinter的工作,导致非常难看的位图。


1.3.5文本宽度和边界框

    如上所示,指定字形的原点对应基线上笔的位置,没有必要定位字形边界框的某个角,这不像多数典型的位图字体格式。有些情况,原点可以在边界框的外边,有时,也可以在里边,这要看给定的字形外形了。
    同样,字形的步进宽度是在布局时应用于笔位置的增量,而不是字形的宽度,那是字形边界的宽度。对文本串,具有相同的规约,这意味着:

  • 指定文本串的边界框没有必要包含文本光标,也不需要后边的字形放置在它的角上。
  • 字符串的步进宽度和它的边界框大小无关,特别时它在开始和最后包含空格或 tab。
  • 最后,附加的处理如间距调整能够创建文本串,它的大小不直接依赖单独字形度量并列排列。例如, VA
    的步进宽度不是 V和A各自的步进之和。

 


1.4字距调整

    字距调整这个术语指用来在一个文本串中调整重合字形的相对位置的特定信息。


1.4.1字距调整对

    字距调整包括根据相邻字形的轮廓修改它们之间的距离。例如 T和y可以贴得更近一点,因为 y的上缘正好在T的右上角一横的下边。
    当仅仅根据字形的标准宽度来布局文本,一些连续的字符看上去有点太挤和太松,有的字体外观包含一个表,它包含文本布局所需的指定字形对的字距距离。

  • 这个对是顺序的, AV对的距离和 VA对不一定一致;
  • 依据布局或书写,字距可以表示水平或垂直方向。
  • 字距表示成网格单元, 它们通常是 X轴方向的,意味着负值表示两个字形需要在水平方向放的更近一点。



1.4.2应用字距调整

    在渲染文本时应用字据调整是一个比较简单的过程,只需要在写下一个字形时,将缩放的字距加到笔位置即可。然而,正确的渲染器要考虑的更细一点。
    “滑动点”问题是一个很好的例子:很多字体外观包括一个大写字符(如 T、F)和一个点.之间的字距调整,以将点正好放置在前者的主腿的右侧。
    根据字符的外形,有时候需要在点和随后的字符间作附加的调整。一个方案是,只在需要时滑动点,当然这需要对文本的意思有了解。如果当我们在渲染特定段落的最后一个点时,上面的调整就不适合了。这只是一个例子,还有很多其他例子显示一个真正的印刷工人需要恰当地布局文本。
    有一个很简单地算法,可以避免滑动点问题。

1.在基线上放置第一个字形;

2.将笔位置保存到 pen1;

3.根据第一个和第二个字形的字距距离调整笔位置;

4.放置第二个字形,并计算下个笔位置,放到 pen2;

5.如果pen1大于pen2,使用pen1作为下个笔位置,否则使用 pen2。

 


1.5文本处理


1.5.1书写简单文本串

    在第一个例子中,我们将生成一个简单的 Roman文字串,即采用水平的自左向右布局, 使用专有的象素度量,这个过程如下:
1. 将字符串转换成一系列字形索引;
2. 将笔放置在光标位置;
3. 获得或装入字形映象;
4. 平移字形以使它的原点匹配笔位置;
5. 将字形渲染到目标设备;
6. 根据字形的步进象素增加笔位置;
7. 对剩余的字形进行第三步;
8. 当所有字形都处理了,在新的笔位置设置文本光标。
    注意字距调整不在这个算法中。


1.5.2子象素定位

    在渲染文本时使用子象素定位有时很有用。这非常重要,例如为了提供半所见即所得的文本布局,文本渲染的算法和上一节很相似,但是有些区别:

  • 笔位置表示成小数形式的象素;
  • 因为将一个已经 hint过的轮廓平移一个非整型距离将破坏网格对齐,字形原点的位置在渲染字符映象前必须取整;
  • 步进宽度表示成小数形式的象素,没有必要是整型。

    这里是算法的改进版本:
1. 将字符串转换成一系列字形索引;
2. 将笔放置在光标位置,这可以是一个非整型点;
3. 获得或装入字形映象;
4. 平移字形以使它的原点匹配取整后的笔位置;
5. 将字形渲染到目标设备;
6. 根据字形的步进象素宽度增加笔位置,这个宽度可以是小数形式;
7. 对剩余的字形进行第三步;
8. 当所有字形都处理了,在新的笔位置设置文本光标。
    注意使用小数象素定位后,两个指定字符间的空间将不是固定的,它右先前的取整操作堆积的数决定。


1.5.3简单字距调整

    在基本文本渲染算法上增加字距调整非常简单,当一个字距调整对发现了,简单地在第 4步前,将缩放后的
调整距离增加到笔位置即可。淡然,这个距离在算法 1需要被取整,算法 2不必要。


1.5.4自右向左布局

布局Arabic或Heberw文字的过程非常相似,区别只是在字形渲染前,笔位置需要减少(记住步进宽度总是正值)

 

1.5.5垂直布局

    布局垂直文字也是同样的过程,重要的区别如下:

  • 基线是垂直的,使用垂直的度量而不是水平度量;
  • 左跨距通常是负的,但字形原点必须在基线上;
  • 步进高度总是正值,所以笔位置必须减少以从上至下书写;



1.6 FT轮廓

 

1.6.1 FT轮廓描述和结构

a. 轮廓曲线分解
    一个轮廓是 2D平面上一系列封闭的轮廓线。 每个轮廓线由一系列线段和 Bezier弧组成,根据文件格式不同,曲线可以是二次和三次多项式, 前者叫quadratic或conic弧,它们在TrueType格式中用到,后者叫cubic弧,多数用于 Type1格式。
    每条弧由一系列起点、终点和控制点描述,轮廓的每个点有一个特定的标记,表示它用来描述一个线段还是一条弧。这个标记可以有以下值:
FT_Curve_Tag_On 当点在曲线上,这对应线段和弧的起点和终点。其他标记叫做“ Off”点,它不在轮廓线,但是作为 Bezier弧的控制点。
FT_Curve_Tag_Conic 一个Off点,控制一个 conic Bezier弧
FT_Curve_Tag_Cubic 一个Off点,控制一个 cubic Bezier弧
    下面的规则应用于将轮廓点分解成线段和弧

  • 两个相邻的“ on”点表示一条线段;
  • 一个conic Off点在两个 on点之间表示一个 conic Bezier弧,off点是控制点, on点是起点和终点;
  • 两个相邻的 cubic off点在两个on点之间表示一个 cubic Bezier弧,它必须有两个 cubic控制点和两个 on点。
  • 最后,两个相邻的 conic off点强制??在它们正中间创建一个虚拟的 on点。这大大方便定义连续的 conic
    弧。TrueType规范就是这么定义的。

    注意,在单个轮廓线中可以混合使用 conic和cubic弧,不过现在没有那种字体驱动产生这样的轮廓。


b. 轮廓描述符
    FT轮廓通过一个简单的结构描述:


FT_Outline
n_points 轮廓中的点数
n_contours 轮廓中轮廓线数
points 点坐标数组
contours 轮廓线端点索引数组
tags 点标记数组


    这里,points是一个FT_Vector记录数组的指针,用来存储每个轮廓点的向量坐标。它表示为一个象素 1/64,也叫做26.6固定浮点格式。
    contours是一组点索引,用来划定轮廓的轮廓线。例如,第一个轮廓线总是从 0点开始,以 contours[0]点结束。第二个轮廓线从 contours[0]+1点开始,以 contours[1]结束,等等。
    注意,每条轮廓线都是封闭的, n_points应该和 contours[n_controus-1]+1 相同。最后,tags是一组字节,用来存放每个轮廓的点标记。



1.6.2边界和控制框计算

    边界框(bbox)是一个完全包含指定轮廓的矩形,所要的是最小的边界框。因为弧的定义, bezier的控制点无需包含在轮廓的边界框中。例如轮廓的上缘是一个 Bezier弧,一个off点就位于bbox的上面。不过这在字符轮廓中很少出现, 因为大多字体设计者和创建工具都会在每个曲线拐点处放一个 on点,这会使hinting更加容易。于是我们定义了控制框( cbox),它是一个包含轮廓所有点的最小矩形,很明显,它包含 bbox,通常它们是一样的。不想 bbox,cbox计算起来非常快。
    控制框和边界框可以通过函数 FT_Outline_Get_CBox()和 FT_Outline_Get_BBox() 自动计算,前者总是非常快,后者在有外界控制点的情况下会慢一点,因为需要找到 conic和cubic弧的末端,如果不是这种情况,它和计算控制框一样快。
    注意,虽然大多字形轮廓为易于 hint具有相同的 cbox和bbox,这在它们进行变换以后,如旋转,就不再是这种情况了。



1.6.3坐标、缩放和网格对齐

    轮廓点的向量坐标表示为 26.6格式,即一个象素的 1/64。因此,坐标(1.0,-2.5)存放整型对(x:64,y:-192)。
    在主字形轮廓从 EM网格缩放到当前字符大小后, hinter负责对齐重要的轮廓点到象素网格。虽然这个过程很难几句话说清楚,但是它的目的也就是取整点的位置,以保持字形重要的特性,如宽度、主干等。下面的操作可以用来将 26.6格式的向量距离取整到网格

这份文档提供了FreeType 2函数库设计实现的细节。本文档的目标是让开发人员更好的理解FreeType 2是如何组织的,并让他们扩充、定制和调试它。 首先,我们先了解这个库的目的,也就是说,为什么会写这个库: * 它让客户应用程序方便的访问字体文件,无论字体文件存储在哪里,并且字体格式无关。 * 方便的提取全局字体数据,这些数据在平常的字体格式中普遍存在。(例如:全局度量标准,字符编码/字符映射表,等等) * 方便的提取某个字符的字形数据(度量标准,图像,名字,其他任何东西) * 访问字体格式特定的功能(例如,SFNT表,多重控制,OpenType轮廓表) Freetype 2的设计也受如下要求很大的影响: * 高可移植性。这个库必须可以运行在任何环境中。这个要求引入了一些非常激烈的选择,这些是FreeType2的低级系统界面的一部分。 * 可扩展性。新特性应该可以在极少改动库基础代码的前提下添加。这个要求引入了非常简单的设计:几乎所有操作都是以模块的形式提供的。 * 可定制。它应该能够很容易建立一个只包含某个特定项目所需的特性的版本。当你需要集成它到一个嵌入式图形库的字体服务器中时,这是非常重要的。 * 简洁高效。这个库的主要目标是只有很少cpu和内存资源的嵌入式系统。 这份文档的其他部分分为几个部分。首先,一些章节介绍了库的基本设计以及Freetype 2内部对象/数据的管理。 接下来的章节专注于库的定制和这个话题相关的系统特定的界面,如何写你自己的模块和如何按需裁减库初始化和编译。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值