fonteditor-core
Version:
fonts (ttf, woff, woff2, eot, svg, otf) parse, write, transform, glyph adjust.
260 lines (231 loc) • 8.02 kB
JavaScript
/**
* @file cff表
* @author mengke01(kekee000@gmail.com)
*
* reference:
* http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5176.CFF.pdf
*
* modify from:
* https://github.com/nodebox/opentype.js/blob/master/src/tables/cff.js
*/
import table from './table';
import string from '../util/string';
import encoding from './cff/encoding';
import cffStandardStrings from './cff/cffStandardStrings';
import parseCFFDict from './cff/parseCFFDict';
import parseCFFGlyph from './cff/parseCFFGlyph';
import parseCFFCharset from './cff/parseCFFCharset';
import parseCFFEncoding from './cff/parseCFFEncoding';
import Reader from '../reader';
/**
* 获取cff偏移
*
* @param {Reader} reader 读取器
* @param {number} offSize 偏移大小
* @param {number} offset 起始偏移
* @return {number} 偏移
*/
function getOffset(reader, offSize) {
let v = 0;
for (let i = 0; i < offSize; i++) {
v <<= 8;
v += reader.readUint8();
}
return v;
}
/**
* 解析cff表头部
*
* @param {Reader} reader 读取器
* @return {Object} 头部字段
*/
function parseCFFHead(reader) {
const head = {};
head.startOffset = reader.offset;
head.endOffset = head.startOffset + 4;
head.formatMajor = reader.readUint8();
head.formatMinor = reader.readUint8();
head.size = reader.readUint8();
head.offsetSize = reader.readUint8();
return head;
}
/**
* 解析`CFF`表索引
*
* @param {Reader} reader 读取器
* @param {number} offset 偏移
* @param {Funciton} conversionFn 转换函数
* @return {Object} 表对象
*/
function parseCFFIndex(reader, offset, conversionFn) {
if (offset) {
reader.seek(offset);
}
const start = reader.offset;
const offsets = [];
const objects = [];
const count = reader.readUint16();
let i;
let l;
if (count !== 0) {
const offsetSize = reader.readUint8();
for (i = 0, l = count + 1; i < l; i++) {
offsets.push(getOffset(reader, offsetSize));
}
for (i = 0, l = count; i < l; i++) {
let value = reader.readBytes(offsets[i + 1] - offsets[i]);
if (conversionFn) {
value = conversionFn(value);
}
objects.push(value);
}
}
return {
objects,
startOffset: start,
endOffset: reader.offset
};
}
// Subroutines are encoded using the negative half of the number space.
// See type 2 chapter 4.7 "Subroutine operators".
function calcCFFSubroutineBias(subrs) {
let bias;
if (subrs.length < 1240) {
bias = 107;
}
else if (subrs.length < 33900) {
bias = 1131;
}
else {
bias = 32768;
}
return bias;
}
export default table.create(
'cff',
[],
{
read(reader, font) {
const offset = this.offset;
reader.seek(offset);
const head = parseCFFHead(reader);
const nameIndex = parseCFFIndex(reader, head.endOffset, string.getString);
const topDictIndex = parseCFFIndex(reader, nameIndex.endOffset);
const stringIndex = parseCFFIndex(reader, topDictIndex.endOffset, string.getString);
const globalSubrIndex = parseCFFIndex(reader, stringIndex.endOffset);
const cff = {
head
};
// 全局子glyf数据
cff.gsubrs = globalSubrIndex.objects;
cff.gsubrsBias = calcCFFSubroutineBias(globalSubrIndex.objects);
// 顶级字典数据
const dictReader = new Reader(new Uint8Array(topDictIndex.objects[0]).buffer);
const topDict = parseCFFDict.parseTopDict(
dictReader,
0,
dictReader.length,
stringIndex.objects
);
cff.topDict = topDict;
// 私有字典数据
const privateDictLength = topDict.private[0];
let privateDict = {};
let privateDictOffset;
if (privateDictLength) {
privateDictOffset = offset + topDict.private[1];
privateDict = parseCFFDict.parsePrivateDict(
reader,
privateDictOffset,
privateDictLength,
stringIndex.objects
);
cff.defaultWidthX = privateDict.defaultWidthX;
cff.nominalWidthX = privateDict.nominalWidthX;
}
else {
cff.defaultWidthX = 0;
cff.nominalWidthX = 0;
}
// 私有子glyf数据
if (privateDict.subrs) {
const subrOffset = privateDictOffset + privateDict.subrs;
const subrIndex = parseCFFIndex(reader, subrOffset);
cff.subrs = subrIndex.objects;
cff.subrsBias = calcCFFSubroutineBias(cff.subrs);
}
else {
cff.subrs = [];
cff.subrsBias = 0;
}
cff.privateDict = privateDict;
// 解析glyf数据和名字
const charStringsIndex = parseCFFIndex(reader, offset + topDict.charStrings);
const nGlyphs = charStringsIndex.objects.length;
if (topDict.charset < 3) {
// @author: fr33z00
// See end of chapter 13 (p22) of #5176.CFF.pdf :
// Still more optimization is possible by
// observing that many fonts adopt one of 3 common charsets. In
// these cases the operand to the charset operator in the Top DICT
// specifies a predefined charset id, in place of an offset, as shown in table 22
cff.charset = cffStandardStrings;
}
else {
cff.charset = parseCFFCharset(reader, offset + topDict.charset, nGlyphs, stringIndex.objects);
}
// Standard encoding
if (topDict.encoding === 0) {
cff.encoding = encoding.standardEncoding;
}
// Expert encoding
else if (topDict.encoding === 1) {
cff.encoding = encoding.expertEncoding;
}
else {
cff.encoding = parseCFFEncoding(reader, offset + topDict.encoding);
}
cff.glyf = [];
// only parse subset glyphs
const subset = font.readOptions.subset;
if (subset && subset.length > 0) {
// subset map
const subsetMap = {
0: true // 设置.notdef
};
const codes = font.cmap;
// unicode to index
Object.keys(codes).forEach((c) => {
if (subset.indexOf(+c) > -1) {
const i = codes[c];
subsetMap[i] = true;
}
});
font.subsetMap = subsetMap;
Object.keys(subsetMap).forEach((i) => {
i = +i;
const glyf = parseCFFGlyph(charStringsIndex.objects[i], cff, i);
glyf.name = cff.charset[i];
cff.glyf[i] = glyf;
});
}
// parse all
else {
for (let i = 0, l = nGlyphs; i < l; i++) {
const glyf = parseCFFGlyph(charStringsIndex.objects[i], cff, i);
glyf.name = cff.charset[i];
cff.glyf.push(glyf);
}
}
return cff;
},
// eslint-disable-next-line no-unused-vars
write(writer, font) {
throw new Error('not support write cff table');
},
// eslint-disable-next-line no-unused-vars
size(font) {
throw new Error('not support get cff table size');
}
}
);