修改为本地引用,清理冗余文件

This commit is contained in:
崮生(子虚) 2026-04-09 09:50:26 +08:00
parent a8fdb24de4
commit 765a301649
133 changed files with 771 additions and 13657 deletions

View File

@ -1,4 +1,5 @@
import { Font, type FontEditor } from "fonteditor-core";
import { Font } from "../../vendor/fonteditor-core/lib/ttf/font.js";
import type { FontEditor } from "../../vendor/fonteditor-core/lib/ttf/font.js";
/**
*

48
debug_profiling.ts Normal file
View File

@ -0,0 +1,48 @@
/**
*
*/
import { readFile } from "node:fs/promises";
import { Font } from "./vendor/fonteditor-core/lib/ttf/font.js";
const FONT_PATH = "font/令东齐伋复刻体.ttf";
const raw = await readFile(FONT_PATH);
const fontBuffer = new Uint8Array(raw).buffer;
const testCases = [
{ label: "8个汉字", subset: [..."天地玄黄宇宙洪荒"].map(c => c.codePointAt(0)!) },
{ label: "千字文前段", subset: [..."天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳云腾致雨露结为霜金生丽水玉出昆冈剑号巨阙珠称夜光果珍李柰菜重芥姜海咸河淡鳞潜羽翔"].map(c => c.codePointAt(0)!) },
];
const ROUNDS = 30;
for (const { label, subset } of testCases) {
let createSum = 0, optimizeSum = 0, sortSum = 0, writeSum = 0;
for (let i = 0; i < ROUNDS; i++) {
let t0 = performance.now();
const font = Font.create(fontBuffer, { type: "ttf", subset });
let t1 = performance.now();
createSum += t1 - t0;
const optimized = font.optimize();
let t2 = performance.now();
optimizeSum += t2 - t1;
const sorted = optimized.sort();
let t3 = performance.now();
sortSum += t3 - t2;
const result = sorted.write({ type: "ttf" });
let t4 = performance.now();
writeSum += t4 - t3;
}
const total = createSum + optimizeSum + sortSum + writeSum;
console.log(`${label} (${ROUNDS} rounds):`);
console.log(` create: ${createSum.toFixed(1)}ms (${(createSum / total * 100).toFixed(1)}%)`);
console.log(` optimize: ${optimizeSum.toFixed(1)}ms (${(optimizeSum / total * 100).toFixed(1)}%)`);
console.log(` sort: ${sortSum.toFixed(1)}ms (${(sortSum / total * 100).toFixed(1)}%)`);
console.log(` write: ${writeSum.toFixed(1)}ms (${(writeSum / total * 100).toFixed(1)}%)`);
console.log(` total: ${total.toFixed(1)}ms`);
console.log();
}

51
debug_table_timing3.ts Normal file
View File

@ -0,0 +1,51 @@
/**
*
*/
import { readFile } from "node:fs/promises";
const FONT_PATH = "font/令东齐伋复刻体.ttf";
const raw = await readFile(FONT_PATH);
const fontBuffer = new Uint8Array(raw).buffer;
const ROUNDS = 20;
const tableTimes: Record<string, number> = {};
for (let r = 0; r < ROUNDS; r++) {
const ReaderModule = await import("./vendor/fonteditor-core/lib/ttf/reader.js");
const Reader = (ReaderModule as any).default || ReaderModule;
const DirectoryModule = await import("./vendor/fonteditor-core/lib/ttf/table/directory.js");
const Directory = (DirectoryModule as any).default || DirectoryModule;
const supportModule = await import("./vendor/fonteditor-core/lib/ttf/table/support.js");
const support = (supportModule as any).default || supportModule;
const reader = new Reader(fontBuffer, 0, fontBuffer.byteLength, false);
const ttf: any = {};
ttf.version = reader.readFixed(0);
ttf.numTables = reader.readUint16();
ttf.searchRange = reader.readUint16();
ttf.entrySelector = reader.readUint16();
ttf.rangeShift = reader.readUint16();
ttf.tables = new Directory(reader.offset).read(reader, ttf);
ttf.readOptions = { subset: [..."天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳云腾致雨露结为霜金生丽水玉出昆冈剑号巨阙珠称夜光果珍李柰菜重芥姜海咸河淡鳞潜羽翔"].map(c => c.codePointAt(0)) };
for (const tableName of Object.keys(support)) {
if (ttf.tables[tableName]) {
const offset = ttf.tables[tableName].offset;
const t0 = performance.now();
ttf[tableName] = new support[tableName](offset).read(reader, ttf);
const t1 = performance.now();
tableTimes[tableName] = (tableTimes[tableName] || 0) + (t1 - t0);
}
}
reader.dispose();
}
const total = Object.values(tableTimes).reduce((a: number, b: number) => a + b, 0);
const sorted = Object.entries(tableTimes).sort((a, b) => b[1] - a[1]);
console.log(`表读取时间 (${ROUNDS} rounds):`);
for (const [name, time] of sorted) {
console.log(` ${name.padEnd(8)} ${time.toFixed(1).padStart(8)}ms ${(time / total * 100).toFixed(1).padStart(3)}%`);
}
console.log(` ${'total'.padEnd(8)} ${total.toFixed(1).padStart(8)}ms`);

View File

@ -15,18 +15,20 @@
"release": "pnpm build && pnpm build_backend && pnpm docker_build && pnpm docker_push"
},
"dependencies": {
"fonteditor-core": "file:./vendor/fonteditor-core",
"solid-js": "^1.9.12",
"web-streams-polyfill": "^4.2.0"
},
"devDependencies": {
"@types/node": "^25.5.2",
"@xmldom/xmldom": "^0.9.9",
"jsdom": "^29.0.2",
"skia-canvas": "^3.0.8",
"tsup": "^8.5.1",
"typescript": "^6.0.2",
"undici": "^8.0.2",
"vite": "^8.0.7",
"vite-plugin-pilot": "^1.0.19",
"vite-plugin-solid": "^2.11.12"
"vite-plugin-solid": "^2.11.12",
"vitest": "^4.1.3"
}
}

570
pnpm-lock.yaml generated
View File

@ -8,9 +8,6 @@ importers:
.:
dependencies:
fonteditor-core:
specifier: file:./vendor/fonteditor-core
version: file:vendor/fonteditor-core
solid-js:
specifier: ^1.9.12
version: 1.9.12
@ -21,6 +18,12 @@ importers:
'@types/node':
specifier: ^25.5.2
version: 25.5.2
'@xmldom/xmldom':
specifier: ^0.9.9
version: 0.9.9
jsdom:
specifier: ^29.0.2
version: 29.0.2
skia-canvas:
specifier: ^3.0.8
version: 3.0.8
@ -42,9 +45,23 @@ importers:
vite-plugin-solid:
specifier: ^2.11.12
version: 2.11.12(solid-js@1.9.12)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.27.7))
vitest:
specifier: ^4.1.3
version: 4.1.3(@types/node@25.5.2)(jsdom@29.0.2)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.27.7))
packages:
'@asamuzakjp/css-color@5.1.8':
resolution: {integrity: sha512-OISPR9c2uPo23rUdvfEQiLPjoMLOpEeLNnP5iGkxr6tDDxJd3NjD+6fxY0mdaMbIPUjFGL4HFOJqLvow5q4aqQ==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
'@asamuzakjp/dom-selector@7.0.8':
resolution: {integrity: sha512-erMO6FgtM02dC24NGm0xufMzWz5OF0wXKR7BpvGD973bq/GbmR8/DbxNZbj0YevQ5hlToJaWSVK/G9/NDgGEVw==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
'@asamuzakjp/nwsapi@2.3.9':
resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
'@babel/code-frame@7.29.0':
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'}
@ -126,6 +143,46 @@ packages:
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
engines: {node: '>=6.9.0'}
'@bramus/specificity@2.4.2':
resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
hasBin: true
'@csstools/color-helpers@6.0.2':
resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==}
engines: {node: '>=20.19.0'}
'@csstools/css-calc@3.1.1':
resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==}
engines: {node: '>=20.19.0'}
peerDependencies:
'@csstools/css-parser-algorithms': ^4.0.0
'@csstools/css-tokenizer': ^4.0.0
'@csstools/css-color-parser@4.0.2':
resolution: {integrity: sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==}
engines: {node: '>=20.19.0'}
peerDependencies:
'@csstools/css-parser-algorithms': ^4.0.0
'@csstools/css-tokenizer': ^4.0.0
'@csstools/css-parser-algorithms@4.0.0':
resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==}
engines: {node: '>=20.19.0'}
peerDependencies:
'@csstools/css-tokenizer': ^4.0.0
'@csstools/css-syntax-patches-for-csstree@1.1.2':
resolution: {integrity: sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA==}
peerDependencies:
css-tree: ^3.2.1
peerDependenciesMeta:
css-tree:
optional: true
'@csstools/css-tokenizer@4.0.0':
resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
engines: {node: '>=20.19.0'}
'@emnapi/core@1.9.1':
resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==}
@ -291,6 +348,15 @@ packages:
cpu: [x64]
os: [win32]
'@exodus/bytes@1.15.0':
resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
peerDependencies:
'@noble/hashes': ^1.8.0 || ^2.0.0
peerDependenciesMeta:
'@noble/hashes':
optional: true
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
@ -552,6 +618,9 @@ packages:
cpu: [x64]
os: [win32]
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
@ -567,15 +636,50 @@ packages:
'@types/babel__traverse@7.28.0':
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/node@25.5.2':
resolution: {integrity: sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==}
'@xmldom/xmldom@0.8.12':
resolution: {integrity: sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==}
engines: {node: '>=10.0.0'}
'@vitest/expect@4.1.3':
resolution: {integrity: sha512-CW8Q9KMtXDGHj0vCsqui0M5KqRsu0zm0GNDW7Gd3U7nZ2RFpPKSCpeCXoT+/+5zr1TNlsoQRDEz+LzZUyq6gnQ==}
'@vitest/mocker@4.1.3':
resolution: {integrity: sha512-XN3TrycitDQSzGRnec/YWgoofkYRhouyVQj4YNsJ5r/STCUFqMrP4+oxEv3e7ZbLi4og5kIHrZwekDJgw6hcjw==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
'@vitest/pretty-format@4.1.3':
resolution: {integrity: sha512-hYqqwuMbpkkBodpRh4k4cQSOELxXky1NfMmQvOfKvV8zQHz8x8Dla+2wzElkMkBvSAJX5TRGHJAQvK0TcOafwg==}
'@vitest/runner@4.1.3':
resolution: {integrity: sha512-VwgOz5MmT0KhlUj40h02LWDpUBVpflZ/b7xZFA25F29AJzIrE+SMuwzFf0b7t4EXdwRNX61C3B6auIXQTR3ttA==}
'@vitest/snapshot@4.1.3':
resolution: {integrity: sha512-9l+k/J9KG5wPJDX9BcFFzhhwNjwkRb8RsnYhaT1vPY7OufxmQFc9sZzScRCPTiETzl37mrIWVY9zxzmdVeJwDQ==}
'@vitest/spy@4.1.3':
resolution: {integrity: sha512-ujj5Uwxagg4XUIfAUyRQxAg631BP6e9joRiN99mr48Bg9fRs+5mdUElhOoZ6rP5mBr8Bs3lmrREnkrQWkrsTCw==}
'@vitest/utils@4.1.3':
resolution: {integrity: sha512-Pc/Oexse/khOWsGB+w3q4yzA4te7W4gpZZAvk+fr8qXfTURZUMj5i7kuxsNK5mP/dEB6ao3jfr0rs17fHhbHdw==}
'@xmldom/xmldom@0.9.9':
resolution: {integrity: sha512-qycIHAucxy/LXAYIjmLmtQ8q9GPnMbnjG1KXhWm9o5sCr6pOYDATkMPiTNa6/v8eELyqOQ2FsEqeoFYmgv/gJg==}
engines: {node: '>=14.6'}
acorn@8.16.0:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
@ -589,6 +693,10 @@ packages:
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
babel-plugin-jsx-dom-expressions@0.40.6:
resolution: {integrity: sha512-v3P1MW46Lm7VMpAkq0QfyzLWWkC8fh+0aE5Km4msIgDx5kjenHU0pF2s+4/NH8CQn/kla6+Hvws+2AF7bfV5qQ==}
peerDependencies:
@ -608,6 +716,9 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
bidi-js@1.0.3:
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
browserslist@4.28.2:
resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
@ -626,6 +737,10 @@ packages:
caniuse-lite@1.0.30001787:
resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==}
chai@6.2.2:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
engines: {node: '>=18'}
chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
@ -644,9 +759,17 @@ packages:
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
css-tree@3.2.1:
resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
data-urls@7.0.0:
resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@ -656,6 +779,9 @@ packages:
supports-color:
optional: true
decimal.js@10.6.0:
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
@ -667,6 +793,9 @@ packages:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
es-module-lexer@2.0.0:
resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==}
esbuild@0.27.7:
resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
engines: {node: '>=18'}
@ -676,6 +805,13 @@ packages:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
expect-type@1.3.0:
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
engines: {node: '>=12.0.0'}
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@ -697,9 +833,6 @@ packages:
debug:
optional: true
fonteditor-core@file:vendor/fonteditor-core:
resolution: {directory: vendor/fonteditor-core, type: directory}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@ -709,6 +842,10 @@ packages:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
html-encoding-sniffer@6.0.0:
resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
html-entities@2.3.3:
resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
@ -716,6 +853,9 @@ packages:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
is-what@4.1.16:
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
engines: {node: '>=12.13'}
@ -727,6 +867,15 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
jsdom@29.0.2:
resolution: {integrity: sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0}
peerDependencies:
canvas: ^3.0.0
peerDependenciesMeta:
canvas:
optional: true
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
@ -822,12 +971,19 @@ packages:
resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
lru-cache@11.3.3:
resolution: {integrity: sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==}
engines: {node: 20 || >=22}
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
mdn-data@2.27.1:
resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
merge-anything@5.1.7:
resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==}
engines: {node: '>=12.13'}
@ -853,12 +1009,18 @@ packages:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
obug@2.1.1:
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
parenthesis@3.1.8:
resolution: {integrity: sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw==}
parse5@7.3.0:
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
parse5@8.0.0:
resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
@ -898,10 +1060,18 @@ packages:
resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==}
engines: {node: ^10 || ^12 || >=14}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
readdirp@4.1.2:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
resolve-from@5.0.0:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
engines: {node: '>=8'}
@ -916,6 +1086,10 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
saxes@6.0.0:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@ -930,6 +1104,9 @@ packages:
resolution: {integrity: sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==}
engines: {node: '>=10'}
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
skia-canvas@3.0.8:
resolution: {integrity: sha512-FSYKxp8Ng2vOeeOBiyPhnn6ui6FirPJXMyjk4PKl8N/OWzVrkMawUgY9zubIWHMdYtyWFn0gfX3QlRwg6HBmdg==}
@ -949,6 +1126,12 @@ packages:
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
engines: {node: '>= 12'}
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
std-env@4.0.0:
resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==}
string-split-by@1.0.0:
resolution: {integrity: sha512-KaJKY+hfpzNyet/emP81PJA9hTVSfxNLS9SFTWxdCnnW1/zOOwiV248+EfoX7IQFcBaOp4G5YE6xTJMF+pLg6A==}
@ -957,6 +1140,9 @@ packages:
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
thenify-all@1.6.0:
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
engines: {node: '>=0.8'}
@ -964,13 +1150,39 @@ packages:
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
tinyexec@1.1.1:
resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==}
engines: {node: '>=18'}
tinyglobby@0.2.16:
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
engines: {node: '>=12.0.0'}
tinyrainbow@3.1.0:
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
engines: {node: '>=14.0.0'}
tldts-core@7.0.28:
resolution: {integrity: sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==}
tldts@7.0.28:
resolution: {integrity: sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==}
hasBin: true
tough-cookie@6.0.1:
resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==}
engines: {node: '>=16'}
tr46@6.0.0:
resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==}
engines: {node: '>=20'}
tree-kill@1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
@ -1011,6 +1223,10 @@ packages:
undici-types@7.18.2:
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
undici@7.24.7:
resolution: {integrity: sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==}
engines: {node: '>=20.18.1'}
undici@8.0.2:
resolution: {integrity: sha512-B9MeU5wuFhkFAuNeA19K2GDFcQXZxq33fL0nRy2Aq30wdufZbyyvxW3/ChaeipXVfy/wUweZyzovQGk39+9k2w==}
engines: {node: '>=22.19.0'}
@ -1096,15 +1312,100 @@ packages:
vite:
optional: true
vitest@4.1.3:
resolution: {integrity: sha512-DBc4Tx0MPNsqb9isoyOq00lHftVx/KIU44QOm2q59npZyLUkENn8TMFsuzuO+4U2FUa9rgbbPt3udrP25GcjXw==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
'@vitest/browser-playwright': 4.1.3
'@vitest/browser-preview': 4.1.3
'@vitest/browser-webdriverio': 4.1.3
'@vitest/coverage-istanbul': 4.1.3
'@vitest/coverage-v8': 4.1.3
'@vitest/ui': 4.1.3
happy-dom: '*'
jsdom: '*'
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@opentelemetry/api':
optional: true
'@types/node':
optional: true
'@vitest/browser-playwright':
optional: true
'@vitest/browser-preview':
optional: true
'@vitest/browser-webdriverio':
optional: true
'@vitest/coverage-istanbul':
optional: true
'@vitest/coverage-v8':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
w3c-xmlserializer@5.0.0:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'}
web-streams-polyfill@4.2.0:
resolution: {integrity: sha512-0rYDzGOh9EZpig92umN5g5D/9A1Kff7k0/mzPSSCY8jEQeYkgRMoY7LhbXtUCWzLCMX0TUE9aoHkjFNB7D9pfA==}
engines: {node: '>= 8'}
webidl-conversions@8.0.1:
resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==}
engines: {node: '>=20'}
whatwg-mimetype@5.0.0:
resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==}
engines: {node: '>=20'}
whatwg-url@16.0.1:
resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
why-is-node-running@2.3.0:
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
hasBin: true
xml-name-validator@5.0.0:
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
engines: {node: '>=18'}
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
snapshots:
'@asamuzakjp/css-color@5.1.8':
dependencies:
'@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-color-parser': 4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@asamuzakjp/dom-selector@7.0.8':
dependencies:
'@asamuzakjp/nwsapi': 2.3.9
bidi-js: 1.0.3
css-tree: 3.2.1
is-potential-custom-element-name: 1.0.1
'@asamuzakjp/nwsapi@2.3.9': {}
'@babel/code-frame@7.29.0':
dependencies:
'@babel/helper-validator-identifier': 7.28.5
@ -1216,6 +1517,34 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
'@bramus/specificity@2.4.2':
dependencies:
css-tree: 3.2.1
'@csstools/color-helpers@6.0.2': {}
'@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
dependencies:
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@csstools/css-color-parser@4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
dependencies:
'@csstools/color-helpers': 6.0.2
'@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)':
dependencies:
'@csstools/css-tokenizer': 4.0.0
'@csstools/css-syntax-patches-for-csstree@1.1.2(css-tree@3.2.1)':
optionalDependencies:
css-tree: 3.2.1
'@csstools/css-tokenizer@4.0.0': {}
'@emnapi/core@1.9.1':
dependencies:
'@emnapi/wasi-threads': 1.2.0
@ -1310,6 +1639,8 @@ snapshots:
'@esbuild/win32-x64@0.27.7':
optional: true
'@exodus/bytes@1.15.0': {}
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@ -1464,6 +1795,8 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.60.1':
optional: true
'@standard-schema/spec@1.1.0': {}
'@tybys/wasm-util@0.10.1':
dependencies:
tslib: 2.8.1
@ -1490,13 +1823,61 @@ snapshots:
dependencies:
'@babel/types': 7.29.0
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
assertion-error: 2.0.1
'@types/deep-eql@4.0.2': {}
'@types/estree@1.0.8': {}
'@types/node@25.5.2':
dependencies:
undici-types: 7.18.2
'@xmldom/xmldom@0.8.12': {}
'@vitest/expect@4.1.3':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.3
'@vitest/spy': 4.1.3
'@vitest/utils': 4.1.3
chai: 6.2.2
tinyrainbow: 3.1.0
'@vitest/mocker@4.1.3(vite@8.0.7(@types/node@25.5.2)(esbuild@0.27.7))':
dependencies:
'@vitest/spy': 4.1.3
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 8.0.7(@types/node@25.5.2)(esbuild@0.27.7)
'@vitest/pretty-format@4.1.3':
dependencies:
tinyrainbow: 3.1.0
'@vitest/runner@4.1.3':
dependencies:
'@vitest/utils': 4.1.3
pathe: 2.0.3
'@vitest/snapshot@4.1.3':
dependencies:
'@vitest/pretty-format': 4.1.3
'@vitest/utils': 4.1.3
magic-string: 0.30.21
pathe: 2.0.3
'@vitest/spy@4.1.3': {}
'@vitest/utils@4.1.3':
dependencies:
'@vitest/pretty-format': 4.1.3
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
'@xmldom/xmldom@0.9.9': {}
acorn@8.16.0: {}
@ -1504,6 +1885,8 @@ snapshots:
any-promise@1.3.0: {}
assertion-error@2.0.1: {}
babel-plugin-jsx-dom-expressions@0.40.6(@babel/core@7.29.0):
dependencies:
'@babel/core': 7.29.0
@ -1522,6 +1905,10 @@ snapshots:
baseline-browser-mapping@2.10.16: {}
bidi-js@1.0.3:
dependencies:
require-from-string: 2.0.2
browserslist@4.28.2:
dependencies:
baseline-browser-mapping: 2.10.16
@ -1539,6 +1926,8 @@ snapshots:
caniuse-lite@1.0.30001787: {}
chai@6.2.2: {}
chokidar@4.0.3:
dependencies:
readdirp: 4.1.2
@ -1551,18 +1940,34 @@ snapshots:
convert-source-map@2.0.0: {}
css-tree@3.2.1:
dependencies:
mdn-data: 2.27.1
source-map-js: 1.2.1
csstype@3.2.3: {}
data-urls@7.0.0:
dependencies:
whatwg-mimetype: 5.0.0
whatwg-url: 16.0.1
transitivePeerDependencies:
- '@noble/hashes'
debug@4.4.3:
dependencies:
ms: 2.1.3
decimal.js@10.6.0: {}
detect-libc@2.1.2: {}
electron-to-chromium@1.5.333: {}
entities@6.0.1: {}
es-module-lexer@2.0.0: {}
esbuild@0.27.7:
optionalDependencies:
'@esbuild/aix-ppc64': 0.27.7
@ -1594,6 +1999,12 @@ snapshots:
escalade@3.2.0: {}
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.8
expect-type@1.3.0: {}
fdir@6.5.0(picomatch@4.0.4):
optionalDependencies:
picomatch: 4.0.4
@ -1606,15 +2017,17 @@ snapshots:
follow-redirects@1.15.11: {}
fonteditor-core@file:vendor/fonteditor-core:
dependencies:
'@xmldom/xmldom': 0.8.12
fsevents@2.3.3:
optional: true
gensync@1.0.0-beta.2: {}
html-encoding-sniffer@6.0.0:
dependencies:
'@exodus/bytes': 1.15.0
transitivePeerDependencies:
- '@noble/hashes'
html-entities@2.3.3: {}
https-proxy-agent@7.0.6:
@ -1624,12 +2037,40 @@ snapshots:
transitivePeerDependencies:
- supports-color
is-potential-custom-element-name@1.0.1: {}
is-what@4.1.16: {}
joycon@3.1.1: {}
js-tokens@4.0.0: {}
jsdom@29.0.2:
dependencies:
'@asamuzakjp/css-color': 5.1.8
'@asamuzakjp/dom-selector': 7.0.8
'@bramus/specificity': 2.4.2
'@csstools/css-syntax-patches-for-csstree': 1.1.2(css-tree@3.2.1)
'@exodus/bytes': 1.15.0
css-tree: 3.2.1
data-urls: 7.0.0
decimal.js: 10.6.0
html-encoding-sniffer: 6.0.0
is-potential-custom-element-name: 1.0.1
lru-cache: 11.3.3
parse5: 8.0.0
saxes: 6.0.0
symbol-tree: 3.2.4
tough-cookie: 6.0.1
undici: 7.24.7
w3c-xmlserializer: 5.0.0
webidl-conversions: 8.0.1
whatwg-mimetype: 5.0.0
whatwg-url: 16.0.1
xml-name-validator: 5.0.0
transitivePeerDependencies:
- '@noble/hashes'
jsesc@3.1.0: {}
json5@2.2.3: {}
@ -1689,6 +2130,8 @@ snapshots:
load-tsconfig@0.2.5: {}
lru-cache@11.3.3: {}
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
@ -1697,6 +2140,8 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
mdn-data@2.27.1: {}
merge-anything@5.1.7:
dependencies:
is-what: 4.1.16
@ -1722,12 +2167,18 @@ snapshots:
object-assign@4.1.1: {}
obug@2.1.1: {}
parenthesis@3.1.8: {}
parse5@7.3.0:
dependencies:
entities: 6.0.1
parse5@8.0.0:
dependencies:
entities: 6.0.1
pathe@2.0.3: {}
picocolors@1.1.1: {}
@ -1754,8 +2205,12 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
punycode@2.3.1: {}
readdirp@4.1.2: {}
require-from-string@2.0.2: {}
resolve-from@5.0.0: {}
rolldown@1.0.0-rc.13:
@ -1810,6 +2265,10 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.60.1
fsevents: 2.3.3
saxes@6.0.0:
dependencies:
xmlchars: 2.2.0
semver@6.3.1: {}
seroval-plugins@1.5.2(seroval@1.5.2):
@ -1818,6 +2277,8 @@ snapshots:
seroval@1.5.2: {}
siginfo@2.0.0: {}
skia-canvas@3.0.8:
dependencies:
detect-libc: 2.1.2
@ -1847,6 +2308,10 @@ snapshots:
source-map@0.7.6: {}
stackback@0.0.2: {}
std-env@4.0.0: {}
string-split-by@1.0.0:
dependencies:
parenthesis: 3.1.8
@ -1861,6 +2326,8 @@ snapshots:
tinyglobby: 0.2.16
ts-interface-checker: 0.1.13
symbol-tree@3.2.4: {}
thenify-all@1.6.0:
dependencies:
thenify: 3.3.1
@ -1869,13 +2336,33 @@ snapshots:
dependencies:
any-promise: 1.3.0
tinybench@2.9.0: {}
tinyexec@0.3.2: {}
tinyexec@1.1.1: {}
tinyglobby@0.2.16:
dependencies:
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
tinyrainbow@3.1.0: {}
tldts-core@7.0.28: {}
tldts@7.0.28:
dependencies:
tldts-core: 7.0.28
tough-cookie@6.0.1:
dependencies:
tldts: 7.0.28
tr46@6.0.0:
dependencies:
punycode: 2.3.1
tree-kill@1.2.2: {}
ts-interface-checker@0.1.13: {}
@ -1917,6 +2404,8 @@ snapshots:
undici-types@7.18.2: {}
undici@7.24.7: {}
undici@8.0.2: {}
update-browserslist-db@1.2.3(browserslist@4.28.2):
@ -1960,6 +2449,59 @@ snapshots:
optionalDependencies:
vite: 8.0.7(@types/node@25.5.2)(esbuild@0.27.7)
vitest@4.1.3(@types/node@25.5.2)(jsdom@29.0.2)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.27.7)):
dependencies:
'@vitest/expect': 4.1.3
'@vitest/mocker': 4.1.3(vite@8.0.7(@types/node@25.5.2)(esbuild@0.27.7))
'@vitest/pretty-format': 4.1.3
'@vitest/runner': 4.1.3
'@vitest/snapshot': 4.1.3
'@vitest/spy': 4.1.3
'@vitest/utils': 4.1.3
es-module-lexer: 2.0.0
expect-type: 1.3.0
magic-string: 0.30.21
obug: 2.1.1
pathe: 2.0.3
picomatch: 4.0.4
std-env: 4.0.0
tinybench: 2.9.0
tinyexec: 1.1.1
tinyglobby: 0.2.16
tinyrainbow: 3.1.0
vite: 8.0.7(@types/node@25.5.2)(esbuild@0.27.7)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 25.5.2
jsdom: 29.0.2
transitivePeerDependencies:
- msw
w3c-xmlserializer@5.0.0:
dependencies:
xml-name-validator: 5.0.0
web-streams-polyfill@4.2.0: {}
webidl-conversions@8.0.1: {}
whatwg-mimetype@5.0.0: {}
whatwg-url@16.0.1:
dependencies:
'@exodus/bytes': 1.15.0
tr46: 6.0.0
webidl-conversions: 8.0.1
transitivePeerDependencies:
- '@noble/hashes'
why-is-node-running@2.3.0:
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
xml-name-validator@5.0.0: {}
xmlchars@2.2.0: {}
yallist@3.1.1: {}

12
task.md
View File

@ -1,3 +1,11 @@
/loop 持续优化字体子集化性能,可以大胆放开手脚的去做,但是优化完一定要通过基准测试。中途不要切换到其他模式,比如计划模式也不要询问我,你直接做就行了,请你持续的去优化,不要去询问我,不要去中断,好吧
/loop 持续优化字体子集化性能,可以大胆放开手脚的去做,但是优化完一定要通过`pnpx tsx ./基准测试.test.ts`。中途不要切换到其他模式,比如计划模式也不要询问我,你直接做就行了,请你持续的去优化,不要去询问我,不要去中断,好吧
啊,你每次优化能不能把基准测试保存在本地目录下,这样我方便查看。你的文档中应该在每个重大节点更新基准测试结果,这样我能方便看到你使用了哪些优化方法,得到了什么样的优化效果。
啊,你每次优化能不能把基准测试结果文档保存在本地目录下,这样我方便查看。你的文档中应该在每个重大节点更新基准测试结果,这样我能方便看到你使用了哪些优化方法,得到了什么样的优化效果。
=== 字体裁剪基准测试 ===
8个汉字: avg=23.6ms min=18.4ms max=37.2ms 输出=16,508 bytes ssim=1.0000
拉丁+数字: avg=16.4ms min=13.7ms max=18.2ms 输出=1,272 bytes ssim=1.0000
千字文前段: avg=59.4ms min=47.3ms max=76.5ms 输出=161,344 bytes ssim=1.0000

View File

@ -0,0 +1,82 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = reducePathFlat;
/**
* @file 缩减path大小扁平格式专用去除冗余节点
* @author mengke01(kekee000@gmail.com)
*/
/**
* 缩减glyf去除冗余节点扁平格式 [x, y, onCurve, ...]
* 懒分配首个点被移除前不分配 reduced 数组大多数 contour 无冗余点可直接返回
*/
function reducePathFlat(contour) {
if (!contour.length) {
return contour;
}
var len = contour.length;
var l = len / 3;
/** 懒分配:仅在首个被保留的点出现时才创建 reduced 数组 */
var reduced = null;
var ri = 0;
var removed = 0;
// 段1: i=0首点prev 是尾点)
var px = contour[0], py = contour[1], po = contour[2];
var prevX = contour[(l - 1) * 3], prevY = contour[(l - 1) * 3 + 1], prevO = contour[(l - 1) * 3 + 2];
var nextX = contour[3], nextY = contour[4], nextO = contour[5];
var dx = px - nextX;
var dy = py - nextY;
if ((po && nextO || !po && !nextO) && dx * dx + dy * dy <= 1) { removed++; }
else {
var cross = (nextY - py) * (prevX - px) - (prevY - py) * (nextX - px);
if (prevO && nextO && cross > -0.001 && cross < 0.001) { removed++; }
else {
reduced = new Array(len);
reduced[ri++] = px; reduced[ri++] = py; reduced[ri++] = po;
}
}
// 段2: i=1..l-2中间点prev/next 简单偏移)
for (var i = 1; i < l - 1; i++) {
var pi = i * 3;
px = contour[pi]; py = contour[pi + 1]; po = contour[pi + 2];
prevX = contour[pi - 3]; prevY = contour[pi - 2]; prevO = contour[pi - 1];
nextX = contour[pi + 3]; nextY = contour[pi + 4]; nextO = contour[pi + 5];
dx = px - nextX;
dy = py - nextY;
if ((po && nextO || !po && !nextO) && dx * dx + dy * dy <= 1) { removed++; continue; }
cross = (nextY - py) * (prevX - px) - (prevY - py) * (nextX - px);
if (prevO && nextO && cross > -0.001 && cross < 0.001) { removed++; continue; }
if (!reduced) reduced = new Array(len);
reduced[ri++] = px; reduced[ri++] = py; reduced[ri++] = po;
}
// 段3: i=l-1尾点next 是首点)
if (l > 1) {
var pi = (l - 1) * 3;
px = contour[pi]; py = contour[pi + 1]; po = contour[pi + 2];
prevX = contour[pi - 3]; prevY = contour[pi - 2]; prevO = contour[pi - 1];
nextX = contour[0]; nextY = contour[1]; nextO = contour[2];
dx = px - nextX;
dy = py - nextY;
if ((po && nextO || !po && !nextO) && dx * dx + dy * dy <= 1) { removed++; }
else {
cross = (nextY - py) * (prevX - px) - (prevY - py) * (nextX - px);
if (prevO && nextO && cross > -0.001 && cross < 0.001) { removed++; }
else {
if (!reduced) reduced = new Array(len);
reduced[ri++] = px; reduced[ri++] = py; reduced[ri++] = po;
}
}
}
// 没有任何缩减,直接返回原数组避免拷贝
if (!reduced) return contour;
// 截断到实际大小
reduced.length = ri;
return reduced;
}

View File

@ -1,9 +0,0 @@
/**
* @file DOM解析器兼容node端和浏览器端
* @author mengke01(kekee000@gmail.com)
*/
/* eslint-disable no-undef */
export default typeof window !== 'undefined' && window.DOMParser
? window.DOMParser
: require('@xmldom/xmldom').DOMParser;

View File

@ -1,76 +0,0 @@
/**
* @file 用于国际化的字符串管理类
* @author mengke01(kekee000@gmail.com)
*/
function appendLanguage(store, languageList) {
languageList.forEach(item => {
const language = item[0];
store[language] = Object.assign(store[language] || {}, item[1]);
});
return store;
}
/**
* 管理国际化字符根据lang切换语言版本
*
* @class I18n
* @param {Array} languageList 当前支持的语言列表
* @param {string=} defaultLanguage 默认语言
* languageList = [
* 'en-us', // 语言名称
* langObject // 语言字符串列表
* ]
*/
export default class I18n {
constructor(languageList, defaultLanguage) {
this.store = appendLanguage({}, languageList);
this.setLanguage(
defaultLanguage
|| typeof navigator !== 'undefined' && navigator.language && navigator.language.toLowerCase()
|| 'en-us'
);
}
/**
* 设置语言
*
* @param {string} language 语言
* @return {this}
*/
setLanguage(language) {
if (!this.store[language]) {
language = 'en-us';
}
this.lang = this.store[this.language = language];
return this;
}
/**
* 添加一个语言字符串
*
* @param {string} language 语言
* @param {Object} langObject 语言对象
* @return {this}
*/
addLanguage(language, langObject) {
appendLanguage(this.store, [[language, langObject]]);
return this;
}
/**
* 获取当前语言字符串
*
* @param {string} path 语言路径
* @return {string} 语言字符串
*/
get(path) {
const ref = path.split('.');
let refObject = this.lang;
let level;
while (refObject != null && (level = ref.shift())) {
refObject = refObject[level];
}
return refObject != null ? refObject : '';
}
}

View File

@ -1,85 +0,0 @@
/**
* @file ajax获取文本数据
* @author mengke01(kekee000@gmail.com)
*/
/**
* ajax获取数据
*
* @param {Object} options 参数选项
* @param {string=} options.type 类型
* @param {string=} options.method method
* @param {Function=} options.onSuccess 成功回调
* @param {Function=} options.onError 失败回调
* @param {Object=} options.params 参数集合
*/
export default function ajaxFile(options) {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
const status = xhr.status;
if (status >= 200 && status < 300 || status === 304) {
if (options.onSuccess) {
if (options.type === 'binary') {
const buffer = xhr.responseBlob || xhr.response;
options.onSuccess(buffer);
}
else if (options.type === 'xml') {
options.onSuccess(xhr.responseXML);
}
else if (options.type === 'json') {
options.onSuccess(JSON.parse(xhr.responseText));
}
else {
options.onSuccess(xhr.responseText);
}
}
}
else if (options.onError) {
options.onError(xhr, xhr.status);
}
}
};
const method = (options.method || 'GET').toUpperCase();
let params = null;
if (options.params) {
let str = [];
Object.keys(options.params).forEach(key => {
str.push(key + '=' + encodeURIComponent(options.params[key]));
});
str = str.join('&');
if (method === 'GET') {
options.url += (options.url.indexOf('?') === -1 ? '?' : '&') + str;
}
else {
params = str;
}
}
xhr.open(method, options.url, true);
if (options.type === 'binary') {
xhr.responseType = 'arraybuffer';
}
xhr.send(params);
}
export function loadFile(url, type = 'binary') {
return new Promise((resolve, reject) => {
ajaxFile({
type,
url,
onSuccess(buffer) {
resolve(buffer);
},
onError(e) {
reject(e);
}
});
});
}

View File

@ -1,238 +0,0 @@
/**
* @file 语言相关函数
* @author mengke01(kekee000@gmail.com)
*/
export function isArray(obj) {
return obj != null && toString.call(obj).slice(8, -1) === 'Array';
}
export function isObject(obj) {
return obj != null && toString.call(obj).slice(8, -1) === 'Object';
}
export function isString(obj) {
return obj != null && toString.call(obj).slice(8, -1) === 'String';
}
export function isFunction(obj) {
return obj != null && toString.call(obj).slice(8, -1) === 'Function';
}
export function isDate(obj) {
return obj != null && toString.call(obj).slice(8, -1) === 'Date';
}
export function isEmptyObject(object) {
for (const name in object) {
// eslint-disable-next-line no-prototype-builtins
if (object.hasOwnProperty(name)) {
return false;
}
}
return true;
}
/**
* 为函数提前绑定前置参数柯里化
*
* @see http://en.wikipedia.org/wiki/Currying
* @param {Function} fn 要绑定的函数
* @param {...Array} cargs cargs
* @return {Function}
*/
export function curry(fn, ...cargs) {
return function (...rargs) {
const args = cargs.concat(rargs);
// eslint-disable-next-line no-invalid-this
return fn.apply(this, args);
};
}
/**
* 方法静态化, 反绑定延迟绑定
*
* @param {Function} method 待静态化的方法
* @return {Function} 静态化包装后方法
*/
export function generic(method) {
return function (...fargs) {
return Function.call.apply(method, fargs);
};
}
/**
* 设置覆盖相关的属性值
*
* @param {Object} thisObj 覆盖对象
* @param {Object} thatObj 值对象
* @param {Array.<string>} fields 字段
* @return {Object} thisObj
*/
export function overwrite(thisObj, thatObj, fields) {
if (!thatObj) {
return thisObj;
}
// 这里`fields`未指定则仅overwrite自身可枚举的字段指定`fields`则不做限制
fields = fields || Object.keys(thatObj);
fields.forEach(field => {
// 拷贝对象
if (
thisObj[field] && typeof thisObj[field] === 'object'
&& thatObj[field] && typeof thatObj[field] === 'object'
) {
overwrite(thisObj[field], thatObj[field]);
}
else {
thisObj[field] = thatObj[field];
}
});
return thisObj;
}
/**
* 深复制对象仅复制数据
*
* @param {Object} source 源数据
* @return {Object} 复制的数据
*/
export function clone(source) {
if (!source || typeof source !== 'object') {
return source;
}
let cloned = source;
if (isArray(source)) {
cloned = source.slice().map(clone);
}
else if (isObject(source) && 'isPrototypeOf' in source) {
cloned = {};
for (const key of Object.keys(source)) {
cloned[key] = clone(source[key]);
}
}
return cloned;
}
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time.
// @see underscore.js
export function throttle(func, wait) {
let context;
let args;
let timeout;
let result;
let previous = 0;
const later = function () {
previous = new Date();
timeout = null;
result = func.apply(context, args);
};
return function (...args) {
const now = new Date();
const remaining = wait - (now - previous);
// eslint-disable-next-line no-invalid-this
context = this;
if (remaining <= 0) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
}
else if (!timeout) {
timeout = setTimeout(later, remaining);
}
return result;
};
}
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
// @see underscore.js
export function debounce(func, wait, immediate) {
let timeout;
let result;
return function (...args) {
// eslint-disable-next-line no-invalid-this
const context = this;
const later = function () {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
}
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
}
return result;
};
}
/**
* 判断两个对象的字段是否相等
*
* @param {Object} thisObj 要比较的对象
* @param {Object} thatObj 参考对象
* @param {Array} fields 指定字段
* @return {boolean} 是否相等
*/
export function equals(thisObj, thatObj, fields) {
if (thisObj === thatObj) {
return true;
}
if (thisObj == null && thatObj == null) {
return true;
}
if (thisObj == null && thatObj != null || thisObj != null && thatObj == null) {
return false;
}
// 这里`fields`未指定则仅overwrite自身可枚举的字段指定`fields`则不做限制
fields = fields || (typeof thisObj === 'object'
? Object.keys(thisObj)
: []);
if (!fields.length) {
return thisObj === thatObj;
}
let equal = true;
for (let i = 0, l = fields.length, field; equal && i < l; i++) {
field = fields[i];
if (
thisObj[field] && typeof thisObj[field] === 'object'
&& thatObj[field] && typeof thatObj[field] === 'object'
) {
equal = equal && equals(thisObj[field], thatObj[field]);
}
else {
equal = equal && (thisObj[field] === thatObj[field]);
}
}
return equal;
}

View File

@ -1,106 +0,0 @@
/**
* @file 字符串相关的函数
* @author mengke01(kekee000@gmail.com)
*/
export default {
/**
* HTML解码字符串
*
* @param {string} source 源字符串
* @return {string}
*/
decodeHTML(source) {
const str = String(source)
.replace(/&quot;/g, '"')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&');
// 处理转义的中文和实体字符
return str.replace(/&#([\d]+);/g, ($0, $1) => String.fromCodePoint(parseInt($1, 10)));
},
/**
* HTML编码字符串
*
* @param {string} source 源字符串
* @return {string}
*/
encodeHTML(source) {
return String(source)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
},
/**
* 获取string字节长度
*
* @param {string} source 源字符串
* @return {number} 长度
*/
getLength(source) {
// eslint-disable-next-line no-control-regex
return String(source).replace(/[^\x00-\xff]/g, '11').length;
},
/**
* 字符串格式化支持如 ${xxx.xxx} 的语法
*
* @param {string} source 模板字符串
* @param {Object} data 数据
* @return {string} 格式化后字符串
*/
format(source, data) {
return source.replace(/\$\{([\w.]+)\}/g, ($0, $1) => {
const ref = $1.split('.');
let refObject = data;
let level;
while (refObject != null && (level = ref.shift())) {
refObject = refObject[level];
}
return refObject != null ? refObject : '';
});
},
/**
* 使用指定字符填充字符串,默认`0`
*
* @param {string} str 字符串
* @param {number} size 填充到的大小
* @param {string=} ch 填充字符
* @return {string} 字符串
*/
pad(str, size, ch) {
str = String(str);
if (str.length > size) {
return str.slice(str.length - size);
}
return new Array(size - str.length + 1).join(ch || '0') + str;
},
/**
* 获取字符串哈希编码
*
* @param {string} str 字符串
* @return {number} 哈希值
*/
hashcode(str) {
if (!str) {
return 0;
}
let hash = 0;
for (let i = 0, l = str.length; i < l; i++) {
hash = 0x7FFFFFFFF & (hash * 31 + str.charCodeAt(i));
}
return hash;
}
};

View File

@ -1,190 +0,0 @@
/**
* @file 计算曲线包围盒
* @author mengke01(kekee000@gmail.com)
*
* modify from:
* zrender
* https://github.com/ecomfe/zrender/blob/master/src/tool/computeBoundingBox.js
*/
import pathIterator from './pathIterator';
/**
* 计算包围盒
*
* @param {Array} points 点集
* @return {Object} bounding box
*/
function computeBoundingBox(points) {
if (points.length === 0) {
return false;
}
let left = points[0].x;
let right = points[0].x;
let top = points[0].y;
let bottom = points[0].y;
for (let i = 1; i < points.length; i++) {
const p = points[i];
if (p.x < left) {
left = p.x;
}
if (p.x > right) {
right = p.x;
}
if (p.y < top) {
top = p.y;
}
if (p.y > bottom) {
bottom = p.y;
}
}
return {
x: left,
y: top,
width: right - left,
height: bottom - top
};
}
/**
* 计算二阶贝塞尔曲线的包围盒
* http://pissang.net/blog/?p=91
*
* @param {Object} p0 p0
* @param {Object} p1 p1
* @param {Object} p2 p2
* @return {Object} bound对象
*/
function computeQuadraticBezierBoundingBox(p0, p1, p2) {
// Find extremities, where derivative in x dim or y dim is zero
let tmp = (p0.x + p2.x - 2 * p1.x);
// p1 is center of p0 and p2 in x dim
let t1;
if (tmp === 0) {
t1 = 0.5;
}
else {
t1 = (p0.x - p1.x) / tmp;
}
tmp = (p0.y + p2.y - 2 * p1.y);
// p1 is center of p0 and p2 in y dim
let t2;
if (tmp === 0) {
t2 = 0.5;
}
else {
t2 = (p0.y - p1.y) / tmp;
}
t1 = Math.max(Math.min(t1, 1), 0);
t2 = Math.max(Math.min(t2, 1), 0);
const ct1 = 1 - t1;
const ct2 = 1 - t2;
const x1 = ct1 * ct1 * p0.x + 2 * ct1 * t1 * p1.x + t1 * t1 * p2.x;
const y1 = ct1 * ct1 * p0.y + 2 * ct1 * t1 * p1.y + t1 * t1 * p2.y;
const x2 = ct2 * ct2 * p0.x + 2 * ct2 * t2 * p1.x + t2 * t2 * p2.x;
const y2 = ct2 * ct2 * p0.y + 2 * ct2 * t2 * p1.y + t2 * t2 * p2.y;
return computeBoundingBox(
[
p0,
p2,
{
x: x1,
y: y1
},
{
x: x2,
y: y2
}
]
);
}
/**
* 计算曲线包围盒
*
* @private
* @param {...Array} args 坐标点集, 支持多个path
* @return {Object} {x, y, width, height}
*/
function computePathBoundingBox(...args) {
const points = [];
const iterator = function (c, p0, p1, p2) {
if (c === 'L') {
points.push(p0);
points.push(p1);
}
else if (c === 'Q') {
const bound = computeQuadraticBezierBoundingBox(p0, p1, p2);
points.push(bound);
points.push({
x: bound.x + bound.width,
y: bound.y + bound.height
});
}
};
if (args.length === 1) {
pathIterator(args[0], (c, p0, p1, p2) => {
if (c === 'L') {
points.push(p0);
points.push(p1);
}
else if (c === 'Q') {
const bound = computeQuadraticBezierBoundingBox(p0, p1, p2);
points.push(bound);
points.push({
x: bound.x + bound.width,
y: bound.y + bound.height
});
}
});
}
else {
for (let i = 0, l = args.length; i < l; i++) {
pathIterator(args[i], iterator);
}
}
return computeBoundingBox(points);
}
/**
* 计算曲线点边界
*
* @private
* @param {...Array} args path对象, 支持多个path
* @return {Object} {x, y, width, height}
*/
export function computePathBox(...args) {
let points = [];
if (args.length === 1) {
points = args[0];
}
else {
for (let i = 0, l = args.length; i < l; i++) {
Array.prototype.splice.apply(points, [points.length, 0].concat(args[i]));
}
}
return computeBoundingBox(points);
}
export const computeBounding = computeBoundingBox;
export const quadraticBezier = computeQuadraticBezierBoundingBox;
export const computePath = computePathBoundingBox;

View File

@ -1,247 +0,0 @@
/**
* @file 使用插值法获取椭圆弧度以支持svg arc命令
* @author mengke01(kekee000@gmail.com)
*
* modify from:
* https://github.com/fontello/svgpath/blob/master/lib/a2c.js
* references:
* http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
*/
import bezierCubic2Q2 from '../math/bezierCubic2Q2';
const TAU = Math.PI * 2;
function vectorAngle(ux, uy, vx, vy) {
// Calculate an angle between two vectors
const sign = (ux * vy - uy * vx < 0) ? -1 : 1;
const umag = Math.sqrt(ux * ux + uy * uy);
const vmag = Math.sqrt(ux * ux + uy * uy);
const dot = ux * vx + uy * vy;
let div = dot / (umag * vmag);
if (div > 1 || div < -1) {
// rounding errors, e.g. -1.0000000000000002 can screw up this
div = Math.max(div, -1);
div = Math.min(div, 1);
}
return sign * Math.acos(div);
}
function correctRadii(midx, midy, rx, ry) {
// Correction of out-of-range radii
rx = Math.abs(rx);
ry = Math.abs(ry);
const Λ = (midx * midx) / (rx * rx) + (midy * midy) / (ry * ry);
if (Λ > 1) {
rx *= Math.sqrt(Λ);
ry *= Math.sqrt(Λ);
}
return [rx, ry];
}
function getArcCenter(x1, y1, x2, y2, fa, fs, rx, ry, sin, cos) {
// Convert from endpoint to center parameterization,
// see http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
// Step 1.
//
// Moving an ellipse so origin will be the middlepoint between our two
// points. After that, rotate it to line up ellipse axes with coordinate
// axes.
//
const x1p = cos * (x1 - x2) / 2 + sin * (y1 - y2) / 2;
const y1p = -sin * (x1 - x2) / 2 + cos * (y1 - y2) / 2;
const rx_sq = rx * rx;
const ry_sq = ry * ry;
const x1p_sq = x1p * x1p;
const y1p_sq = y1p * y1p;
// Step 2.
//
// Compute coordinates of the centre of this ellipse (cx', cy')
// in the new coordinate system.
//
let radicant = (rx_sq * ry_sq) - (rx_sq * y1p_sq) - (ry_sq * x1p_sq);
if (radicant < 0) {
// due to rounding errors it might be e.g. -1.3877787807814457e-17
radicant = 0;
}
radicant /= (rx_sq * y1p_sq) + (ry_sq * x1p_sq);
radicant = Math.sqrt(radicant) * (fa === fs ? -1 : 1);
const cxp = radicant * rx / ry * y1p;
const cyp = radicant * -ry / rx * x1p;
// Step 3.
//
// Transform back to get centre coordinates (cx, cy) in the original
// coordinate system.
//
const cx = cos * cxp - sin * cyp + (x1 + x2) / 2;
const cy = sin * cxp + cos * cyp + (y1 + y2) / 2;
// Step 4.
//
// Compute angles (θ1, Δθ).
//
const v1x = (x1p - cxp) / rx;
const v1y = (y1p - cyp) / ry;
const v2x = (-x1p - cxp) / rx;
const v2y = (-y1p - cyp) / ry;
const θ1 = vectorAngle(1, 0, v1x, v1y);
let Δθ = vectorAngle(v1x, v1y, v2x, v2y);
if (fs === 0 && Δθ > 0) {
Δθ -= TAU;
}
if (fs === 1 && Δθ < 0) {
Δθ += TAU;
}
return [cx, cy, θ1, Δθ];
}
function approximateUnitArc(θ1, Δθ) {
// Approximate one unit arc segment with bézier curves,
// see http://math.stackexchange.com/questions/873224/
// calculate-control-points-of-cubic-bezier-curve-approximating-a-part-of-a-circle
const α = 4 / 3 * Math.tan(Δθ / 4);
const x1 = Math.cos(θ1);
const y1 = Math.sin(θ1);
const x2 = Math.cos(θ1 + Δθ);
const y2 = Math.sin(θ1 + Δθ);
return [x1, y1, x1 - y1 * α, y1 + x1 * α, x2 + y2 * α, y2 - x2 * α, x2, y2];
}
function a2c(x1, y1, x2, y2, fa, fs, rx, ry, φ) {
const sin = Math.sin(φ * TAU / 360);
const cos = Math.cos(φ * TAU / 360);
// Make sure radii are valid
//
const x1p = cos * (x1 - x2) / 2 + sin * (y1 - y2) / 2;
const y1p = -sin * (x1 - x2) / 2 + cos * (y1 - y2) / 2;
if (x1p === 0 && y1p === 0) {
// we're asked to draw line to itself
return [];
}
if (rx === 0 || ry === 0) {
// one of the radii is zero
return [];
}
const radii = correctRadii(x1p, y1p, rx, ry);
rx = radii[0];
ry = radii[1];
// Get center parameters (cx, cy, θ1, Δθ)
//
const cc = getArcCenter(x1, y1, x2, y2, fa, fs, rx, ry, sin, cos);
const result = [];
let θ1 = cc[2];
let Δθ = cc[3];
// Split an arc to multiple segments, so each segment
// will be less than τ/4 (= 90°)
//
const segments = Math.max(Math.ceil(Math.abs(Δθ) / (TAU / 4)), 1);
Δθ /= segments;
for (let i = 0; i < segments; i++) {
result.push(approximateUnitArc(θ1, Δθ));
θ1 += Δθ;
}
// We have a bezier approximation of a unit circle,
// now need to transform back to the original ellipse
//
return result.map(curve => {
for (let i = 0; i < curve.length; i += 2) {
let x = curve[i + 0];
let y = curve[i + 1];
// scale
x *= rx;
y *= ry;
// rotate
const xp = cos * x - sin * y;
const yp = sin * x + cos * y;
// translate
curve[i + 0] = xp + cc[0];
curve[i + 1] = yp + cc[1];
}
return curve;
});
}
/**
* 获取椭圆弧度
*
* @param {number} rx 椭圆长半轴
* @param {number} ry 椭圆短半轴
* @param {number} angle 旋转角度
* @param {number} largeArc 是否大圆弧
* @param {number} sweep 是否延伸圆弧
* @param {Object} p0 分割点1
* @param {Object} p1 分割点2
* @return {Array} 分割后的路径
*/
export default function getArc(rx, ry, angle, largeArc, sweep, p0, p1) {
const result = a2c(p0.x, p0.y, p1.x, p1.y, largeArc, sweep, rx, ry, angle);
const path = [];
if (result.length) {
path.push({
x: result[0][0],
y: result[0][1],
onCurve: true
});
// 将三次曲线转换成二次曲线
result.forEach(c => {
const q2Array = bezierCubic2Q2({
x: c[0],
y: c[1]
}, {
x: c[2],
y: c[3]
}, {
x: c[4],
y: c[5]
}, {
x: c[6],
y: c[7]
});
q2Array[0][2].onCurve = true;
path.push(q2Array[0][1]);
path.push(q2Array[0][2]);
if (q2Array[1]) {
q2Array[1][2].onCurve = true;
path.push(q2Array[1][1]);
path.push(q2Array[1][2]);
}
});
}
return path;
}

View File

@ -1,49 +0,0 @@
/**
* @file matrix变换操作
* @author mengke01(kekee000@gmail.com)
*/
/**
* 仿射矩阵相乘
*
* @param {Array=} matrix1 矩阵1
* @param {Array=} matrix2 矩阵2
* @return {Array} 新矩阵
*/
export function mul(matrix1 = [1, 0, 0, 1], matrix2 = [1, 0, 0, 1]) {
// 旋转变换 4 个参数
if (matrix1.length === 4) {
return [
matrix1[0] * matrix2[0] + matrix1[2] * matrix2[1],
matrix1[1] * matrix2[0] + matrix1[3] * matrix2[1],
matrix1[0] * matrix2[2] + matrix1[2] * matrix2[3],
matrix1[1] * matrix2[2] + matrix1[3] * matrix2[3]
];
}
// 旋转位移变换, 6 个参数
return [
matrix1[0] * matrix2[0] + matrix1[2] * matrix2[1],
matrix1[1] * matrix2[0] + matrix1[3] * matrix2[1],
matrix1[0] * matrix2[2] + matrix1[2] * matrix2[3],
matrix1[1] * matrix2[2] + matrix1[3] * matrix2[3],
matrix1[0] * matrix2[4] + matrix1[2] * matrix2[5] + matrix1[4],
matrix1[1] * matrix2[4] + matrix1[3] * matrix2[5] + matrix1[5]
];
}
/**
* 多个仿射矩阵相乘
*
* @param {...Array} matrixs matrix array
* @return {Array} 新矩阵
*/
export function multiply(...matrixs) {
let result = matrixs[0];
for (let i = 1, matrix; (matrix = matrixs[i]); i++) {
result = mul(result, matrix);
}
return result;
}

View File

@ -1,71 +0,0 @@
/**
* @file 圆路径集合逆时针
* @author mengke01(kekee000@gmail.com)
*/
export default [
{
x: 582,
y: 0
},
{
x: 758,
y: 75
},
{
x: 890,
y: 208
},
{
x: 965,
y: 384
},
{
x: 965,
y: 583
},
{
x: 890,
y: 760
},
{
x: 758,
y: 891
},
{
x: 582,
y: 966
},
{
x: 383,
y: 966
},
{
x: 207,
y: 891
},
{
x: 75,
y: 760
},
{
x: 0,
y: 583
},
{
x: 0,
y: 384
},
{
x: 75,
y: 208
},
{
x: 207,
y: 75
},
{
x: 383,
y: 0
}
];

View File

@ -1,29 +0,0 @@
/**
* @file 调整路径缩放和平移
* @author mengke01(kekee000@gmail.com)
*/
/**
* 对path坐标进行调整
*
* @param {Object} contour 坐标点
* @param {number} scaleX x缩放比例
* @param {number} scaleY y缩放比例
* @param {number} offsetX x偏移
* @param {number} offsetY y偏移
*
* @return {Object} contour 坐标点
*/
export default function pathAdjust(contour, scaleX, scaleY, offsetX, offsetY) {
scaleX = scaleX === undefined ? 1 : scaleX;
scaleY = scaleY === undefined ? 1 : scaleY;
const x = offsetX || 0;
const y = offsetY || 0;
let p;
for (let i = 0, l = contour.length; i < l; i++) {
p = contour[i];
p.x = scaleX * (p.x + x);
p.y = scaleY * (p.y + y);
}
return contour;
}

View File

@ -1,27 +0,0 @@
/**
* @file 对路径进行四舍五入
* @author mengke01(kekee000@gmail.com)
*/
/**
* 对path坐标进行调整
*
* @param {Array} contour 轮廓点数组
* @param {number} point 四舍五入的点数
* @return {Object} contour 坐标点
*/
export default function pathCeil(contour, point) {
let p;
for (let i = 0, l = contour.length; i < l; i++) {
p = contour[i];
if (!point) {
p.x = Math.round(p.x);
p.y = Math.round(p.y);
}
else {
p.x = Number(p.x.toFixed(point));
p.y = Number(p.y.toFixed(point));
}
}
return contour;
}

View File

@ -1,71 +0,0 @@
/**
* @file 遍历路径的路径集合包括segment和 bezier curve
* @author mengke01(kekee000@gmail.com)
*/
/**
* 遍历路径的路径集合
*
* @param {Array} contour 坐标点集
* @param {Function} callBack 回调函数参数集合command, p0, p1, p2, i
* p0, p1, p2 直线或者贝塞尔曲线参数
* i 当前遍历的点
* 其中command = L 或者 Q表示直线或者贝塞尔曲线
*/
export default function pathIterator(contour, callBack) {
let curPoint;
let prevPoint;
let nextPoint;
let cursorPoint; // cursorPoint 为当前单个绘制命令的起点
for (let i = 0, l = contour.length; i < l; i++) {
curPoint = contour[i];
prevPoint = i === 0 ? contour[l - 1] : contour[i - 1];
nextPoint = i === l - 1 ? contour[0] : contour[i + 1];
// 起始坐标
if (i === 0) {
if (curPoint.onCurve) {
cursorPoint = curPoint;
}
else if (prevPoint.onCurve) {
cursorPoint = prevPoint;
}
else {
cursorPoint = {
x: (prevPoint.x + curPoint.x) / 2,
y: (prevPoint.y + curPoint.y) / 2
};
}
}
// 直线
if (curPoint.onCurve && nextPoint.onCurve) {
if (false === callBack('L', curPoint, nextPoint, 0, i)) {
break;
}
cursorPoint = nextPoint;
}
else if (!curPoint.onCurve) {
if (nextPoint.onCurve) {
if (false === callBack('Q', cursorPoint, curPoint, nextPoint, i)) {
break;
}
cursorPoint = nextPoint;
}
else {
const last = {
x: (curPoint.x + nextPoint.x) / 2,
y: (curPoint.y + nextPoint.y) / 2
};
if (false === callBack('Q', cursorPoint, curPoint, last, i)) {
break;
}
cursorPoint = last;
}
}
}
}

View File

@ -1,37 +0,0 @@
/**
* @file 路径旋转
* @author mengke01(kekee000@gmail.com)
*/
/**
* 对path坐标进行调整
*
* @param {Object} contour 坐标点
* @param {number} angle 角度
* @param {number} centerX x偏移
* @param {number} centerY y偏移
*
* @return {Object} contour 坐标点
*/
export default function pathRotate(contour, angle, centerX, centerY) {
angle = angle === undefined ? 0 : angle;
const x = centerX || 0;
const y = centerY || 0;
const cos = Math.cos(angle);
const sin = Math.sin(angle);
let px;
let py;
let p;
// x1=cos(angle)*x-sin(angle)*y;
// y1=cos(angle)*y+sin(angle)*x;
for (let i = 0, l = contour.length; i < l; i++) {
p = contour[i];
px = cos * (p.x - x) - sin * (p.y - y);
py = cos * (p.y - y) + sin * (p.x - x);
p.x = px + x;
p.y = py + y;
}
return contour;
}

View File

@ -1,40 +0,0 @@
/**
* @file path倾斜变换
* @author mengke01(kekee000@gmail.com)
*/
/**
* path倾斜变换
*
* @param {Object} contour 坐标点
* @param {number} angle 角度
* @param {number} offsetX x偏移
* @param {number} offsetY y偏移
*
* @return {Object} contour 坐标点
*/
export default function pathSkew(contour, angle, offsetX, offsetY) {
angle = angle === undefined ? 0 : angle;
const x = offsetX || 0;
const tan = Math.tan(angle);
let p;
let i;
let l;
// x 平移
if (x === 0) {
for (i = 0, l = contour.length; i < l; i++) {
p = contour[i];
p.x += tan * (p.y - offsetY);
}
}
// y平移
else {
for (i = 0, l = contour.length; i < l; i++) {
p = contour[i];
p.y += tan * (p.x - offsetX);
}
}
return contour;
}

View File

@ -1,26 +0,0 @@
/**
* @file 按X轴平移变换, 变换中心为图像中心点
* @author mengke01(kekee000@gmail.com)
*/
import {computePath} from './computeBoundingBox';
/**
* path倾斜变换
*
* @param {Object} contour 坐标点
* @param {number} angle 角度
*
* @return {Object} contour 坐标点
*/
export default function pathSkewX(contour, angle) {
angle = angle === undefined ? 0 : angle;
const y = computePath(contour).y;
const tan = Math.tan(angle);
let p;
// x 平移
for (let i = 0, l = contour.length; i < l; i++) {
p = contour[i];
p.x += tan * (p.y - y);
}
return contour;
}

View File

@ -1,26 +0,0 @@
/**
* @file 按Y轴平移变换, 变换中心为图像中心点
* @author mengke01(kekee000@gmail.com)
*/
import {computePath} from './computeBoundingBox';
/**
* path倾斜变换
*
* @param {Object} contour 坐标点
* @param {number} angle 角度
*
* @return {Object} contour 坐标点
*/
export default function pathSkewY(contour, angle) {
angle = angle === undefined ? 0 : angle;
const x = computePath(contour).x;
const tan = Math.tan(angle);
let p;
// y 平移
for (let i = 0, l = contour.length; i < l; i++) {
p = contour[i];
p.y += tan * (p.x - x);
}
return contour;
}

View File

@ -1,40 +0,0 @@
/**
* @file 对轮廓进行transform变换
* @author mengke01(kekee000@gmail.com)
*
* 参考资料
* http://blog.csdn.net/henren555/article/details/9699449
*
* |X| |a c e| |x|
* |Y| = |b d f| * |y|
* |1| |0 0 1| |1|
*
* X = x * a + y * c + e
* Y = x * b + y * d + f
*/
/**
* 图形仿射矩阵变换
*
* @param {Array.<Object>} contour 轮廓点
* @param {number} a m11
* @param {number} b m12
* @param {number} c m21
* @param {number} d m22
* @param {number} e dx
* @param {number} f dy
* @return {Array.<Object>} contour 轮廓点
*/
export default function transform(contour, a, b, c, d, e, f) {
let x;
let y;
let p;
for (let i = 0, l = contour.length; i < l; i++) {
p = contour[i];
x = p.x;
y = p.y;
p.x = x * a + y * c + e;
p.y = x * b + y * d + f;
}
return contour;
}

View File

@ -1,185 +0,0 @@
/**
* @file 路径相关的函数集合
* @author mengke01(kekee000@gmail.com)
*/
import {getPointHash} from './util';
/**
* 对路径进行插值补全省略的点
*
* @param {Array} path 路径
* @return {Array} 路径
*/
export function interpolate(path) {
const newPath = [];
for (let i = 0, l = path.length; i < l; i++) {
const next = i === l - 1 ? 0 : i + 1;
newPath.push(path[i]);
// 插值
if (!path[i].onCurve && !path[next].onCurve) {
newPath.push({
x: (path[i].x + path[next].x) / 2,
y: (path[i].y + path[next].y) / 2,
onCurve: true
});
}
}
return newPath;
}
/**
* 去除路径中的插值点
*
* @param {Array} path 路径
* @return {Array} 路径
*/
export function deInterpolate(path) {
const newPath = [];
for (let i = 0, l = path.length; i < l; i++) {
const next = i === l - 1 ? 0 : i + 1;
const prev = i === 0 ? l - 1 : i - 1;
// 插值
if (
!path[prev].onCurve && path[i].onCurve && !path[next].onCurve
&& Math.abs(2 * path[i].x - path[prev].x - path[next].x) < 0.001
&& Math.abs(2 * path[i].y - path[prev].y - path[next].y) < 0.001
) {
continue;
}
newPath.push(path[i]);
}
return newPath;
}
/**
* 判断路径的方向是否顺时针
*
* see:
* http://debian.fmi.uni-sofia.bg/~sergei/cgsr/docs/clockwise.htm
*
* @param {Array} path 路径
* @return {number} 0 无方向 1 clockwise, -1 counter clockwise
*/
export function isClockWise(path) {
if (path.length < 3) {
return 0;
}
let zCount = 0;
for (let i = 0, l = path.length; i < l; i++) {
const cur = path[i];
const prev = i === 0 ? path[l - 1] : path[i - 1];
const next = i === l - 1 ? path[0] : path[i + 1];
const z = (cur.x - prev.x) * (next.y - cur.y)
- (cur.y - prev.y) * (next.x - cur.x);
if (z < 0) {
zCount--;
}
else if (z > 0) {
zCount++;
}
}
return zCount === 0
? 0
: zCount < 0 ? 1 : -1;
}
/**
* 获取路径哈希
*
* @param {Array} path 路径数组
* @return {number} 哈希值
*/
export function getPathHash(path) {
let hash = 0;
const seed = 131;
path.forEach(p => {
hash = 0x7FFFFFFF & (hash * seed + getPointHash(p) + (p.onCurve ? 1 : 0));
});
return hash;
}
/**
* 移除重复点
*
* @param {Array} points 点集合
* @return {Array} 移除后点集合
*/
export function removeOverlapPoints(points) {
const hash = {};
const ret = [];
for (let i = 0, l = points.length; i < l; i++) {
const hashcode = points[i].x * 31 + points[i].y;
if (!hash[hashcode]) {
ret.push(points[i]);
hash[hashcode] = 1;
}
}
return ret;
}
/**
* 对path进行双向链表连接
*
* @param {Array} path 轮廓数组
* @return {Array} path
*/
export function makeLink(path) {
for (let i = 0, l = path.length; i < l; i++) {
const cur = path[i];
const prev = i === 0 ? path[l - 1] : path[i - 1];
const next = i === l - 1 ? path[0] : path[i + 1];
cur.index = i;
cur.next = next;
cur.prev = prev;
}
return path;
}
/**
* 对path进行缩放
*
* @param {Array} path 轮廓数组
* @param {number} ratio 缩放大小
*
* @return {Array} path
*/
export function scale(path, ratio) {
for (let i = 0, l = path.length; i < l; i++) {
const cur = path[i];
cur.x *= ratio;
cur.y *= ratio;
}
return path;
}
export function clone(path) {
return path ? path.map(p => {
const newP = {
x: p.x,
y: p.y
};
if (p.onCurve) {
newP.onCurve = true;
}
return newP;
}) : path;
}

View File

@ -1,93 +0,0 @@
/**
* @file 路径组变化函数
* @author mengke01(kekee000@gmail.com)
*/
import {computePath} from './computeBoundingBox';
import pathAdjust from './pathAdjust';
import pathRotate from './pathRotate';
/**
* 翻转路径
*
* @param {Array} paths 路径数组
* @param {number} xScale x翻转
* @param {number} yScale y翻转
* @return {Array} 变换后的路径
*/
function mirrorPaths(paths, xScale, yScale) {
const {x, y, width, height} = computePath(...paths);
if (xScale === -1) {
paths.forEach(p => {
pathAdjust(p, -1, 1, -x, 0);
pathAdjust(p, 1, 1, x + width, 0);
p.reverse();
});
}
if (yScale === -1) {
paths.forEach(p => {
pathAdjust(p, 1, -1, 0, -y);
pathAdjust(p, 1, 1, 0, y + height);
p.reverse();
});
}
return paths;
}
export default {
/**
* 旋转路径
*
* @param {Array} paths 路径数组
* @param {number} angle 弧度
* @return {Array} 变换后的路径
*/
rotate(paths, angle) {
if (!angle) {
return paths;
}
const bound = computePath(...paths);
const cx = bound.x + (bound.width) / 2;
const cy = bound.y + (bound.height) / 2;
paths.forEach(p => {
pathRotate(p, angle, cx, cy);
});
return paths;
},
/**
* 路径组变换
*
* @param {Array} paths 路径数组
* @param {number} x x 方向缩放
* @param {number} y y 方向缩放
* @return {Array} 变换后的路径
*/
move(paths, x, y) {
const bound = computePath(...paths);
paths.forEach(path => {
pathAdjust(path, 1, 1, x - bound.x, y - bound.y);
});
return paths;
},
mirror(paths) {
return mirrorPaths(paths, -1, 1);
},
flip(paths) {
return mirrorPaths(paths, 1, -1);
}
};

View File

@ -1,73 +0,0 @@
/**
* @file 缩减path大小去除冗余节点
* @author mengke01(kekee000@gmail.com)
*/
/**
* 判断点是否多余的点
*
* @param {Object} prev 上一个
* @param {Object} p 当前
* @param {Object} next 下一个
* @return {boolean}
*/
function redundant(prev, p, next) {
// 是否重合的点, 只有两个点同在曲线上或者同不在曲线上移出
if (
(p.onCurve && next.onCurve || !p.onCurve && !next.onCurve)
&& Math.pow(p.x - next.x, 2) + Math.pow(p.y - next.y, 2) <= 1
) {
return true;
}
// 三点同线 检查直线点
if (
(p.onCurve && prev.onCurve && next.onCurve)
&& Math.abs((next.y - p.y) * (prev.x - p.x) - (prev.y - p.y) * (next.x - p.x)) <= 0.001
) {
return true;
}
// 三点同线 检查控制点
if (
(!p.onCurve && prev.onCurve && next.onCurve)
&& Math.abs((next.y - p.y) * (prev.x - p.x) - (prev.y - p.y) * (next.x - p.x)) <= 0.001
) {
return true;
}
return false;
}
/**
* 缩减glyf去除冗余节点
*
* @param {Array} contour 路径对象
* @return {Array} 路径对象
*/
export default function reducePath(contour) {
if (!contour.length) {
return contour;
}
let prev;
let next;
let p;
for (let i = contour.length - 1, last = i; i >= 0; i--) {
// 这里注意逆序
p = contour[i];
next = i === last ? contour[0] : contour[i + 1];
prev = i === 0 ? contour[last] : contour[i - 1];
if (redundant(prev, p, next)) {
contour.splice(i, 1);
last--;
continue;
}
}
return contour;
}

View File

@ -1,92 +0,0 @@
/**
* @file grahpics点相关工具箱
* @author mengke01(kekee000@gmail.com)
*/
/**
* 将点进行误差舍入
*
* @param {Object} p 点对象
* @return {Object}
*/
export function ceilPoint(p) {
let t = p.x;
// 处理形如 4.99999 = 5, 5.00001 = 5的情况
if (Math.abs(Math.round(t) - t) < 0.00002) {
p.x = Math.round(t);
}
else {
p.x = Math.round(p.x * 100000) / 100000;
}
t = p.y;
if (Math.abs(Math.round(t) - t) < 0.00005) {
p.y = Math.round(t);
}
else {
p.y = Math.round(p.y * 100000) / 100000;
}
return p;
}
/**
* 将数值进行误差舍入
*
* @param {Object} x 数值
* @return {number}
*/
export function ceil(x) {
if (Math.abs(Math.round(x) - x) < 0.00002) {
return Math.round(x);
}
return Math.round(x * 100000) / 100000;
}
/**
* 判断点是否在bounding box内部
*
* @param {Object} bound bounding box对象
* @param {Object} p 点对象
* @param {boolean=} fixed 是否四舍五入
* @return {boolean} 是否
*/
export function isPointInBound(bound, p, fixed) {
if (fixed) {
return ceil(p.x) <= ceil(bound.x + bound.width)
&& ceil(p.x) >= ceil(bound.x)
&& ceil(p.y) <= ceil(bound.y + bound.height)
&& ceil(p.y) >= ceil(bound.y);
}
return p.x <= bound.x + bound.width
&& p.x >= bound.x
&& p.y <= bound.y + bound.height
&& p.y >= bound.y;
}
/**
* 判断点是否重合
*
* @param {Object} p0 p0
* @param {Object} p1 p1
* @return {boolean} 是否
*/
export function isPointOverlap(p0, p1) {
return ceil(p0.x) === ceil(p1.x) && ceil(p0.y) === ceil(p1.y);
}
/**
* 获取点的hash值
*
* @param {Object} p p
* @param {Object} p1 p1
* @return {number}
*/
export function getPointHash(p) {
return Math.floor(7 * Math.floor(p.x * 10) + 131 * Math.floor(p.y * 100));
}

View File

@ -1,76 +0,0 @@
/**
* @file 主函数
* @author mengke01(kekee000@gmail.com)
*/
import {Font, createFont} from './ttf/font';
import TTF from './ttf/ttf';
import TTFReader from './ttf/ttfreader';
import TTFWriter from './ttf/ttfwriter';
import ttf2eot from './ttf/ttf2eot';
import eot2ttf from './ttf/eot2ttf';
import ttf2woff from './ttf/ttf2woff';
import woff2ttf from './ttf/woff2ttf';
import ttf2svg from './ttf/ttf2svg';
import svg2ttfobject from './ttf/svg2ttfobject';
import Reader from './ttf/reader';
import Writer from './ttf/writer';
import OTFReader from './ttf/otfreader';
import otf2ttfobject from './ttf/otf2ttfobject';
import ttf2base64 from './ttf/ttf2base64';
import ttf2icon from './ttf/ttf2icon';
import ttftowoff2 from './ttf/ttftowoff2';
import woff2tottf from './ttf/woff2tottf';
import woff2 from '../woff2/index';
import bufferUtil from './nodejs/buffer';
export {
createFont,
Font,
TTF,
TTFReader,
TTFWriter,
ttf2eot,
eot2ttf,
ttf2woff,
woff2ttf,
ttf2svg,
svg2ttfobject,
Reader,
Writer,
OTFReader,
otf2ttfobject,
ttf2base64,
ttf2icon,
ttftowoff2,
woff2tottf,
woff2,
};
export const toArrayBuffer = bufferUtil.toArrayBuffer;
export const toBuffer = bufferUtil.toBuffer;
export default {
createFont,
Font,
TTF,
TTFReader,
TTFWriter,
ttf2eot,
eot2ttf,
ttf2woff,
woff2ttf,
ttf2svg,
svg2ttfobject,
Reader,
Writer,
OTFReader,
otf2ttfobject,
ttf2base64,
ttf2icon,
ttftowoff2,
woff2tottf,
woff2,
toArrayBuffer: bufferUtil.toArrayBuffer,
toBuffer: bufferUtil.toBuffer,
};

View File

@ -1,61 +0,0 @@
/**
* @file 主函数
* @author mengke01(kekee000@gmail.com)
*/
import {Font, createFont} from './ttf/font';
import TTF from './ttf/ttf';
import TTFReader from './ttf/ttfreader';
import TTFWriter from './ttf/ttfwriter';
import ttf2eot from './ttf/ttf2eot';
import eot2ttf from './ttf/eot2ttf';
import ttf2woff from './ttf/ttf2woff';
import woff2ttf from './ttf/woff2ttf';
import ttf2svg from './ttf/ttf2svg';
import svg2ttfobject from './ttf/svg2ttfobject';
import Reader from './ttf/reader';
import Writer from './ttf/writer';
import OTFReader from './ttf/otfreader';
import otf2ttfobject from './ttf/otf2ttfobject';
import ttf2base64 from './ttf/ttf2base64';
import ttf2icon from './ttf/ttf2icon';
import ttftowoff2 from './ttf/ttftowoff2';
import woff2tottf from './ttf/woff2tottf';
import woff2 from '../woff2/index';
import bufferUtil from './nodejs/buffer';
const modules = {
createFont,
Font,
TTF,
TTFReader,
TTFWriter,
ttf2eot,
eot2ttf,
ttf2woff,
woff2ttf,
ttf2svg,
svg2ttfobject,
Reader,
Writer,
OTFReader,
otf2ttfobject,
ttf2base64,
ttf2icon,
ttftowoff2,
woff2tottf,
woff2,
toArrayBuffer: bufferUtil.toArrayBuffer,
toBuffer: bufferUtil.toBuffer,
};
// Export named exports for ESM
export { Font, woff2, createFont };
// Export default object
export default modules;
if (typeof exports !== 'undefined') {
// eslint-disable-next-line import/no-commonjs
module.exports = modules;
}

View File

@ -1,93 +0,0 @@
/**
* @file 三次贝塞尔转二次贝塞尔
* @author mengke01(kekee000@gmail.com)
*
* references:
* https://github.com/search?utf8=%E2%9C%93&q=svg2ttf
* http://www.caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html
*
*/
function toQuad(p1, c1, c2, p2) {
// Quad control point is (3*c2 - p2 + 3*c1 - p1)/4
const x = (3 * c2.x - p2.x + 3 * c1.x - p1.x) / 4;
const y = (3 * c2.y - p2.y + 3 * c1.y - p1.y) / 4;
return [
p1,
{x, y},
p2
];
}
/**
* 三次贝塞尔转二次贝塞尔
*
* @param {Object} p1 开始点
* @param {Object} c1 控制点1
* @param {Object} c2 控制点2
* @param {Object} p2 结束点
* @return {Array} 二次贝塞尔控制点
*/
export default function bezierCubic2Q2(p1, c1, c2, p2) {
// 判断极端情况,控制点和起止点一样
if (p1.x === c1.x && p1.y === c1.y && c2.x === p2.x && c2.y === p2.y) {
return [
[
p1,
{
x: (p1.x + p2.x) / 2,
y: (p1.y + p2.y) / 2
},
p2
]
];
}
const mx = p2.x - 3 * c2.x + 3 * c1.x - p1.x;
const my = p2.y - 3 * c2.y + 3 * c1.y - p1.y;
// control points near
if (mx * mx + my * my <= 4) {
return [
toQuad(p1, c1, c2, p2)
];
}
// Split to 2 qubic beziers by midpoints
// (p2 + 3*c2 + 3*c1 + p1)/8
const mp = {
x: (p2.x + 3 * c2.x + 3 * c1.x + p1.x) / 8,
y: (p2.y + 3 * c2.y + 3 * c1.y + p1.y) / 8
};
return [
toQuad(
p1,
{
x: (p1.x + c1.x) / 2,
y: (p1.y + c1.y) / 2
},
{
x: (p1.x + 2 * c1.x + c2.x) / 4,
y: (p1.y + 2 * c1.y + c2.y) / 4
},
mp
),
toQuad(
mp,
{
x: (p2.x + c1.x + 2 * c2.x) / 4,
y: (p2.y + c1.y + 2 * c2.y) / 4
},
{
x: (p2.x + c2.x) / 2,
y: (p2.y + c2.y) / 2
},
p2
)
];
}

View File

@ -1,43 +0,0 @@
/**
* @file Buffer和ArrayBuffer转换
* @author mengke01(kekee000@gmail.com)
*/
/* eslint-disable no-undef */
export default {
/**
* Buffer转换成ArrayBuffer
*
* @param {Buffer} buffer 缓冲数组
* @return {ArrayBuffer}
*/
toArrayBuffer(buffer) {
const length = buffer.length;
const view = new DataView(new ArrayBuffer(length), 0, length);
for (let i = 0, l = length; i < l; i++) {
view.setUint8(i, buffer[i], false);
}
return view.buffer;
},
/**
* ArrayBuffer转换成Buffer
*
* @param {ArrayBuffer} arrayBuffer 缓冲数组
* @return {Buffer}
*/
toBuffer(arrayBuffer) {
if (Array.isArray(arrayBuffer)) {
return Buffer.from(arrayBuffer);
}
const length = arrayBuffer.byteLength;
const view = new DataView(arrayBuffer, 0, length);
const buffer = Buffer.alloc(length);
for (let i = 0, l = length; i < l; i++) {
buffer[i] = view.getUint8(i, false);
}
return buffer;
}
};

View File

@ -1,19 +0,0 @@
/**
* @file 默认的ttf字体配置
* @author mengke01(kekee000@gmail.com)
*/
export default {
// 默认的字体编码
fontId: 'fonteditor',
// 默认的名字集合
name: {
// 默认的字体家族
fontFamily: 'fonteditor',
fontSubFamily: 'Medium',
uniqueSubFamily: 'FontEditor 1.0 : fonteditor',
version: 'Version 1.0; FontEditor (v1.0)',
postScriptName: 'fonteditor'
}
};

View File

@ -1,181 +0,0 @@
/**
* @file 空的ttf格式json对象
* @author mengke01(kekee000@gmail.com)
*/
/* eslint-disable */
export default {
"version": 1,
"numTables": 10,
"searchRange": 128,
"entrySelector": 3,
"rangeShift": 64,
"head": {
"version": 1,
"fontRevision": 1,
"checkSumAdjustment": 0,
"magickNumber": 1594834165,
"flags": 11,
"unitsPerEm": 1024,
"created": 1428940800000,
"modified": 1428940800000,
"xMin": 34,
"yMin": 0,
"xMax": 306,
"yMax": 682,
"macStyle": 0,
"lowestRecPPEM": 8,
"fontDirectionHint": 2,
"indexToLocFormat": 0,
"glyphDataFormat": 0
},
"glyf": [{
"contours": [
[{
"x": 34,
"y": 0,
"onCurve": true
}, {
"x": 34,
"y": 682,
"onCurve": true
}, {
"x": 306,
"y": 682,
"onCurve": true
}, {
"x": 306,
"y": 0,
"onCurve": true
}],
[{
"x": 68,
"y": 34,
"onCurve": true
}, {
"x": 272,
"y": 34,
"onCurve": true
}, {
"x": 272,
"y": 648,
"onCurve": true
}, {
"x": 68,
"y": 648,
"onCurve": true
}]
],
"xMin": 34,
"yMin": 0,
"xMax": 306,
"yMax": 682,
"advanceWidth": 374,
"leftSideBearing": 34,
"name": ".notdef"
}],
"cmap": {},
"name": {
"fontFamily": "fonteditor",
"fontSubFamily": "Medium",
"uniqueSubFamily": "FontEditor 1.0 : fonteditor",
"version": "Version 1.0 ; FontEditor (v0.0.1)",
"postScriptName": "fonteditor",
"fullName": "fonteditor"
},
"hhea": {
"version": 1,
"ascent": 812,
"descent": -212,
"lineGap": 92,
"advanceWidthMax": 374,
"minLeftSideBearing": 34,
"minRightSideBearing": 68,
"xMaxExtent": 306,
"caretSlopeRise": 1,
"caretSlopeRun": 0,
"caretOffset": 0,
"reserved0": 0,
"reserved1": 0,
"reserved2": 0,
"reserved3": 0,
"metricDataFormat": 0,
"numOfLongHorMetrics": 1
},
"post": {
"italicAngle": 0,
"postoints": 65411,
"underlinePosition": 50,
"underlineThickness": 0,
"isFixedPitch": 0,
"minMemType42": 0,
"maxMemType42": 0,
"minMemType1": 0,
"maxMemType1": 1,
"format": 2
},
"maxp": {
"version": 1.0,
"numGlyphs": 0,
"maxPoints": 0,
"maxContours": 0,
"maxCompositePoints": 0,
"maxCompositeContours": 0,
"maxZones": 0,
"maxTwilightPoints": 0,
"maxStorage": 0,
"maxFunctionDefs": 0,
"maxStackElements": 0,
"maxSizeOfInstructions": 0,
"maxComponentElements": 0,
"maxComponentDepth": 0
},
"OS/2": {
"version": 4,
"xAvgCharWidth": 1031,
"usWeightClass": 400,
"usWidthClass": 5,
"fsType": 0,
"ySubscriptXSize": 665,
"ySubscriptYSize": 716,
"ySubscriptXOffset": 0,
"ySubscriptYOffset": 143,
"ySuperscriptXSize": 665,
"ySuperscriptYSize": 716,
"ySuperscriptXOffset": 0,
"ySuperscriptYOffset": 491,
"yStrikeoutSize": 51,
"yStrikeoutPosition": 265,
"sFamilyClass": 0,
"bFamilyType": 2,
"bSerifStyle": 0,
"bWeight": 6,
"bProportion": 3,
"bContrast": 0,
"bStrokeVariation": 0,
"bArmStyle": 0,
"bLetterform": 0,
"bMidline": 0,
"bXHeight": 0,
"ulUnicodeRange1": 1,
"ulUnicodeRange2": 268435456,
"ulUnicodeRange3": 0,
"ulUnicodeRange4": 0,
"achVendID": "PfEd",
"fsSelection": 192,
"usFirstCharIndex": 65535,
"usLastCharIndex": -1,
"sTypoAscender": 812,
"sTypoDescender": -212,
"sTypoLineGap": 92,
"usWinAscent": 812,
"usWinDescent": 212,
"ulCodePageRange1": 1,
"ulCodePageRange2": 0,
"sxHeight": 792,
"sCapHeight": 0,
"usDefaultChar": 0,
"usBreakChar": 32,
"usMaxContext": 1
}
};

View File

@ -1,23 +0,0 @@
/**
* @file 复合图元标记位
* @author mengke01(kekee000@gmail.com)
*
* 复合图元标记位
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html
*/
export default {
ARG_1_AND_2_ARE_WORDS: 0x01,
ARGS_ARE_XY_VALUES: 0x02,
ROUND_XY_TO_GRID: 0x04,
WE_HAVE_A_SCALE: 0x08,
RESERVED: 0x10,
MORE_COMPONENTS: 0x20,
WE_HAVE_AN_X_AND_Y_SCALE: 0x40,
WE_HAVE_A_TWO_BY_TWO: 0x80,
WE_HAVE_INSTRUCTIONS: 0x100,
USE_MY_METRICS: 0x200,
OVERLAP_COMPOUND: 0x400,
SCALED_COMPONENT_OFFSET: 0x800,
UNSCALED_COMPONENT_OFFSET: 0x1000
};

View File

@ -1,26 +0,0 @@
/**
* @file Unicode Platform-specific Encoding Identifiers
* @author mengke01(kekee000@gmail.com)
*/
// mac encoding id
export const mac = {
'Default': 0, // default use
'Version1.1': 1,
'ISO10646': 2,
'UnicodeBMP': 3,
'UnicodenonBMP': 4,
'UnicodeVariationSequences': 5,
'FullUnicodecoverage': 6
};
// windows encoding id
export const win = {
Symbol: 0,
UCS2: 1, // default use
ShiftJIS: 2,
PRC: 3,
BigFive: 4,
Johab: 5,
UCS4: 6
};

View File

@ -1,18 +0,0 @@
/**
* @file 轮廓标记位
* @author mengke01(kekee000@gmail.com)
*
* see:
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html
*/
export default {
ONCURVE: 0x01, // on curve ,off curve
XSHORT: 0x02, // x-Short Vector
YSHORT: 0x04, // y-Short Vector
REPEAT: 0x08, // next byte is flag repeat count
XSAME: 0x10, // This x is same (Positive x-Short vector)
YSAME: 0x20, // This y is same (Positive y-Short vector)
Reserved1: 0x40,
Reserved2: 0x80
};

View File

@ -1,36 +0,0 @@
/**
* @file ttf `name`编码表
* @author mengke01(kekee000@gmail.com)
*/
const nameId = {
0: 'copyright',
1: 'fontFamily',
2: 'fontSubFamily',
3: 'uniqueSubFamily',
4: 'fullName',
5: 'version',
6: 'postScriptName',
7: 'tradeMark',
8: 'manufacturer',
9: 'designer',
10: 'description',
11: 'urlOfFontVendor',
12: 'urlOfFontDesigner',
13: 'licence',
14: 'urlOfLicence',
16: 'preferredFamily',
17: 'preferredSubFamily',
18: 'compatibleFull',
19: 'sampleText'
};
// 反转names
const nameIdHash = {};
Object.keys(nameId).forEach(id => {
nameIdHash[nameId[id]] = +id;
});
nameId.names = nameIdHash;
export default nameId;

View File

@ -1,154 +0,0 @@
/**
* @file 字体外观分类器
* @author mengke01(kekee000@gmail.com)
*
* @see:
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6OS2.html
*/
export default {
bFamilyType: [
'Any',
'No Fit',
'Text and Display',
'Script',
'Decorative',
'Pictorial'
],
bSerifStyle: [
'Any',
'No Fit',
'Cove',
'Obtuse Cove',
'Square Cove',
'Obtuse Square Cove',
'Square',
'Thin',
'Bone',
'Exaggerated',
'Triangle',
'Normal Sans',
'Obtuse Sans',
'Perp Sans',
'Flared',
'Rounded'
],
bWeight: [
'Any',
'No Fit',
'Very Light',
'Light',
'Thin',
'Book',
'Medium',
'Demi',
'Bold',
'Heavy',
'Black',
'Nord'
],
bProportion: [
'Any',
'No Fit',
'Old Style',
'Modern',
'Even Width',
'Expanded',
'Condensed',
'Very Expanded',
'Very Condensed',
'Monospaced'
],
bContrast: [
'Any',
'No Fit',
'None',
'Very Low',
'Low',
'Medium Low',
'Medium',
'Medium High',
'High',
'Very High'
],
bStrokeVariation: [
'Any',
'No Fit',
'Gradual/Diagonal',
'Gradual/Transitional',
'Gradual/Vertical',
'Gradual/Horizontal',
'Rapid/Vertical',
'Rapid/Horizontal',
'Instant/Vertical'
],
bArmStyle: [
'Any',
'No Fit',
'Straight Arms/Horizontal',
'Straight Arms/Wedge',
'Straight Arms/Vertical',
'Straight Arms/Single Serif',
'Straight Arms/Double Serif',
'Non-Straight Arms/Horizontal',
'Non-Straight Arms/Wedge',
'Non-Straight Arms/Vertical',
'Non-Straight Arms/Single Serif',
'Non-Straight Arms/Double Serif'
],
bLetterform: [
'Any',
'No Fit',
'Normal/Contact',
'Normal/Weighted',
'Normal/Boxed',
'Normal/Flattened',
'Normal/Rounded',
'Normal/Off Center',
'Normal/Square',
'Oblique/Contact',
'Oblique/Weighted',
'Oblique/Boxed',
'Oblique/Flattened',
'Oblique/Rounded',
'Oblique/Off Center',
'Oblique/Square'
],
bMidline: [
'Any',
'No Fit',
'Standard/Trimmed',
'Standard/Pointed',
'Standard/Serifed',
'High/Trimmed',
'High/Pointed',
'High/Serifed',
'Constant/Trimmed',
'Constant/Pointed',
'Constant/Serifed',
'Low/Trimmed',
'Low/Pointed',
'Low/Serifed'
],
bXHeight: [
'Any',
'No Fit',
'Constant/Small',
'Constant/Standard',
'Constant/Large',
'Ducking/Small',
'Ducking/Standard',
'Ducking/Large'
]
};

View File

@ -1,11 +0,0 @@
/**
* @file 字体所属平台
* @author mengke01(kekee000@gmail.com)
*/
export default{
Unicode: 0,
Macintosh: 1, // mac
reserved: 2,
Microsoft: 3 // win
};

View File

@ -1,268 +0,0 @@
/**
* @file Mac glyf命名表
* @author mengke01(kekee000@gmail.com)
*
* see:
* http://www.microsoft.com/typography/otspec/WGL4.htm
*/
export default {
0: '.notdef',
1: '.null',
2: 'nonmarkingreturn',
3: 'space',
4: 'exclam',
5: 'quotedbl',
6: 'numbersign',
7: 'dollar',
8: 'percent',
9: 'ampersand',
10: 'quotesingle',
11: 'parenleft',
12: 'parenright',
13: 'asterisk',
14: 'plus',
15: 'comma',
16: 'hyphen',
17: 'period',
18: 'slash',
19: 'zero',
20: 'one',
21: 'two',
22: 'three',
23: 'four',
24: 'five',
25: 'six',
26: 'seven',
27: 'eight',
28: 'nine',
29: 'colon',
30: 'semicolon',
31: 'less',
32: 'equal',
33: 'greater',
34: 'question',
35: 'at',
36: 'A',
37: 'B',
38: 'C',
39: 'D',
40: 'E',
41: 'F',
42: 'G',
43: 'H',
44: 'I',
45: 'J',
46: 'K',
47: 'L',
48: 'M',
49: 'N',
50: 'O',
51: 'P',
52: 'Q',
53: 'R',
54: 'S',
55: 'T',
56: 'U',
57: 'V',
58: 'W',
59: 'X',
60: 'Y',
61: 'Z',
62: 'bracketleft',
63: 'backslash',
64: 'bracketright',
65: 'asciicircum',
66: 'underscore',
67: 'grave',
68: 'a',
69: 'b',
70: 'c',
71: 'd',
72: 'e',
73: 'f',
74: 'g',
75: 'h',
76: 'i',
77: 'j',
78: 'k',
79: 'l',
80: 'm',
81: 'n',
82: 'o',
83: 'p',
84: 'q',
85: 'r',
86: 's',
87: 't',
88: 'u',
89: 'v',
90: 'w',
91: 'x',
92: 'y',
93: 'z',
94: 'braceleft',
95: 'bar',
96: 'braceright',
97: 'asciitilde',
98: 'Adieresis',
99: 'Aring',
100: 'Ccedilla',
101: 'Eacute',
102: 'Ntilde',
103: 'Odieresis',
104: 'Udieresis',
105: 'aacute',
106: 'agrave',
107: 'acircumflex',
108: 'adieresis',
109: 'atilde',
110: 'aring',
111: 'ccedilla',
112: 'eacute',
113: 'egrave',
114: 'ecircumflex',
115: 'edieresis',
116: 'iacute',
117: 'igrave',
118: 'icircumflex',
119: 'idieresis',
120: 'ntilde',
121: 'oacute',
122: 'ograve',
123: 'ocircumflex',
124: 'odieresis',
125: 'otilde',
126: 'uacute',
127: 'ugrave',
128: 'ucircumflex',
129: 'udieresis',
130: 'dagger',
131: 'degree',
132: 'cent',
133: 'sterling',
134: 'section',
135: 'bullet',
136: 'paragraph',
137: 'germandbls',
138: 'registered',
139: 'copyright',
140: 'trademark',
141: 'acute',
142: 'dieresis',
143: 'notequal',
144: 'AE',
145: 'Oslash',
146: 'infinity',
147: 'plusminus',
148: 'lessequal',
149: 'greaterequal',
150: 'yen',
151: 'mu',
152: 'partialdiff',
153: 'summation',
154: 'product',
155: 'pi',
156: 'integral',
157: 'ordfeminine',
158: 'ordmasculine',
159: 'Omega',
160: 'ae',
161: 'oslash',
162: 'questiondown',
163: 'exclamdown',
164: 'logicalnot',
165: 'radical',
166: 'florin',
167: 'approxequal',
168: 'Delta',
169: 'guillemotleft',
170: 'guillemotright',
171: 'ellipsis',
172: 'nonbreakingspace',
173: 'Agrave',
174: 'Atilde',
175: 'Otilde',
176: 'OE',
177: 'oe',
178: 'endash',
179: 'emdash',
180: 'quotedblleft',
181: 'quotedblright',
182: 'quoteleft',
183: 'quoteright',
184: 'divide',
185: 'lozenge',
186: 'ydieresis',
187: 'Ydieresis',
188: 'fraction',
189: 'currency',
190: 'guilsinglleft',
191: 'guilsinglright',
192: 'fi',
193: 'fl',
194: 'daggerdbl',
195: 'periodcentered',
196: 'quotesinglbase',
197: 'quotedblbase',
198: 'perthousand',
199: 'Acircumflex',
200: 'Ecircumflex',
201: 'Aacute',
202: 'Edieresis',
203: 'Egrave',
204: 'Iacute',
205: 'Icircumflex',
206: 'Idieresis',
207: 'Igrave',
208: 'Oacute',
209: 'Ocircumflex',
210: 'apple',
211: 'Ograve',
212: 'Uacute',
213: 'Ucircumflex',
214: 'Ugrave',
215: 'dotlessi',
216: 'circumflex',
217: 'tilde',
218: 'macron',
219: 'breve',
220: 'dotaccent',
221: 'ring',
222: 'cedilla',
223: 'hungarumlaut',
224: 'ogonek',
225: 'caron',
226: 'Lslash',
227: 'lslash',
228: 'Scaron',
229: 'scaron',
230: 'Zcaron',
231: 'zcaron',
232: 'brokenbar',
233: 'Eth',
234: 'eth',
235: 'Yacute',
236: 'yacute',
237: 'Thorn',
238: 'thorn',
239: 'minus',
240: 'multiply',
241: 'onesuperior',
242: 'twosuperior',
243: 'threesuperior',
244: 'onehalf',
245: 'onequarter',
246: 'threequarters',
247: 'franc',
248: 'Gbreve',
249: 'gbreve',
250: 'Idotaccent',
251: 'Scedilla',
252: 'scedilla',
253: 'Cacute',
254: 'cacute',
255: 'Ccaron',
256: 'ccaron',
257: 'dcroat'
};

View File

@ -1,298 +0,0 @@
/**
* @file unicode 编码与postName对照表
* @author mengke01(kekee000@gmail.com)
*
* see:
* http://www.microsoft.com/typography/otspec/WGL4.htm
*/
export default {
0: 1,
1: 1,
2: 1,
3: 1,
4: 1,
5: 1,
6: 1,
7: 1,
8: 1,
9: 2,
10: 1,
11: 1,
12: 1,
13: 2,
14: 1,
15: 1,
16: 1,
17: 1,
18: 1,
19: 1,
20: 1,
21: 1,
22: 1,
23: 1,
24: 1,
25: 1,
26: 1,
27: 1,
28: 1,
29: 1,
30: 1,
31: 1,
32: 3,
33: 4,
34: 5,
35: 6,
36: 7,
37: 8,
38: 9,
39: 10,
40: 11,
41: 12,
42: 13,
43: 14,
44: 15,
45: 16,
46: 17,
47: 18,
48: 19,
49: 20,
50: 21,
51: 22,
52: 23,
53: 24,
54: 25,
55: 26,
56: 27,
57: 28,
58: 29,
59: 30,
60: 31,
61: 32,
62: 33,
63: 34,
64: 35,
65: 36,
66: 37,
67: 38,
68: 39,
69: 40,
70: 41,
71: 42,
72: 43,
73: 44,
74: 45,
75: 46,
76: 47,
77: 48,
78: 49,
79: 50,
80: 51,
81: 52,
82: 53,
83: 54,
84: 55,
85: 56,
86: 57,
87: 58,
88: 59,
89: 60,
90: 61,
91: 62,
92: 63,
93: 64,
94: 65,
95: 66,
96: 67,
97: 68,
98: 69,
99: 70,
100: 71,
101: 72,
102: 73,
103: 74,
104: 75,
105: 76,
106: 77,
107: 78,
108: 79,
109: 80,
110: 81,
111: 82,
112: 83,
113: 84,
114: 85,
115: 86,
116: 87,
117: 88,
118: 89,
119: 90,
120: 91,
121: 92,
122: 93,
123: 94,
124: 95,
125: 96,
126: 97,
160: 172,
161: 163,
162: 132,
163: 133,
164: 189,
165: 150,
166: 232,
167: 134,
168: 142,
169: 139,
170: 157,
171: 169,
172: 164,
174: 138,
175: 218,
176: 131,
177: 147,
178: 242,
179: 243,
180: 141,
181: 151,
182: 136,
184: 222,
185: 241,
186: 158,
187: 170,
188: 245,
189: 244,
190: 246,
191: 162,
192: 173,
193: 201,
194: 199,
195: 174,
196: 98,
197: 99,
198: 144,
199: 100,
200: 203,
201: 101,
202: 200,
203: 202,
204: 207,
205: 204,
206: 205,
207: 206,
208: 233,
209: 102,
210: 211,
211: 208,
212: 209,
213: 175,
214: 103,
215: 240,
216: 145,
217: 214,
218: 212,
219: 213,
220: 104,
221: 235,
222: 237,
223: 137,
224: 106,
225: 105,
226: 107,
227: 109,
228: 108,
229: 110,
230: 160,
231: 111,
232: 113,
233: 112,
234: 114,
235: 115,
236: 117,
237: 116,
238: 118,
239: 119,
240: 234,
241: 120,
242: 122,
243: 121,
244: 123,
245: 125,
246: 124,
247: 184,
248: 161,
249: 127,
250: 126,
251: 128,
252: 129,
253: 236,
254: 238,
255: 186,
262: 253,
263: 254,
268: 255,
269: 256,
273: 257,
286: 248,
287: 249,
304: 250,
305: 215,
321: 226,
322: 227,
338: 176,
339: 177,
350: 251,
351: 252,
352: 228,
353: 229,
376: 187,
381: 230,
382: 231,
402: 166,
710: 216,
711: 225,
728: 219,
729: 220,
730: 221,
731: 224,
733: 223,
960: 155,
8211: 178,
8212: 179,
8216: 182,
8217: 183,
8218: 196,
8220: 180,
8221: 181,
8222: 197,
8224: 130,
8225: 194,
8226: 135,
8230: 171,
8240: 198,
8249: 190,
8250: 191,
8355: 247,
8482: 140,
8486: 159,
8706: 152,
8710: 168,
8719: 154,
8721: 153,
8722: 239,
8725: 188,
8729: 195,
8730: 165,
8734: 146,
8747: 156,
8776: 167,
8800: 143,
8804: 148,
8805: 149,
9674: 185,
61441: 192,
61442: 193,
64257: 192,
64258: 193,
65535: 0 // 0xFFFF指向.notdef
};

View File

@ -1,16 +0,0 @@
/**
* @file 字体粗细度量
* @author mengke01(kekee000@gmail.com)
*/
export default {
100: 'Ultra-light',
200: 'Extra-light',
300: 'Light',
400: 'Semi-light',
500: 'Medium (normal)',
600: 'Semi-bold',
700: 'Bold',
800: 'Extra-Bold',
900: 'Ultra-bold'
};

View File

@ -1,16 +0,0 @@
/**
* @file 字体宽度度量
* @author mengke01(kekee000@gmail.com)
*/
export default {
1: 'Ultra-condensed',
2: 'Extra-condensed',
3: 'Condensed',
4: 'Semi-condensed',
5: 'Medium (normal)',
6: 'Semi-expanded',
7: 'Expanded',
8: 'Extra-expanded',
9: 'Ultra-expanded'
};

View File

@ -1,15 +0,0 @@
/**
* @file eot数组转base64编码
* @author mengke01(kekee000@gmail.com)
*/
import bytes2base64 from './util/bytes2base64';
/**
* eot数组转base64编码
*
* @param {Array} arrayBuffer ArrayBuffer对象
* @return {string} base64编码
*/
export default function eot2base64(arrayBuffer) {
return 'data:font/eot;charset=utf-8;base64,' + bytes2base64(arrayBuffer);
}

View File

@ -1,79 +0,0 @@
/**
* @file eot转ttf
* @author mengke01(kekee000@gmail.com)
*/
import Reader from './reader';
import Writer from './writer';
import error from './error';
/**
* eot格式转换成ttf字体格式
*
* @param {ArrayBuffer} eotBuffer eot缓冲数组
* @param {Object} options 选项
*
* @return {ArrayBuffer} ttf格式byte流
*/
// eslint-disable-next-line no-unused-vars
export default function eot2ttf(eotBuffer, options = {}) {
// 这里用小尾方式读取
const eotReader = new Reader(eotBuffer, 0, eotBuffer.byteLength, true);
// check magic number
const magicNumber = eotReader.readUint16(34);
if (magicNumber !== 0x504C) {
error.raise(10110);
}
// check version
const version = eotReader.readUint32(8);
if (version !== 0x20001 && version !== 0x10000 && version !== 0x20002) {
error.raise(10110);
}
const eotSize = eotBuffer.byteLength || eotBuffer.length;
const fontSize = eotReader.readUint32(4);
let fontOffset = 82;
const familyNameSize = eotReader.readUint16(fontOffset);
fontOffset += 4 + familyNameSize;
const styleNameSize = eotReader.readUint16(fontOffset);
fontOffset += 4 + styleNameSize;
const versionNameSize = eotReader.readUint16(fontOffset);
fontOffset += 4 + versionNameSize;
const fullNameSize = eotReader.readUint16(fontOffset);
fontOffset += 2 + fullNameSize;
// version 0x20001
if (version === 0x20001 || version === 0x20002) {
const rootStringSize = eotReader.readUint16(fontOffset + 2);
fontOffset += 4 + rootStringSize;
}
// version 0x20002
if (version === 0x20002) {
fontOffset += 10;
const signatureSize = eotReader.readUint16(fontOffset);
fontOffset += 2 + signatureSize;
fontOffset += 4;
const eudcFontSize = eotReader.readUint32(fontOffset);
fontOffset += 4 + eudcFontSize;
}
if (fontOffset + fontSize > eotSize) {
error.raise(10001);
}
// support slice
if (eotBuffer.slice) {
return eotBuffer.slice(fontOffset, fontOffset + fontSize);
}
// not support ArrayBuffer.slice eg. IE10
const bytes = eotReader.readBytes(fontOffset, fontSize);
return new Writer(new ArrayBuffer(fontSize)).writeBytes(bytes).getBuffer();
}

View File

@ -1,53 +0,0 @@
/**
* @file ttf 相关错误号定义
* @author mengke01(kekee000@gmail.com)
*/
import string from '../common/string';
import i18n from './i18n';
export default {
/**
* 抛出一个异常
*
* @param {Object} e 异常号或者异常对象
* @param {...Array} fargs args 参数
*
* 例如
* e = 1001
* e = {
* number: 1001,
* data: 错误数据
* }
*/
raise(e, ...fargs) {
let number;
let data;
if (typeof e === 'object') {
number = e.number || 0;
data = e.data;
}
else {
number = e;
}
let message = i18n.lang[number];
if (fargs.length > 0) {
const args = typeof fargs[0] === 'object'
? fargs[0]
: fargs;
message = string.format(message, args);
}
const event = new Error(message);
event.number = number;
if (data) {
event.data = data;
}
throw event;
}
};

View File

@ -1,361 +0,0 @@
/**
* @file 字体管理对象处理字体相关的读取查询转换
*
* @author mengke01(kekee000@gmail.com)
*/
import bufferTool from '../nodejs/buffer';
import getEmptyttfObject from './getEmptyttfObject';
import TTF from './ttf';
import woff2ttf from './woff2ttf';
import otf2ttfobject from './otf2ttfobject';
import eot2ttf from './eot2ttf';
import svg2ttfobject from './svg2ttfobject';
import TTFReader from './ttfreader';
import TTFWriter from './ttfwriter';
import ttf2eot from './ttf2eot';
import ttf2woff from './ttf2woff';
import ttf2svg from './ttf2svg';
import ttf2symbol from './ttf2symbol';
import ttftowoff2 from './ttftowoff2';
import woff2tottf from './woff2tottf';
import ttf2base64 from './ttf2base64';
import eot2base64 from './eot2base64';
import woff2base64 from './woff2base64';
import svg2base64 from './svg2base64';
import bytes2base64 from './util/bytes2base64';
import woff2tobase64 from './woff2tobase64';
import optimizettf from './util/optimizettf';
// 必须是nodejs环境下的Buffer对象才能触发buffer转换
const SUPPORT_BUFFER =
typeof process === 'object' &&
typeof process.versions === 'object' &&
typeof process.versions.node !== 'undefined' &&
typeof Buffer === 'function';
class Font {
/**
* 字体对象构造函数
*
* @param {ArrayBuffer|Buffer|string|Document} buffer 字体数据
* @param {Object} options 读取参数
*/
constructor(buffer, options = { type: 'ttf' }) {
// 字形对象
if (typeof buffer === 'object' && buffer.glyf) {
this.set(buffer);
}
// buffer
else if (buffer) {
this.read(buffer, options);
}
// 空
else {
this.readEmpty();
}
}
/**
* Create a Font instance
*
* @param {ArrayBuffer|Buffer|string|Document} buffer 字体数据
* @param {Object} options 读取参数
* @return {Font}
*/
static create(buffer, options) {
return new Font(buffer, options);
}
/**
* 设置一个空的 ttfObject 对象
*
* @return {Font}
*/
readEmpty() {
this.data = getEmptyttfObject();
return this;
}
/**
* 读取字体数据
*
* @param {ArrayBuffer|Buffer|string|Document} buffer 字体数据
* @param {Object} options 读取参数
* @param {string} options.type 字体类型
*
* ttf, woff , eot 读取配置
* @param {boolean} options.hinting 是否保留 hinting 信息
* @param {boolean} options.kerning 是否保留 kerning 信息
* @param {boolean} options.compound2simple 复合字形转简单字形
*
* woff 读取配置
* @param {Function} options.inflate 解压相关函数
*
* svg 读取配置
* @param {boolean} options.combinePath 是否合并成单个字形仅限于普通svg导入
* @return {Font}
*/
read(buffer, options) {
// nodejs buffer
if (SUPPORT_BUFFER) {
if (buffer instanceof Buffer) {
buffer = bufferTool.toArrayBuffer(buffer);
}
}
if (options.type === 'ttf') {
this.data = new TTFReader(options).read(buffer);
} else if (options.type === 'otf') {
this.data = otf2ttfobject(buffer, options);
} else if (options.type === 'eot') {
buffer = eot2ttf(buffer, options);
this.data = new TTFReader(options).read(buffer);
} else if (options.type === 'woff') {
buffer = woff2ttf(buffer, options);
this.data = new TTFReader(options).read(buffer);
} else if (options.type === 'woff2') {
buffer = woff2tottf(buffer, options);
this.data = new TTFReader(options).read(buffer);
} else if (options.type === 'svg') {
this.data = svg2ttfobject(buffer, options);
} else {
throw new Error('not support font type' + options.type);
}
this.type = options.type;
return this;
}
/**
* 写入字体数据
*
* @param {Object} options 写入参数
* @param {string} options.type 字体类型, 默认 ttf
* @param {boolean} options.toBuffer nodejs 环境中返回 Buffer 对象, 默认 true
*
* ttf 字体参数
* @param {boolean} options.hinting 是否保留 hinting 信息
* @param {boolean} options.kerning 是否保留 kerning 信息
* svg,woff 字体参数
* @param {Object} options.metadata 字体相关的信息
*
* woff 字体参数
* @param {Function} options.deflate 压缩相关函数
* @return {Buffer|ArrayBuffer|string}
*/
write(options = {}) {
if (!options.type) {
options.type = this.type;
}
let buffer = null;
if (options.type === 'ttf') {
buffer = new TTFWriter(options).write(this.data);
} else if (options.type === 'eot') {
buffer = new TTFWriter(options).write(this.data);
buffer = ttf2eot(buffer, options);
} else if (options.type === 'woff') {
buffer = new TTFWriter(options).write(this.data);
buffer = ttf2woff(buffer, options);
} else if (options.type === 'woff2') {
buffer = new TTFWriter(options).write(this.data);
buffer = ttftowoff2(buffer, options);
} else if (options.type === 'svg') {
buffer = ttf2svg(this.data, options);
} else if (options.type === 'symbol') {
buffer = ttf2symbol(this.data, options);
} else {
throw new Error('not support font type' + options.type);
}
if (SUPPORT_BUFFER) {
if (false !== options.toBuffer && buffer instanceof ArrayBuffer) {
buffer = bufferTool.toBuffer(buffer);
}
}
return buffer;
}
/**
* 转换成 base64编码
*
* @param {Object} options 写入参数
* @param {string} options.type 字体类型, 默认 ttf
* 其他 options参数, 参考 write
* @see write
*
* @param {ArrayBuffer=} buffer 如果提供了buffer数据则使用 buffer数据, 否则转换现有的 font
* @return {string}
*/
toBase64(options, buffer) {
if (!options.type) {
options.type = this.type;
}
if (buffer) {
if (SUPPORT_BUFFER) {
if (buffer instanceof Buffer) {
buffer = bufferTool.toArrayBuffer(buffer);
}
}
} else {
options.toBuffer = false;
buffer = this.write(options);
}
let base64Str;
if (options.type === 'ttf') {
base64Str = ttf2base64(buffer);
} else if (options.type === 'eot') {
base64Str = eot2base64(buffer);
} else if (options.type === 'woff') {
base64Str = woff2base64(buffer);
} else if (options.type === 'woff2') {
base64Str = woff2tobase64(buffer);
} else if (options.type === 'svg') {
base64Str = svg2base64(buffer);
} else if (options.type === 'symbol') {
base64Str = svg2base64(buffer, 'image/svg+xml');
} else {
throw new Error('not support font type' + options.type);
}
return base64Str;
}
/**
* 设置 font 对象
*
* @param {Object} data font的ttfObject对象
* @return {this}
*/
set(data) {
this.data = data;
return this;
}
/**
* 获取 font 数据
*
* @return {Object} ttfObject 对象
*/
get() {
return this.data;
}
/**
* 对字形数据进行优化
*
* @param {Object} out 输出结果
* @param {boolean|Object} out.result `true` 或者有问题的地方
* @return {Font}
*/
optimize(out) {
const result = optimizettf(this.data);
if (out) {
out.result = result;
}
return this;
}
/**
* 将字体中的复合字形转为简单字形
*
* @return {this}
*/
compound2simple() {
const ttfHelper = this.getHelper();
ttfHelper.compound2simple();
this.data = ttfHelper.get();
return this;
}
/**
* 对字形按照unicode编码排序
*
* @return {this}
*/
sort() {
const ttfHelper = this.getHelper();
ttfHelper.sortGlyf();
this.data = ttfHelper.get();
return this;
}
/**
* 查找相关字形
*
* @param {Object} condition 查询条件
* @param {Array|number} condition.unicode unicode编码列表或者单个unicode编码
* @param {string} condition.name glyf名字例如`uniE001`, `uniE`
* @param {Function} condition.filter 自定义过滤器
* @example
* condition.filter(glyf) {
* return glyf.name === 'logo';
* }
* @return {Array} glyf字形列表
*/
find(condition) {
const ttfHelper = this.getHelper();
const indexList = ttfHelper.findGlyf(condition);
return indexList.length ? ttfHelper.getGlyf(indexList) : indexList;
}
/**
* 合并 font 到当前的 font
*
* @param {Object} font Font 对象
* @param {Object} options 参数选项
* @param {boolean} options.scale 是否自动缩放
* @param {boolean} options.adjustGlyf 是否调整字形以适应边界
* ( options.scale 参数互斥)
*
* @return {Font}
*/
merge(font, options) {
const ttfHelper = this.getHelper();
ttfHelper.mergeGlyf(font.get(), options);
this.data = ttfHelper.get();
return this;
}
/**
* 获取 TTF helper 实例
*/
getHelper() {
return new TTF(this.data);
}
}
/**
* base64序列化buffer 数据
*
* @param {ArrayBuffer|Buffer|string} buffer 字体数据
* @return {Font}
*/
Font.toBase64 = function (buffer) {
if (typeof buffer === 'string') {
// node 环境中没有 btoa 函数
if (typeof btoa === 'undefined') {
return Buffer.from(buffer, 'binary').toString('base64');
}
return btoa(buffer);
}
return bytes2base64(buffer);
};
function createFont(buffer, options) {
return new Font(buffer, options);
}
export {Font, createFont};
export default Font;

View File

@ -1,16 +0,0 @@
/**
* @file 获取空的ttf对象
* @author mengke01(kekee000@gmail.com)
*/
import {clone} from '../common/lang';
import emptyttf from './data/empty';
import config from './data/default';
export default function getEmpty() {
const ttf = clone(emptyttf);
Object.assign(ttf.name, config.name);
ttf.head.created = ttf.head.modified = Date.now();
return ttf;
}

View File

@ -1,82 +0,0 @@
/**
* @file 语言字符串管理
* @author mengke01(kekee000@gmail.com)
*/
import I18n from '../common/I18n';
const zh = {
// error define
10001: '超出读取范围:${0}, ${1}',
10002: '超出写入范围:${0}, ${1}',
10003: '未知数据类型:${0}, ${1}',
10004: '不支持svg解析',
10101: '错误的ttf文件',
10102: '错误的woff文件',
10103: '错误的svg文件',
10104: '读取ttf文件错误',
10105: '读取woff文件错误',
10106: '读取svg文件错误',
10107: '写入ttf文件错误',
10108: '写入woff文件错误',
10109: '写入svg文件错误',
10112: '写入svg symbol 错误',
10110: '读取eot文件错误',
10111: '读取eot字体错误',
10200: '重复的unicode代码点字形序号${0}',
10201: 'ttf字形轮廓数据为空',
10202: '不支持标志位ARGS_ARE_XY_VALUES',
10203: '未找到表:${0}',
10204: '读取ttf表错误',
10205: '未找到解压函数',
10301: '错误的otf文件',
10302: '读取otf表错误',
10303: 'otf字形轮廓数据为空'
};
const en = {
// error define
10001: 'Reading index out of range: ${0}, ${1}',
10002: 'Writing index out of range: ${0}, ${1}',
10003: 'Unknown datatype: ${0}, ${1}',
10004: 'No svg parser',
10101: 'ttf file damaged',
10102: 'woff file damaged',
10103: 'svg file damaged',
10104: 'Read ttf error',
10105: 'Read woff error',
10106: 'Read svg error',
10107: 'Write ttf error',
10108: 'Write woff error',
10109: 'Write svg error',
10112: 'Write svg symbol error',
10110: 'Read eot error',
10111: 'Write eot error',
10200: 'Repeat unicode, glyph index: ${0}',
10201: 'ttf `glyph` data is empty',
10202: 'Not support compound glyph flag: ARGS_ARE_XY_VALUES',
10203: 'No ttf table: ${0}',
10204: 'Read ttf table data error',
10205: 'No zip deflate function',
10301: 'otf file damaged',
10302: 'Read otf table error',
10303: 'otf `glyph` data is empty'
};
export default new I18n(
[
['zh-cn', zh],
['en-us', en]
],
typeof window !== 'undefined' ? window.language : 'en-us'
);

View File

@ -1,16 +0,0 @@
/**
* @file otf转bse64字体
* @author mengke01(kekee000@gmail.com)
*/
import bytes2base64 from './util/bytes2base64';
/**
* ttf 二进制转base64编码
*
* @param {Array} arrayBuffer ArrayBuffer对象
* @return {string} base64编码
*/
export default function ttf2base64(arrayBuffer) {
return 'data:font/otf;charset=utf-8;base64,' + bytes2base64(arrayBuffer);
}

View File

@ -1,62 +0,0 @@
/**
* @file otf格式转ttf格式对象
* @author mengke01(kekee000@gmail.com)
*/
import error from './error';
import OTFReader from './otfreader';
import otfContours2ttfContours from './util/otfContours2ttfContours';
import {computePathBox} from '../graphics/computeBoundingBox';
/**
* otf格式转ttf格式对象
*
* @param {ArrayBuffer|otfObject} otfBuffer 原始数据或者解析后的otf数据
* @param {Object} options 参数
* @return {Object} ttfObject对象
*/
export default function otf2ttfobject(otfBuffer, options) {
let otfObject;
if (otfBuffer instanceof ArrayBuffer) {
const otfReader = new OTFReader(options);
otfObject = otfReader.read(otfBuffer);
otfReader.dispose();
}
else if (otfBuffer.head && otfBuffer.glyf && otfBuffer.cmap) {
otfObject = otfBuffer;
}
else {
error.raise(10111);
}
// 转换otf轮廓
otfObject.glyf.forEach((g) => {
g.contours = otfContours2ttfContours(g.contours);
const box = computePathBox(...g.contours);
if (box) {
g.xMin = box.x;
g.xMax = box.x + box.width;
g.yMin = box.y;
g.yMax = box.y + box.height;
g.leftSideBearing = g.xMin;
}
else {
g.xMin = 0;
g.xMax = 0;
g.yMin = 0;
g.yMax = 0;
g.leftSideBearing = 0;
}
});
otfObject.version = 0x1;
// 修改maxp相关配置
otfObject.maxp.version = 1.0;
otfObject.maxp.maxZones = otfObject.maxp.maxTwilightPoints ? 2 : 1;
delete otfObject.CFF;
delete otfObject.VORG;
return otfObject;
}

View File

@ -1,171 +0,0 @@
/**
* @file otf字体读取
* @author mengke01(kekee000@gmail.com)
*/
import Directory from './table/directory';
import supportTables from './table/support-otf';
import Reader from './reader';
import error from './error';
export default class OTFReader {
/**
* OTF读取函数
*
* @param {Object} options 写入参数
* @constructor
*/
constructor(options = {}) {
options.subset = options.subset || [];
this.options = options;
}
/**
* 初始化
*
* @param {ArrayBuffer} buffer buffer对象
* @return {Object} ttf对象
*/
readBuffer(buffer) {
const reader = new Reader(buffer, 0, buffer.byteLength, false);
const font = {};
// version
font.version = reader.readString(0, 4);
if (font.version !== 'OTTO') {
error.raise(10301);
}
// num tables
font.numTables = reader.readUint16();
if (font.numTables <= 0 || font.numTables > 100) {
error.raise(10302);
}
// searchRange
font.searchRange = reader.readUint16();
// entrySelector
font.entrySelector = reader.readUint16();
// rangeShift
font.rangeShift = reader.readUint16();
font.tables = new Directory(reader.offset).read(reader, font);
if (!font.tables.head || !font.tables.cmap || !font.tables.CFF) {
error.raise(10302);
}
font.readOptions = this.options;
// 读取支持的表数据
Object.keys(supportTables).forEach((tableName) => {
if (font.tables[tableName]) {
const offset = font.tables[tableName].offset;
font[tableName] = new supportTables[tableName](offset).read(reader, font);
}
});
if (!font.CFF.glyf) {
error.raise(10303);
}
reader.dispose();
return font;
}
/**
* 关联glyf相关的信息
*
* @param {Object} font font对象
*/
resolveGlyf(font) {
const codes = font.cmap;
let glyf = font.CFF.glyf;
const subsetMap = font.readOptions.subset ? font.subsetMap : null; // 当前ttf的子集列表
// unicode
Object.keys(codes).forEach((c) => {
const i = codes[c];
if (subsetMap && !subsetMap[i]) {
return;
}
if (!glyf[i].unicode) {
glyf[i].unicode = [];
}
glyf[i].unicode.push(+c);
});
// leftSideBearing
font.hmtx.forEach((item, i) => {
if (subsetMap && !subsetMap[i]) {
return;
}
glyf[i].advanceWidth = glyf[i].advanceWidth || item.advanceWidth || 0;
glyf[i].leftSideBearing = item.leftSideBearing;
});
// 设置了subsetMap之后需要选取subset中的字形
if (subsetMap) {
const subGlyf = [];
Object.keys(subsetMap).forEach((i) => {
subGlyf.push(glyf[+i]);
});
glyf = subGlyf;
}
font.glyf = glyf;
}
/**
* 清除非必须的表
*
* @param {Object} font font对象
*/
cleanTables(font) {
delete font.readOptions;
delete font.tables;
delete font.hmtx;
delete font.post.glyphNameIndex;
delete font.post.names;
delete font.subsetMap;
// 删除无用的表
const cff = font.CFF;
delete cff.glyf;
delete cff.charset;
delete cff.encoding;
delete cff.gsubrs;
delete cff.gsubrsBias;
delete cff.subrs;
delete cff.subrsBias;
}
/**
* 获取解析后的ttf文档
*
* @param {ArrayBuffer} buffer buffer对象
*
* @return {Object} ttf文档
*/
read(buffer) {
this.font = this.readBuffer(buffer);
this.resolveGlyf(this.font);
this.cleanTables(this.font);
return this.font;
}
/**
* 注销
*/
dispose() {
delete this.font;
delete this.options;
}
}

View File

@ -1,223 +0,0 @@
/**
* @file 数据读取器
* @author mengke01(kekee000@gmail.com)
*
* thanks to
* ynakajima/ttf.js
* https://github.com/ynakajima/ttf.js
*/
import {curry} from '../common/lang';
import error from './error';
// 检查数组支持情况
if (typeof ArrayBuffer === 'undefined' || typeof DataView === 'undefined') {
throw new Error('not support ArrayBuffer and DataView');
}
// 数据类型
const dataType = {
Int8: 1,
Int16: 2,
Int32: 4,
Uint8: 1,
Uint16: 2,
Uint32: 4,
Float32: 4,
Float64: 8
};
export default class Reader {
/**
* 读取器
*
* @constructor
* @param {Array.<byte>} buffer 缓冲数组
* @param {number} offset 起始偏移
* @param {number} length 数组长度
* @param {boolean} littleEndian 是否小尾
*/
constructor(buffer, offset, length, littleEndian) {
const bufferLength = buffer.byteLength || buffer.length;
this.offset = offset || 0;
this.length = length || (bufferLength - this.offset);
this.littleEndian = littleEndian || false;
this.view = new DataView(buffer, this.offset, this.length);
}
/**
* 读取指定的数据类型
*
* @param {string} type 数据类型
* @param {number=} offset 位移
* @param {boolean=} littleEndian 是否小尾
* @return {number} 返回值
*/
read(type, offset, littleEndian) {
// 使用当前位移
if (undefined === offset) {
offset = this.offset;
}
// 使用小尾
if (undefined === littleEndian) {
littleEndian = this.littleEndian;
}
// 扩展方法
if (undefined === dataType[type]) {
return this['read' + type](offset, littleEndian);
}
const size = dataType[type];
this.offset = offset + size;
return this.view['get' + type](offset, littleEndian);
}
/**
* 获取指定的字节数组
*
* @param {number} offset 偏移
* @param {number} length 字节长度
* @return {Array} 字节数组
*/
readBytes(offset, length = null) {
if (length == null) {
length = offset;
offset = this.offset;
}
if (length < 0 || offset + length > this.length) {
error.raise(10001, this.length, offset + length);
}
const buffer = [];
for (let i = 0; i < length; ++i) {
buffer.push(this.view.getUint8(offset + i));
}
this.offset = offset + length;
return buffer;
}
/**
* 读取一个string
*
* @param {number} offset 偏移
* @param {number} length 长度
* @return {string} 字符串
*/
readString(offset, length = null) {
if (length == null) {
length = offset;
offset = this.offset;
}
if (length < 0 || offset + length > this.length) {
error.raise(10001, this.length, offset + length);
}
let value = '';
for (let i = 0; i < length; ++i) {
const c = this.readUint8(offset + i);
value += String.fromCharCode(c);
}
this.offset = offset + length;
return value;
}
/**
* 读取一个字符
*
* @param {number} offset 偏移
* @return {string} 字符串
*/
readChar(offset) {
return this.readString(offset, 1);
}
/**
* 读取一个uint24整形
*
* @param {number} offset 偏移
* @return {number}
*/
readUint24(offset) {
const [i, j, k] = this.readBytes(offset || this.offset, 3);
return (i << 16) + (j << 8) + k;
}
/**
* 读取fixed类型
*
* @param {number} offset 偏移
* @return {number} float
*/
readFixed(offset) {
if (undefined === offset) {
offset = this.offset;
}
const val = this.readInt32(offset, false) / 65536.0;
return Math.ceil(val * 100000) / 100000;
}
/**
* 读取长日期
*
* @param {number} offset 偏移
* @return {Date} Date对象
*/
readLongDateTime(offset) {
if (undefined === offset) {
offset = this.offset;
}
// new Date(1970, 1, 1).getTime() - new Date(1904, 1, 1).getTime();
const delta = -2077545600000;
const time = this.readUint32(offset + 4, false);
const date = new Date();
date.setTime(time * 1000 + delta);
return date;
}
/**
* 跳转到指定偏移
*
* @param {number} offset 偏移
* @return {Object} this
*/
seek(offset) {
if (undefined === offset) {
this.offset = 0;
}
if (offset < 0 || offset > this.length) {
error.raise(10001, this.length, offset);
}
this.offset = offset;
return this;
}
/**
* 注销
*/
dispose() {
delete this.view;
}
}
// 直接支持的数据类型
Object.keys(dataType).forEach((type) => {
Reader.prototype['read' + type] = curry(Reader.prototype.read, type);
});

View File

@ -1,74 +0,0 @@
/**
* @file 根据transform参数变换轮廓
* @author mengke01(kekee000@gmail.com)
*/
import {mul, multiply} from '../../graphics/matrix';
import pathTransform from '../../graphics/pathTransform';
/**
* 根据transform参数变换轮廓
*
* @param {Array} contours 轮廓集合
* @param {Array} transforms 变换指令集合
* transforms = [{
* name: 'scale'
* params: [3,4]
* }]
*
* @return {Array} 变换后的轮廓数组
*/
export default function contoursTransform(contours, transforms) {
if (!contours || !contours.length || !transforms || !transforms.length) {
return contours;
}
let matrix = [1, 0, 0, 1, 0, 0];
for (let i = 0, l = transforms.length; i < l; i++) {
const transform = transforms[i];
const params = transform.params;
let radian = null;
switch (transform.name) {
case 'translate':
matrix = mul(matrix, [1, 0, 0, 1, params[0], params[1]]);
break;
case 'scale':
matrix = mul(matrix, [params[0], 0, 0, params[1], 0, 0]);
break;
case 'matrix':
matrix = mul(matrix,
[params[0], params[1], params[2], params[3], params[4], params[5]]);
break;
case 'rotate':
radian = params[0] * Math.PI / 180;
if (params.length > 1) {
matrix = multiply(
matrix,
[1, 0, 0, 1, -params[1], -params[2]],
[Math.cos(radian), Math.sin(radian), -Math.sin(radian), Math.cos(radian), 0, 0],
[1, 0, 0, 1, params[1], params[2]]
);
}
else {
matrix = mul(
matrix, [Math.cos(radian), Math.sin(radian), -Math.sin(radian), Math.cos(radian), 0, 0]);
}
break;
case 'skewX':
matrix = mul(matrix,
[1, 0, Math.tan(params[0] * Math.PI / 180), 1, 0, 0]);
break;
case 'skewY':
matrix = mul(matrix,
[1, Math.tan(params[0] * Math.PI / 180), 0, 1, 0, 0]);
break;
}
}
contours.forEach(p => {
pathTransform(p, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
});
return contours;
}

View File

@ -1,36 +0,0 @@
/**
* @file 椭圆转换成轮廓
* @author mengke01(kekee000@gmail.com)
*/
import {computePath} from '../../graphics/computeBoundingBox';
import pathAdjust from '../../graphics/pathAdjust';
import circlePath from '../../graphics/path/circle';
import {clone} from '../../common/lang';
/**
* 椭圆转换成轮廓
*
* @param {number} cx 椭圆中心点x
* @param {number} cy 椭圆中心点y
* @param {number} rx 椭圆x轴半径
* @param {number} ry 椭圆y周半径
* @return {Array} 轮廓数组
*/
export default function oval2contour(cx, cy, rx, ry) {
if (undefined === ry) {
ry = rx;
}
const bound = computePath(circlePath);
const scaleX = (+rx) * 2 / bound.width;
const scaleY = (+ry) * 2 / bound.height;
const centerX = bound.width * scaleX / 2;
const centerY = bound.height * scaleY / 2;
const contour = clone(circlePath);
pathAdjust(contour, scaleX, scaleY);
pathAdjust(contour, 1, 1, +cx - centerX, +cy - centerY);
return contour;
}

View File

@ -1,30 +0,0 @@
/**
* @file 解析参数数组
* @author mengke01(kekee000@gmail.com)
*/
const SEGMENT_REGEX = /-?\d+(?:\.\d+)?(?:e[-+]?\d+)?\b/g;
/**
* 获取参数值
*
* @param {string} d 参数
* @return {number} 参数值
*/
function getSegment(d) {
return +d.trim();
}
/**
* 解析参数数组
*
* @param {string} str 参数字符串
* @return {Array} 参数数组
*/
export default function (str) {
if (!str) {
return [];
}
const matchs = str.match(SEGMENT_REGEX);
return matchs ? matchs.map(getSegment) : [];
}

View File

@ -1,39 +0,0 @@
/**
* @file 解析transform参数
* @author mengke01(kekee000@gmail.com)
*/
import parseParams from './parseParams';
const TRANSFORM_REGEX = /(\w+)\s*\(([\d-.,\s]*)\)/g;
/**
* 解析transform参数
*
* @param {string} str 参数字符串
* @return {Array} transform数组, 格式如下
* [
* {
* name: 'scale',
* params: []
* }
* ]
*/
export default function parseTransform(str) {
if (!str) {
return false;
}
TRANSFORM_REGEX.lastIndex = 0;
const transforms = [];
let match;
while ((match = TRANSFORM_REGEX.exec(str))) {
transforms.push({
name: match[1],
params: parseParams(match[2])
});
}
return transforms;
}

View File

@ -1,529 +0,0 @@
/**
* @file svg path转换为轮廓
* @author mengke01(kekee000@gmail.com)
*/
import bezierCubic2Q2 from '../../math/bezierCubic2Q2';
import getArc from '../../graphics/getArc';
import parseParams from './parseParams';
/**
* 三次贝塞尔曲线转二次贝塞尔曲线
*
* @param {Array} cubicList 三次曲线数组
* @param {Array} contour 当前解析后的轮廓数组
* @return {Array} 当前解析后的轮廓数组
*/
function cubic2Points(cubicList, contour) {
let i;
let l;
const q2List = [];
cubicList.forEach(c => {
const list = bezierCubic2Q2(c[0], c[1], c[2], c[3]);
for (i = 0, l = list.length; i < l; i++) {
q2List.push(list[i]);
}
});
let q2;
let prevq2;
for (i = 0, l = q2List.length; i < l; i++) {
q2 = q2List[i];
if (i === 0) {
contour.push({
x: q2[1].x,
y: q2[1].y
});
contour.push({
x: q2[2].x,
y: q2[2].y,
onCurve: true
});
}
else {
prevq2 = q2List[i - 1];
// 检查是否存在切线点
if (
prevq2[1].x + q2[1].x === 2 * q2[0].x
&& prevq2[1].y + q2[1].y === 2 * q2[0].y
) {
contour.pop();
}
contour.push({
x: q2[1].x,
y: q2[1].y
});
contour.push({
x: q2[2].x,
y: q2[2].y,
onCurve: true
});
}
}
contour.push({
x: q2[2].x,
y: q2[2].y,
onCurve: true
});
return contour;
}
/**
* svg 命令数组转轮廓
*
* @param {Array} segments svg 命令数组
* @return {Array} 轮廓数组
*/
function segments2Contours(segments) {
// 解析segments
const contours = [];
let contour = [];
let prevX = 0;
let prevY = 0;
let segment;
let args;
let cmd;
let relative;
let q;
let ql;
let px;
let py;
let cubicList;
let p1;
let p2;
let c1;
let c2;
let prevCubicC1; // 三次贝塞尔曲线前一个控制点,用于绘制`s`命令
for (let i = 0, l = segments.length; i < l; i++) {
segment = segments[i];
cmd = segment.cmd;
relative = segment.relative;
args = segment.args;
if (args && !args.length && cmd !== 'Z') {
console.warn('`' + cmd + '` command args empty!');
continue;
}
if (cmd === 'Z') {
contours.push(contour);
contour = [];
}
else if (cmd === 'M' || cmd === 'L') {
if (args.length % 2) {
throw new Error('`M` command error:' + args.join(','));
}
// 这里可能会连续绘制,最后一个是终点
if (relative) {
px = prevX;
py = prevY;
}
else {
px = 0;
py = 0;
}
for (q = 0, ql = args.length; q < ql; q += 2) {
if (relative) {
px += args[q];
py += args[q + 1];
}
else {
px = args[q];
py = args[q + 1];
}
contour.push({
x: px,
y: py,
onCurve: true
});
}
prevX = px;
prevY = py;
}
else if (cmd === 'H') {
if (relative) {
prevX += args[0];
}
else {
prevX = args[0];
}
contour.push({
x: prevX,
y: prevY,
onCurve: true
});
}
else if (cmd === 'V') {
if (relative) {
prevY += args[0];
}
else {
prevY = args[0];
}
contour.push({
x: prevX,
y: prevY,
onCurve: true
});
}
// 二次贝塞尔
else if (cmd === 'Q') {
// 这里可能会连续绘制,最后一个是终点
if (relative) {
px = prevX;
py = prevY;
}
else {
px = 0;
py = 0;
}
for (q = 0, ql = args.length; q < ql; q += 4) {
contour.push({
x: px + args[q],
y: py + args[q + 1]
});
contour.push({
x: px + args[q + 2],
y: py + args[q + 3],
onCurve: true
});
if (relative) {
px += args[q + 2];
py += args[q + 3];
}
else {
px = 0;
py = 0;
}
}
if (relative) {
prevX = px;
prevY = py;
}
else {
prevX = args[ql - 2];
prevY = args[ql - 1];
}
}
// 二次贝塞尔平滑
else if (cmd === 'T') {
// 这里需要移除上一个曲线的终点
let last = contour.pop();
let pc = contour[contour.length - 1];
if (!pc) {
pc = last;
}
contour.push(pc = {
x: 2 * last.x - pc.x,
y: 2 * last.y - pc.y
});
px = prevX;
py = prevY;
for (q = 0, ql = args.length - 2; q < ql; q += 2) {
if (relative) {
px += args[q];
py += args[q + 1];
}
else {
px = args[q];
py = args[q + 1];
}
last = {
x: px,
y: py
};
contour.push(pc = {
x: 2 * last.x - pc.x,
y: 2 * last.y - pc.y
});
}
if (relative) {
prevX = px + args[ql];
prevY = py + args[ql + 1];
}
else {
prevX = args[ql];
prevY = args[ql + 1];
}
contour.push({
x: prevX,
y: prevY,
onCurve: true
});
}
// 三次贝塞尔
else if (cmd === 'C') {
if (args.length % 6) {
throw new Error('`C` command params error:' + args.join(','));
}
// 这里可能会连续绘制,最后一个是终点
cubicList = [];
if (relative) {
px = prevX;
py = prevY;
}
else {
px = 0;
py = 0;
}
p1 = {
x: prevX,
y: prevY
};
for (q = 0, ql = args.length; q < ql; q += 6) {
c1 = {
x: px + args[q],
y: py + args[q + 1]
};
c2 = {
x: px + args[q + 2],
y: py + args[q + 3]
};
p2 = {
x: px + args[q + 4],
y: py + args[q + 5]
};
cubicList.push([p1, c1, c2, p2]);
p1 = p2;
if (relative) {
px += args[q + 4];
py += args[q + 5];
}
else {
px = 0;
py = 0;
}
}
if (relative) {
prevX = px;
prevY = py;
}
else {
prevX = args[ql - 2];
prevY = args[ql - 1];
}
cubic2Points(cubicList, contour);
prevCubicC1 = cubicList[cubicList.length - 1][2];
}
// 三次贝塞尔平滑
else if (cmd === 'S') {
if (args.length % 4) {
throw new Error('`S` command params error:' + args.join(','));
}
// 这里可能会连续绘制,最后一个是终点
cubicList = [];
if (relative) {
px = prevX;
py = prevY;
}
else {
px = 0;
py = 0;
}
// 这里需要移除上一个曲线的终点
p1 = contour.pop();
if (!prevCubicC1) {
prevCubicC1 = p1;
}
c1 = {
x: 2 * p1.x - prevCubicC1.x,
y: 2 * p1.y - prevCubicC1.y
};
for (q = 0, ql = args.length; q < ql; q += 4) {
c2 = {
x: px + args[q],
y: py + args[q + 1]
};
p2 = {
x: px + args[q + 2],
y: py + args[q + 3]
};
cubicList.push([p1, c1, c2, p2]);
p1 = p2;
c1 = {
x: 2 * p1.x - c2.x,
y: 2 * p1.y - c2.y
};
if (relative) {
px += args[q + 2];
py += args[q + 3];
}
else {
px = 0;
py = 0;
}
}
if (relative) {
prevX = px;
prevY = py;
}
else {
prevX = args[ql - 2];
prevY = args[ql - 1];
}
cubic2Points(cubicList, contour);
prevCubicC1 = cubicList[cubicList.length - 1][2];
}
// 求弧度, rx, ry, angle, largeArc, sweep, ex, ey
else if (cmd === 'A') {
if (args.length % 7) {
throw new Error('arc command params error:' + args.join(','));
}
for (q = 0, ql = args.length; q < ql; q += 7) {
let ex = args[q + 5];
let ey = args[q + 6];
if (relative) {
ex = prevX + ex;
ey = prevY + ey;
}
const path = getArc(
args[q], args[q + 1],
args[q + 2], args[q + 3], args[q + 4],
{x: prevX, y: prevY},
{x: ex, y: ey}
);
if (path && path.length > 1) {
for (let r = 1, rl = path.length; r < rl; r++) {
contour.push(path[r]);
}
}
prevX = ex;
prevY = ey;
}
}
}
return contours;
}
/**
* svg path转轮廓
*
* @param {string} path svg的path字符串
* @return {Array} 转换后的轮廓
*/
export default function path2contours(path) {
if (!path || !path.length) {
return null;
}
path = path.trim();
// 修正头部不为`m`的情况
if (path[0] !== 'M' && path[0] !== 'm') {
path = 'M 0 0' + path;
}
// 修复中间没有结束符`z`的情况
path = path.replace(/(\d+)\s*(m|$)/gi, '$1z$2');
// 获取segments
const segments = [];
let cmd;
let relative = false;
let lastIndex;
let args;
for (let i = 0, l = path.length; i < l; i++) {
const c = path[i].toUpperCase();
const r = c !== path[i];
switch (c) {
case 'M':
/* jshint -W086 */
if (i === 0) {
cmd = c;
lastIndex = 1;
break;
}
// eslint-disable-next-line no-fallthrough
case 'Q':
case 'T':
case 'C':
case 'S':
case 'H':
case 'V':
case 'L':
case 'A':
case 'Z':
if (cmd === 'Z') {
segments.push({cmd: 'Z'});
}
else {
args = path.slice(lastIndex, i);
segments.push({
cmd,
relative,
args: parseParams(args)
});
}
cmd = c;
relative = r;
lastIndex = i + 1;
break;
}
}
segments.push({cmd: 'Z'});
return segments2Contours(segments);
}

View File

@ -1,31 +0,0 @@
/**
* @file 多边形转换成轮廓
* @author mengke01(kekee000@gmail.com)
*/
import parseParams from './parseParams';
/**
* 多边形转换成轮廓
*
* @param {Array} points 多边形点集合
* @return {Array} contours
*/
export default function polygon2contour(points) {
if (!points || !points.length) {
return null;
}
const contours = [];
const segments = parseParams(points);
for (let i = 0, l = segments.length; i < l; i += 2) {
contours.push({
x: segments[i],
y: segments[i + 1],
onCurve: true
});
}
return contours;
}

View File

@ -1,43 +0,0 @@
/**
* @file 矩形转换成轮廓
* @author mengke01(kekee000@gmail.com)
*/
/**
* 矩形转换成轮廓
*
* @param {number} x 左上角x
* @param {number} y 左上角y
* @param {number} width 宽度
* @param {number} height 高度
* @return {Array} 轮廓数组
*/
export default function rect2contour(x, y, width, height) {
x = +x;
y = +y;
width = +width;
height = +height;
return [
{
x,
y,
onCurve: true
},
{
x: x + width,
y,
onCurve: true
},
{
x: x + width,
y: y + height,
onCurve: true
},
{
x,
y: y + height,
onCurve: true
}
];
}

View File

@ -1,124 +0,0 @@
/**
* @file svg节点转字形轮廓
* @author mengke01(kekee000@gmail.com)
*/
import path2contours from './path2contours';
import oval2contour from './oval2contour';
import polygon2contour from './polygon2contour';
import rect2contour from './rect2contour';
import parseTransform from './parseTransform';
import contoursTransform from './contoursTransform';
// 支持的解析器集合
const support = {
path: {
parse: path2contours, // 解析器
params: ['d'], // 参数列表
contours: true // 是否是多个轮廓
},
circle: {
parse: oval2contour,
params: ['cx', 'cy', 'r']
},
ellipse: {
parse: oval2contour,
params: ['cx', 'cy', 'rx', 'ry']
},
rect: {
parse: rect2contour,
params: ['x', 'y', 'width', 'height']
},
polygon: {
parse: polygon2contour,
params: ['points']
},
polyline: {
parse: polygon2contour,
params: ['points']
}
};
/**
* svg节点转字形轮廓
*
* @param {Array} xmlNodes xml节点集合
* @return {Array|false} 轮廓数组
*/
export default function svgnode2contours(xmlNodes) {
let i;
let length;
let j;
let jlength;
let segment; // 当前指令
const parsedSegments = []; // 解析后的指令
if (xmlNodes.length) {
for (i = 0, length = xmlNodes.length; i < length; i++) {
const node = xmlNodes[i];
const name = node.tagName;
if (support[name]) {
const supportParams = support[name].params;
const params = [];
for (j = 0, jlength = supportParams.length; j < jlength; j++) {
params.push(node.getAttribute(supportParams[j]));
}
segment = {
name,
params,
transform: parseTransform(node.getAttribute('transform'))
};
if (node.parentNode) {
let curNode = node.parentNode;
const transforms = segment.transform || [];
let transAttr;
const iterator = function (t) {
transforms.unshift(t);
};
while (curNode !== null && curNode.tagName !== 'svg') {
transAttr = curNode.getAttribute('transform');
if (transAttr) {
parseTransform(transAttr).reverse().forEach(iterator);
}
curNode = curNode.parentNode;
}
segment.transform = transforms.length ? transforms : null;
}
parsedSegments.push(segment);
}
}
}
if (parsedSegments.length) {
const result = [];
for (i = 0, length = parsedSegments.length; i < length; i++) {
segment = parsedSegments[i];
const parser = support[segment.name];
const contour = parser.parse.apply(null, segment.params);
if (contour && contour.length) {
let contours = parser.contours ? contour : [contour];
// 如果有变换则应用变换规则
if (segment.transform) {
contours = contoursTransform(contours, segment.transform);
}
for (j = 0, jlength = contours.length; j < jlength; j++) {
result.push(contours[j]);
}
}
}
return result;
}
return false;
}

View File

@ -1,19 +0,0 @@
/**
* @file svg字符串转base64编码
* @author mengke01(kekee000@gmail.com)
*/
/**
* svg字符串转base64编码
*
* @param {string} svg svg对象
* @param {string} scheme 头部
* @return {string} base64编码
*/
export default function svg2base64(svg, scheme = 'font/svg') {
if (typeof btoa === 'undefined') {
return 'data:' + scheme + ';charset=utf-8;base64,'
+ Buffer.from(svg, 'binary').toString('base64');
}
return 'data:' + scheme + ';charset=utf-8;base64,' + btoa(svg);
}

View File

@ -1,431 +0,0 @@
/**
* @file svg格式转ttfObject格式
* @author mengke01(kekee000@gmail.com)
*/
import string from '../common/string';
import DOMParser from '../common/DOMParser';
import path2contours from './svg/path2contours';
import svgnode2contours from './svg/svgnode2contours';
import {computePathBox} from '../graphics/computeBoundingBox';
import pathsUtil from '../graphics/pathsUtil';
import glyfAdjust from './util/glyfAdjust';
import error from './error';
import getEmptyttfObject from './getEmptyttfObject';
import reduceGlyf from './util/reduceGlyf';
/**
* 加载xml字符串
*
* @param {string} xml xml字符串
* @return {Document}
*/
function loadXML(xml) {
if (DOMParser) {
try {
const domParser = new DOMParser();
const xmlDoc = domParser.parseFromString(xml, 'text/xml');
return xmlDoc;
}
catch (exp) {
error.raise(10103);
}
}
error.raise(10004);
}
/**
* 对xml文本进行处理
*
* @param {string} svg svg文本
* @return {string} 处理后文本
*/
function resolveSVG(svg) {
// 去除xmlns防止xmlns导致svg解析错误
svg = svg.replace(/\s+xmlns(?::[\w-]+)?=("|')[^"']*\1/g, ' ')
.replace(/<defs[>\s][\s\S]+?\/defs>/g, (text) => {
if (text.indexOf('</font>') >= 0) {
return text;
}
return '';
})
.replace(/<use[>\s][\s\S]+?\/use>/g, '');
return svg;
}
/**
* 获取空的ttf格式对象
*
* @return {Object} ttfObject对象
*/
function getEmptyTTF() {
const ttf = getEmptyttfObject();
ttf.head.unitsPerEm = 0; // 去除unitsPerEm以便于重新计算
ttf.from = 'svgfont';
return ttf;
}
/**
* 获取空的对象用来作为ttf的容器
*
* @return {Object} ttfObject对象
*/
function getEmptyObject() {
return {
'from': 'svg',
'OS/2': {},
'name': {},
'hhea': {},
'head': {},
'post': {},
'glyf': []
};
}
/**
* 根据边界获取unitsPerEm
*
* @param {number} xMin x最小值
* @param {number} xMax x最大值
* @param {number} yMin y最小值
* @param {number} yMax y最大值
* @return {number}
*/
function getUnitsPerEm(xMin, xMax, yMin, yMax) {
const seed = Math.ceil(Math.min(yMax - yMin, xMax - xMin));
if (!seed) {
return 1024;
}
if (seed <= 128) {
return seed;
}
// 获取合适的unitsPerEm
let unitsPerEm = 128;
while (unitsPerEm < 16384) {
if (seed <= 1.2 * unitsPerEm) {
return unitsPerEm;
}
unitsPerEm <<= 1;
}
return 1024;
}
/**
* 对ttfObject进行处理去除小数
*
* @param {Object} ttf ttfObject
* @return {Object} ttfObject
*/
function resolve(ttf) {
// 如果是svg格式字体则去小数
// 由于svg格式导入时候会出现字形重复问题这里进行优化
if (ttf.from === 'svgfont' && ttf.head.unitsPerEm > 128) {
ttf.glyf.forEach((g) => {
if (g.contours) {
glyfAdjust(g);
reduceGlyf(g);
}
});
}
// 否则重新计算字形大小缩放到1024的em
else {
let xMin = 16384;
let xMax = -16384;
let yMin = 16384;
let yMax = -16384;
ttf.glyf.forEach((g) => {
if (g.contours) {
const bound = computePathBox(...g.contours);
if (bound) {
xMin = Math.min(xMin, bound.x);
xMax = Math.max(xMax, bound.x + bound.width);
yMin = Math.min(yMin, bound.y);
yMax = Math.max(yMax, bound.y + bound.height);
}
}
});
const unitsPerEm = getUnitsPerEm(xMin, xMax, yMin, yMax);
const scale = 1024 / unitsPerEm;
ttf.glyf.forEach((g) => {
glyfAdjust(g, scale, scale);
reduceGlyf(g);
});
ttf.head.unitsPerEm = 1024;
}
return ttf;
}
/**
* 解析字体信息相关节点
*
* @param {Document} xmlDoc XML文档对象
* @param {Object} ttf ttf对象
* @return {Object} ttf对象
*/
function parseFont(xmlDoc, ttf) {
const metaNode = xmlDoc.getElementsByTagName('metadata')[0];
const fontNode = xmlDoc.getElementsByTagName('font')[0];
const fontFaceNode = xmlDoc.getElementsByTagName('font-face')[0];
if (metaNode && metaNode.textContent) {
ttf.metadata = string.decodeHTML(metaNode.textContent.trim());
}
// 解析font如果有font节点说明是svg格式字体文件
if (fontNode) {
ttf.id = fontNode.getAttribute('id') || '';
ttf.hhea.advanceWidthMax = +(fontNode.getAttribute('horiz-adv-x') || 0);
ttf.from = 'svgfont';
}
if (fontFaceNode) {
const OS2 = ttf['OS/2'];
ttf.name.fontFamily = fontFaceNode.getAttribute('font-family') || '';
OS2.usWeightClass = +(fontFaceNode.getAttribute('font-weight') || 0);
ttf.head.unitsPerEm = +(fontFaceNode.getAttribute('units-per-em') || 0);
// 解析panose, eg: 2 0 6 3 0 0 0 0 0 0
const panose = (fontFaceNode.getAttribute('panose-1') || '').split(' ');
[
'bFamilyType', 'bSerifStyle', 'bWeight', 'bProportion', 'bContrast',
'bStrokeVariation', 'bArmStyle', 'bLetterform', 'bMidline', 'bXHeight'
].forEach((name, i) => {
OS2[name] = +(panose[i] || 0);
});
ttf.hhea.ascent = +(fontFaceNode.getAttribute('ascent') || 0);
ttf.hhea.descent = +(fontFaceNode.getAttribute('descent') || 0);
OS2.bXHeight = +(fontFaceNode.getAttribute('x-height') || 0);
// 解析bounding
const box = (fontFaceNode.getAttribute('bbox') || '').split(' ');
['xMin', 'yMin', 'xMax', 'yMax'].forEach((name, i) => {
ttf.head[name] = +(box[i] || '');
});
ttf.post.underlineThickness = +(fontFaceNode.getAttribute('underline-thickness') || 0);
ttf.post.underlinePosition = +(fontFaceNode.getAttribute('underline-position') || 0);
// unicode range
const unicodeRange = fontFaceNode.getAttribute('unicode-range');
if (unicodeRange) {
unicodeRange.replace(/u\+([0-9A-Z]+)(-[0-9A-Z]+)?/i, ($0, a, b) => {
OS2.usFirstCharIndex = Number('0x' + a);
OS2.usLastCharIndex = b ? Number('0x' + b.slice(1)) : 0xFFFFFFFF;
});
}
}
return ttf;
}
/**
* 解析字体信息相关节点
*
* @param {Document} xmlDoc XML文档对象
* @param {Object} ttf ttf对象
* @return {Object} ttf对象
*/
function parseGlyf(xmlDoc, ttf) {
const missingNode = xmlDoc.getElementsByTagName('missing-glyph')[0];
// 解析glyf
let d;
let unicode;
if (missingNode) {
const missing = {
name: '.notdef'
};
if (missingNode.getAttribute('horiz-adv-x')) {
missing.advanceWidth = +missingNode.getAttribute('horiz-adv-x');
}
if ((d = missingNode.getAttribute('d'))) {
missing.contours = path2contours(d);
}
// 去除默认的空字形
if (ttf.glyf[0] && ttf.glyf[0].name === '.notdef') {
ttf.glyf.splice(0, 1);
}
ttf.glyf.unshift(missing);
}
const glyfNodes = xmlDoc.getElementsByTagName('glyph');
if (glyfNodes.length) {
for (let i = 0, l = glyfNodes.length; i < l; i++) {
const node = glyfNodes[i];
const glyf = {
name: node.getAttribute('glyph-name') || node.getAttribute('name') || ''
};
if (node.getAttribute('horiz-adv-x')) {
glyf.advanceWidth = +node.getAttribute('horiz-adv-x');
}
if ((unicode = node.getAttribute('unicode'))) {
const nextUnicode = [];
let totalCodePoints = 0;
for (let ui = 0; ui < unicode.length; ui++) {
const ucp = unicode.codePointAt(ui);
nextUnicode.push(ucp);
ui = ucp > 0xffff ? ui + 1 : ui;
totalCodePoints += 1;
}
if (totalCodePoints === 1) {
// TTF can't handle ligatures
glyf.unicode = nextUnicode;
if ((d = node.getAttribute('d'))) {
glyf.contours = path2contours(d);
}
ttf.glyf.push(glyf);
}
}
}
}
return ttf;
}
/**
* 解析字体信息相关节点
*
* @param {Document} xmlDoc XML文档对象
* @param {Object} ttf ttf对象
*/
function parsePath(xmlDoc, ttf) {
// 单个path组成一个glfy字形
let contours;
let glyf;
let node;
const pathNodes = xmlDoc.getElementsByTagName('path');
if (pathNodes.length) {
for (let i = 0, l = pathNodes.length; i < l; i++) {
node = pathNodes[i];
glyf = {
name: node.getAttribute('name') || ''
};
contours = svgnode2contours([node]);
glyf.contours = contours;
ttf.glyf.push(glyf);
}
}
// 其他svg指令组成一个glyf字形
contours = svgnode2contours(
Array.prototype.slice.call(xmlDoc.getElementsByTagName('*')).filter((node) => node.tagName !== 'path')
);
if (contours) {
glyf = {
name: ''
};
glyf.contours = contours;
ttf.glyf.push(glyf);
}
}
/**
* 解析xml文档
*
* @param {Document} xmlDoc XML文档对象
* @param {Object} options 导入选项
*
* @return {Object} 解析后对象
*/
function parseXML(xmlDoc, options) {
if (!xmlDoc.getElementsByTagName('svg').length) {
error.raise(10106);
}
let ttf;
// 如果是svg字体格式则解析glyf否则解析path
if (xmlDoc.getElementsByTagName('font')[0]) {
ttf = getEmptyTTF();
parseFont(xmlDoc, ttf);
parseGlyf(xmlDoc, ttf);
}
else {
ttf = getEmptyObject();
parsePath(xmlDoc, ttf);
}
if (!ttf.glyf.length) {
error.raise(10201);
}
if (ttf.from === 'svg') {
const glyf = ttf.glyf;
let i;
let l;
// 合并导入的字形为单个字形
if (options.combinePath) {
const combined = [];
for (i = 0, l = glyf.length; i < l; i++) {
const contours = glyf[i].contours;
for (let index = 0, length = contours.length; index < length; index++) {
combined.push(contours[index]);
}
}
glyf[0].contours = combined;
glyf.splice(1);
}
// 对字形进行反转
for (i = 0, l = glyf.length; i < l; i++) {
// 这里为了使ai等工具里面的字形方便导入对svg做了反向处理
glyf[i].contours = pathsUtil.flip(glyf[i].contours);
}
}
return ttf;
}
/**
* svg格式转ttfObject格式
*
* @param {string|Document} svg svg格式
* @param {Object=} options 导入选项
* @param {boolean} options.combinePath 是否合并成单个字形仅限于普通svg导入
* @return {Object} ttfObject
*/
export default function svg2ttfObject(svg, options = {combinePath: false}) {
let xmlDoc = svg;
if (typeof svg === 'string') {
svg = resolveSVG(svg);
xmlDoc = loadXML(svg);
}
const ttf = parseXML(xmlDoc, options);
return resolve(ttf);
}

View File

@ -1,259 +0,0 @@
/**
* @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');
}
}
);

View File

@ -1,30 +0,0 @@
/**
* @file GPOS
* @author fr33z00(https://github.com/fr33z00)
*
* @reference: https://learn.microsoft.com/en-us/typography/opentype/spec/gpos
*/
import table from './table';
export default table.create(
'GPOS',
[],
{
read(reader, ttf) {
const length = ttf.tables.GPOS.length;
return reader.readBytes(this.offset, length);
},
write(writer, ttf) {
if (ttf.GPOS) {
writer.writeBytes(ttf.GPOS, ttf.GPOS.length);
}
},
size(ttf) {
return ttf.GPOS ? ttf.GPOS.length : 0;
}
}
);

View File

@ -1,321 +0,0 @@
/**
* @file OS/2
* @author mengke01(kekee000@gmail.com)
*
* http://www.microsoft.com/typography/otspec/os2.htm
*/
import table from './table';
import struct from './struct';
export default table.create(
'OS/2',
[
['version', struct.Uint16],
['xAvgCharWidth', struct.Int16],
['usWeightClass', struct.Uint16],
['usWidthClass', struct.Uint16],
['fsType', struct.Uint16],
['ySubscriptXSize', struct.Uint16],
['ySubscriptYSize', struct.Uint16],
['ySubscriptXOffset', struct.Uint16],
['ySubscriptYOffset', struct.Uint16],
['ySuperscriptXSize', struct.Uint16],
['ySuperscriptYSize', struct.Uint16],
['ySuperscriptXOffset', struct.Uint16],
['ySuperscriptYOffset', struct.Uint16],
['yStrikeoutSize', struct.Uint16],
['yStrikeoutPosition', struct.Uint16],
['sFamilyClass', struct.Uint16],
// Panose
['bFamilyType', struct.Uint8],
['bSerifStyle', struct.Uint8],
['bWeight', struct.Uint8],
['bProportion', struct.Uint8],
['bContrast', struct.Uint8],
['bStrokeVariation', struct.Uint8],
['bArmStyle', struct.Uint8],
['bLetterform', struct.Uint8],
['bMidline', struct.Uint8],
['bXHeight', struct.Uint8],
// unicode range
['ulUnicodeRange1', struct.Uint32],
['ulUnicodeRange2', struct.Uint32],
['ulUnicodeRange3', struct.Uint32],
['ulUnicodeRange4', struct.Uint32],
// char 4
['achVendID', struct.String, 4],
['fsSelection', struct.Uint16],
['usFirstCharIndex', struct.Uint16],
['usLastCharIndex', struct.Uint16],
['sTypoAscender', struct.Int16],
['sTypoDescender', struct.Int16],
['sTypoLineGap', struct.Int16],
['usWinAscent', struct.Uint16],
['usWinDescent', struct.Uint16],
// version 0 above 39
['ulCodePageRange1', struct.Uint32],
['ulCodePageRange2', struct.Uint32],
// version 1 above 41
['sxHeight', struct.Int16],
['sCapHeight', struct.Int16],
['usDefaultChar', struct.Uint16],
['usBreakChar', struct.Uint16],
['usMaxContext', struct.Uint16]
// version 2,3,4 above 46
],
{
read(reader, ttf) {
const format = reader.readUint16(this.offset);
let struct = this.struct;
// format2
if (format === 0) {
struct = struct.slice(0, 39);
}
else if (format === 1) {
struct = struct.slice(0, 41);
}
const OS2Head = table.create('os2head', struct);
const tbl = new OS2Head(this.offset).read(reader, ttf);
// 补齐其他version的字段
const os2Fields = {
ulCodePageRange1: 1,
ulCodePageRange2: 0,
sxHeight: 0,
sCapHeight: 0,
usDefaultChar: 0,
usBreakChar: 32,
usMaxContext: 0
};
return Object.assign(os2Fields, tbl);
},
size(ttf) {
// 更新其他表的统计信息
// header
let xMin = 16384;
let yMin = 16384;
let xMax = -16384;
let yMax = -16384;
// hhea
let advanceWidthMax = -1;
let minLeftSideBearing = 16384;
let minRightSideBearing = 16384;
let xMaxExtent = -16384;
// os2 count
let xAvgCharWidth = 0;
let usFirstCharIndex = 0x10FFFF;
let usLastCharIndex = -1;
// maxp
let maxPoints = 0;
let maxContours = 0;
let maxCompositePoints = 0;
let maxCompositeContours = 0;
let maxSizeOfInstructions = 0;
let maxComponentElements = 0;
let glyfNotEmpty = 0; // 非空glyf
const hinting = ttf.writeOptions ? ttf.writeOptions.hinting : false;
// 计算instructions和functiondefs
if (hinting) {
if (ttf.cvt) {
maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.cvt.length);
}
if (ttf.prep) {
maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.prep.length);
}
if (ttf.fpgm) {
maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.fpgm.length);
}
}
ttf.glyf.forEach((glyf) => {
// 统计control point信息
if (glyf.compound) {
let compositeContours = 0;
let compositePoints = 0;
glyf.glyfs.forEach((g) => {
const cglyf = ttf.glyf[g.glyphIndex];
if (!cglyf) {
return;
}
compositeContours += cglyf.contours ? cglyf.contours.length : 0;
if (cglyf.contours && cglyf.contours.length) {
cglyf.contours.forEach((contour) => {
compositePoints += contour.length;
});
}
});
maxComponentElements = Math.max(maxComponentElements, glyf.glyfs.length);
maxCompositePoints = Math.max(maxCompositePoints, compositePoints);
maxCompositeContours = Math.max(maxCompositeContours, compositeContours);
}
// 简单图元
else if (glyf.contours && glyf.contours.length) {
maxContours = Math.max(maxContours, glyf.contours.length);
let points = 0;
glyf.contours.forEach((contour) => {
points += contour.length;
});
maxPoints = Math.max(maxPoints, points);
}
if (hinting && glyf.instructions) {
maxSizeOfInstructions = Math.max(maxSizeOfInstructions, glyf.instructions.length);
}
// 统计边界信息
if (null != glyf.xMin && glyf.xMin < xMin) {
xMin = glyf.xMin;
}
if (null != glyf.yMin && glyf.yMin < yMin) {
yMin = glyf.yMin;
}
if (null != glyf.xMax && glyf.xMax > xMax) {
xMax = glyf.xMax;
}
if (null != glyf.yMax && glyf.yMax > yMax) {
yMax = glyf.yMax;
}
advanceWidthMax = Math.max(advanceWidthMax, glyf.advanceWidth);
minLeftSideBearing = Math.min(minLeftSideBearing, glyf.leftSideBearing);
if (null != glyf.xMax) {
minRightSideBearing = Math.min(minRightSideBearing, glyf.advanceWidth - glyf.xMax);
xMaxExtent = Math.max(xMaxExtent, glyf.xMax);
}
if (null != glyf.advanceWidth) {
xAvgCharWidth += glyf.advanceWidth;
glyfNotEmpty++;
}
let unicodes = glyf.unicode;
if (typeof glyf.unicode === 'number') {
unicodes = [glyf.unicode];
}
if (Array.isArray(unicodes)) {
unicodes.forEach((unicode) => {
if (unicode !== 0xFFFF) {
usFirstCharIndex = Math.min(usFirstCharIndex, unicode);
usLastCharIndex = Math.max(usLastCharIndex, unicode);
}
});
}
});
// 重新设置version 4
ttf['OS/2'].version = 0x4;
ttf['OS/2'].achVendID = (ttf['OS/2'].achVendID + ' ').slice(0, 4);
ttf['OS/2'].xAvgCharWidth = xAvgCharWidth / (glyfNotEmpty || 1);
ttf['OS/2'].ulUnicodeRange2 = 268435456;
ttf['OS/2'].usFirstCharIndex = usFirstCharIndex;
ttf['OS/2'].usLastCharIndex = usLastCharIndex;
// rewrite hhea
ttf.hhea.version = ttf.hhea.version || 0x1;
ttf.hhea.advanceWidthMax = advanceWidthMax;
ttf.hhea.minLeftSideBearing = minLeftSideBearing;
ttf.hhea.minRightSideBearing = minRightSideBearing;
ttf.hhea.xMaxExtent = xMaxExtent;
// rewrite head
ttf.head.version = ttf.head.version || 0x1;
ttf.head.lowestRecPPEM = ttf.head.lowestRecPPEM || 0x8;
ttf.head.xMin = xMin;
ttf.head.yMin = yMin;
ttf.head.xMax = xMax;
ttf.head.yMax = yMax;
// head rewrite
if (ttf.support.head) {
const {xMin, yMin, xMax, yMax} = ttf.support.head;
if (xMin != null) {
ttf.head.xMin = xMin;
}
if (yMin != null) {
ttf.head.yMin = yMin;
}
if (xMax != null) {
ttf.head.xMax = xMax;
}
if (yMax != null) {
ttf.head.yMax = yMax;
}
}
// hhea rewrite
if (ttf.support.hhea) {
const {advanceWidthMax, xMaxExtent, minLeftSideBearing, minRightSideBearing} = ttf.support.hhea;
if (advanceWidthMax != null) {
ttf.hhea.advanceWidthMax = advanceWidthMax;
}
if (xMaxExtent != null) {
ttf.hhea.xMaxExtent = xMaxExtent;
}
if (minLeftSideBearing != null) {
ttf.hhea.minLeftSideBearing = minLeftSideBearing;
}
if (minRightSideBearing != null) {
ttf.hhea.minRightSideBearing = minRightSideBearing;
}
}
// 这里根据存储的maxp来设置新的maxp避免重复计算maxp
ttf.maxp = ttf.maxp || {};
ttf.support.maxp = {
version: 1.0,
numGlyphs: ttf.glyf.length,
maxPoints,
maxContours,
maxCompositePoints,
maxCompositeContours,
maxZones: ttf.maxp.maxZones || 0,
maxTwilightPoints: ttf.maxp.maxTwilightPoints || 0,
maxStorage: ttf.maxp.maxStorage || 0,
maxFunctionDefs: ttf.maxp.maxFunctionDefs || 0,
maxStackElements: ttf.maxp.maxStackElements || 0,
maxSizeOfInstructions,
maxComponentElements,
maxComponentDepth: maxComponentElements ? 1 : 0
};
return table.size.call(this, ttf);
}
}
);

View File

@ -1,67 +0,0 @@
/**
* @file cffStandardStrings.js
* @author mengke01(kekee000@gmail.com)
*/
const cffStandardStrings = [
'.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright',
'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two',
'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater',
'question', 'at', 'A', 'B', 'C', 'D', 'E',
'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore',
'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
'asciitilde', 'exclamdown', 'cent', 'sterling',
'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
'daggerdbl', 'periodcentered', 'paragraph',
'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring',
'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE',
'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu',
'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn',
'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright',
'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex',
'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex',
'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute',
'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis',
'agrave', 'aring', 'atilde', 'ccedilla', 'eacute',
'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute',
'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior',
'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader',
'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle',
'fouroldstyle', 'fiveoldstyle', 'sixoldstyle',
'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
'questionsmall', 'asuperior', 'bsuperior', 'centsuperior',
'dsuperior', 'esuperior', 'isuperior', 'lsuperior',
'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl',
'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall',
'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall',
'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall',
'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall',
'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall',
'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall',
'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall',
'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds',
'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior',
'sevensuperior', 'eightsuperior', 'ninesuperior',
'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior',
'fourinferior', 'fiveinferior', 'sixinferior',
'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior',
'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall',
'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall',
'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall',
'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall',
'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall',
'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall',
'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000',
'001.001', '001.002', '001.003', 'Black', 'Bold',
'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold'
];
export default cffStandardStrings;

View File

@ -1,81 +0,0 @@
/**
* @file cff名字设置
* @author mengke01(kekee000@gmail.com)
*/
const cffStandardEncoding = [
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright',
'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen',
'period', 'slash', 'zero', 'one', 'two',
'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater',
'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft',
'backslash', 'bracketright', 'asciicircum', 'underscore',
'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar',
'braceright', 'asciitilde', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle',
'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger',
'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright',
'guillemotright', 'ellipsis', 'perthousand', '',
'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde',
'macron', 'breve', 'dotaccent', 'dieresis', '',
'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron',
'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'AE', '', 'ordfeminine', '', '', '',
'', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '',
'lslash', 'oslash', 'oe', 'germandbls'
];
const cffExpertEncoding = [
'', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior',
'ampersandsmall', 'Acutesmall', 'parenleftsuperior',
'parenrightsuperior', 'twodotenleader', 'onedotenleader',
'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle',
'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon',
'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior',
'bsuperior', 'centsuperior', 'dsuperior', 'esuperior',
'', '', 'isuperior', '', '', 'lsuperior', 'msuperior',
'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl',
'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall',
'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall',
'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall',
'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall',
'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall',
'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
'', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall',
'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '',
'Macronsmall', '', '', 'figuredash', 'hypheninferior',
'', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters',
'questiondownsmall', 'oneeighth', 'threeeighths',
'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '',
'', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior',
'sixsuperior', 'sevensuperior', 'eightsuperior',
'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior',
'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior',
'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
'Aacutesmall', 'Acircumflexsmall', 'Atildesmall',
'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall',
'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall',
'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall',
'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall',
'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall'
];
export default {
standardEncoding: cffStandardEncoding,
expertEncoding: cffExpertEncoding
};

View File

@ -1,25 +0,0 @@
/**
* @file 获取cff字符串
* @author mengke01(kekee000@gmail.com)
*/
import cffStandardStrings from './cffStandardStrings';
/**
* 根据索引获取cff字符串
*
* @param {Object} strings 标准cff字符串索引
* @param {number} index 索引号
* @return {number} 字符串索引
*/
export default function getCFFString(strings, index) {
if (index <= 390) {
index = cffStandardStrings[index];
}
// Strings below index 392 are standard CFF strings and are not encoded in the font.
else {
index = strings[index - 391];
}
return index;
}

View File

@ -1,62 +0,0 @@
/**
* @file 解析cff字符集
* @author mengke01(kekee000@gmail.com)
*/
import getCFFString from './getCFFString';
/**
* 解析cff字形名称
* See Adobe TN #5176 chapter 13, "Charsets".
*
* @param {Reader} reader 读取器
* @param {number} start 起始偏移
* @param {number} nGlyphs 字形个数
* @param {Object} strings cff字符串字典
* @return {Array} 字符集
*/
export default function parseCFFCharset(reader, start, nGlyphs, strings) {
if (start) {
reader.seek(start);
}
let i;
let sid;
let count;
// The .notdef glyph is not included, so subtract 1.
nGlyphs -= 1;
const charset = ['.notdef'];
const format = reader.readUint8();
if (format === 0) {
for (i = 0; i < nGlyphs; i += 1) {
sid = reader.readUint16();
charset.push(getCFFString(strings, sid));
}
}
else if (format === 1) {
while (charset.length <= nGlyphs) {
sid = reader.readUint16();
count = reader.readUint8();
for (i = 0; i <= count; i += 1) {
charset.push(getCFFString(strings, sid));
sid += 1;
}
}
}
else if (format === 2) {
while (charset.length <= nGlyphs) {
sid = reader.readUint16();
count = reader.readUint16();
for (i = 0; i <= count; i += 1) {
charset.push(getCFFString(strings, sid));
sid += 1;
}
}
}
else {
throw new Error('Unknown charset format ' + format);
}
return charset;
}

View File

@ -1,350 +0,0 @@
/**
* @file 解析cffdict数据
* @author mengke01(kekee000@gmail.com)
*/
import getCFFString from './getCFFString';
const TOP_DICT_META = [
{
name: 'version',
op: 0,
type: 'SID'
},
{
name: 'notice',
op: 1,
type: 'SID'
},
{
name: 'copyright',
op: 1200,
type: 'SID'
},
{
name: 'fullName',
op: 2,
type: 'SID'
},
{
name: 'familyName',
op: 3,
type: 'SID'
},
{
name: 'weight',
op: 4,
type: 'SID'
},
{
name: 'isFixedPitch',
op: 1201,
type: 'number',
value: 0
},
{
name: 'italicAngle',
op: 1202,
type: 'number',
value: 0
},
{
name: 'underlinePosition',
op: 1203,
type: 'number',
value: -100
},
{
name: 'underlineThickness',
op: 1204,
type: 'number',
value: 50
},
{
name: 'paintType',
op: 1205,
type: 'number',
value: 0
},
{
name: 'charstringType',
op: 1206,
type: 'number',
value: 2
},
{
name: 'fontMatrix',
op: 1207,
type: ['real', 'real', 'real', 'real', 'real', 'real'],
value: [0.001, 0, 0, 0.001, 0, 0]
},
{
name: 'uniqueId',
op: 13,
type: 'number'
},
{
name: 'fontBBox',
op: 5,
type: ['number', 'number', 'number', 'number'],
value: [0, 0, 0, 0]
},
{
name: 'strokeWidth',
op: 1208,
type: 'number',
value: 0
},
{
name: 'xuid',
op: 14,
type: [],
value: null
},
{
name: 'charset',
op: 15,
type: 'offset',
value: 0
},
{
name: 'encoding',
op: 16,
type: 'offset',
value: 0
},
{
name: 'charStrings',
op: 17,
type: 'offset',
value: 0
},
{
name: 'private',
op: 18,
type: ['number', 'offset'],
value: [0, 0]
}
];
const PRIVATE_DICT_META = [
{
name: 'subrs',
op: 19,
type: 'offset',
value: 0
},
{
name: 'defaultWidthX',
op: 20,
type: 'number',
value: 0
},
{
name: 'nominalWidthX',
op: 21,
type: 'number',
value: 0
}
];
function entriesToObject(entries) {
const hash = {};
for (let i = 0, l = entries.length; i < l; i++) {
const key = entries[i][0];
if (undefined !== hash[key]) {
console.warn('dict already has key:' + key);
continue;
}
const values = entries[i][1];
hash[key] = values.length === 1 ? values[0] : values;
}
return hash;
}
/* eslint-disable no-constant-condition */
function parseFloatOperand(reader) {
let s = '';
const eof = 15;
const lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-'];
while (true) {
const b = reader.readUint8();
const n1 = b >> 4;
const n2 = b & 15;
if (n1 === eof) {
break;
}
s += lookup[n1];
if (n2 === eof) {
break;
}
s += lookup[n2];
}
return parseFloat(s);
}
/* eslint-enable no-constant-condition */
/**
* 解析cff字典数据
*
* @param {Reader} reader 读取器
* @param {number} b0 操作码
* @return {number} 数据
*/
function parseOperand(reader, b0) {
let b1;
let b2;
let b3;
let b4;
if (b0 === 28) {
b1 = reader.readUint8();
b2 = reader.readUint8();
return b1 << 8 | b2;
}
if (b0 === 29) {
b1 = reader.readUint8();
b2 = reader.readUint8();
b3 = reader.readUint8();
b4 = reader.readUint8();
return b1 << 24 | b2 << 16 | b3 << 8 | b4;
}
if (b0 === 30) {
return parseFloatOperand(reader);
}
if (b0 >= 32 && b0 <= 246) {
return b0 - 139;
}
if (b0 >= 247 && b0 <= 250) {
b1 = reader.readUint8();
return (b0 - 247) * 256 + b1 + 108;
}
if (b0 >= 251 && b0 <= 254) {
b1 = reader.readUint8();
return -(b0 - 251) * 256 - b1 - 108;
}
throw new Error('invalid b0 ' + b0 + ',at:' + reader.offset);
}
/**
* 解析字典值
*
* @param {Object} dict 字典数据
* @param {Array} meta 元数据
* @param {Object} strings cff字符串字典
* @return {Object} 解析后数据
*/
function interpretDict(dict, meta, strings) {
const newDict = {};
// Because we also want to include missing values, we start out from the meta list
// and lookup values in the dict.
for (let i = 0, l = meta.length; i < l; i++) {
const m = meta[i];
let value = dict[m.op];
if (value === undefined) {
value = m.value !== undefined ? m.value : null;
}
if (m.type === 'SID') {
value = getCFFString(strings, value);
}
newDict[m.name] = value;
}
return newDict;
}
/**
* 解析cff dict字典
*
* @param {Reader} reader 读取器
* @param {number} offset 起始偏移
* @param {number} length 大小
* @return {Object} 配置
*/
function parseCFFDict(reader, offset, length) {
if (null != offset) {
reader.seek(offset);
}
const entries = [];
let operands = [];
const lastOffset = reader.offset + (null != length ? length : reader.length);
while (reader.offset < lastOffset) {
let op = reader.readUint8();
// The first byte for each dict item distinguishes between operator (key) and operand (value).
// Values <= 21 are operators.
if (op <= 21) {
// Two-byte operators have an initial escape byte of 12.
if (op === 12) {
op = 1200 + reader.readUint8();
}
entries.push([op, operands]);
operands = [];
}
else {
// Since the operands (values) come before the operators (keys), we store all operands in a list
// until we encounter an operator.
operands.push(parseOperand(reader, op));
}
}
return entriesToObject(entries);
}
/**
* 解析cff top字典
*
* @param {Reader} reader 读取器
* @param {number} start 开始offset
* @param {number} length 大小
* @param {Object} strings 字符串集合
* @return {Object} 字典数据
*/
function parseTopDict(reader, start, length, strings) {
const dict = parseCFFDict(reader, start || 0, length || reader.length);
return interpretDict(dict, TOP_DICT_META, strings);
}
/**
* 解析cff私有字典
*
* @param {Reader} reader 读取器
* @param {number} start 开始offset
* @param {number} length 大小
* @param {Object} strings 字符串集合
* @return {Object} 字典数据
*/
function parsePrivateDict(reader, start, length, strings) {
const dict = parseCFFDict(reader, start || 0, length || reader.length);
return interpretDict(dict, PRIVATE_DICT_META, strings);
}
export default {
parseTopDict,
parsePrivateDict
};

View File

@ -1,48 +0,0 @@
/**
* @file 解析cff编码
* @author mengke01(kekee000@gmail.com)
*/
/**
* 解析cff encoding数据
* See Adobe TN #5176 chapter 12, "Encodings".
*
* @param {Reader} reader 读取器
* @param {number=} start 偏移
* @return {Object} 编码表
*/
export default function parseCFFEncoding(reader, start) {
if (null != start) {
reader.seek(start);
}
let i;
let code;
const encoding = {};
const format = reader.readUint8();
if (format === 0) {
const nCodes = reader.readUint8();
for (i = 0; i < nCodes; i += 1) {
code = reader.readUint8();
encoding[code] = i;
}
}
else if (format === 1) {
const nRanges = reader.readUint8();
code = 1;
for (i = 0; i < nRanges; i += 1) {
const first = reader.readUint8();
const nLeft = reader.readUint8();
for (let j = first; j <= first + nLeft; j += 1) {
encoding[j] = code;
code += 1;
}
}
}
else {
console.warn('unknown encoding format:' + format);
}
return encoding;
}

View File

@ -1,489 +0,0 @@
/**
* @file 解析cff字形
* @author mengke01(kekee000@gmail.com)
*/
/**
* 解析cff字形返回直线和三次bezier曲线点数组
*
* @param {Array} code 操作码
* @param {Object} font 相关联的font对象
* @param {number} index glyf索引
* @return {Object} glyf对象
*/
export default function parseCFFCharstring(code, font, index) {
let c1x;
let c1y;
let c2x;
let c2y;
const contours = [];
let contour = [];
const stack = [];
const glyfs = [];
let nStems = 0;
let haveWidth = false;
let width = font.defaultWidthX;
let open = false;
let x = 0;
let y = 0;
function lineTo(x, y) {
contour.push({
onCurve: true,
x,
y
});
}
function curveTo(c1x, c1y, c2x, c2y, x, y) {
contour.push({
x: c1x,
y: c1y
});
contour.push({
x: c2x,
y: c2y
});
contour.push({
onCurve: true,
x,
y
});
}
function newContour(x, y) {
if (open) {
contours.push(contour);
}
contour = [];
lineTo(x, y);
open = true;
}
function parseStems() {
// The number of stem operators on the stack is always even.
// If the value is uneven, that means a width is specified.
const hasWidthArg = stack.length % 2 !== 0;
if (hasWidthArg && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
}
nStems += stack.length >> 1;
stack.length = 0;
haveWidth = true;
}
function parse(code) {
let b1;
let b2;
let b3;
let b4;
let codeIndex;
let subrCode;
let jpx;
let jpy;
let c3x;
let c3y;
let c4x;
let c4y;
let i = 0;
while (i < code.length) {
let v = code[i];
i += 1;
switch (v) {
case 1: // hstem
parseStems();
break;
case 3: // vstem
parseStems();
break;
case 4: // vmoveto
if (stack.length > 1 && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
haveWidth = true;
}
y += stack.pop();
newContour(x, y);
break;
case 5: // rlineto
while (stack.length > 0) {
x += stack.shift();
y += stack.shift();
lineTo(x, y);
}
break;
case 6: // hlineto
while (stack.length > 0) {
x += stack.shift();
lineTo(x, y);
if (stack.length === 0) {
break;
}
y += stack.shift();
lineTo(x, y);
}
break;
case 7: // vlineto
while (stack.length > 0) {
y += stack.shift();
lineTo(x, y);
if (stack.length === 0) {
break;
}
x += stack.shift();
lineTo(x, y);
}
break;
case 8: // rrcurveto
while (stack.length > 0) {
c1x = x + stack.shift();
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x + stack.shift();
y = c2y + stack.shift();
curveTo(c1x, c1y, c2x, c2y, x, y);
}
break;
case 10: // callsubr
codeIndex = stack.pop() + font.subrsBias;
subrCode = font.subrs[codeIndex];
if (subrCode) {
parse(subrCode);
}
break;
case 11: // return
return;
case 12: // flex operators
v = code[i];
i += 1;
switch (v) {
case 35: // flex
// |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |-
c1x = x + stack.shift(); // dx1
c1y = y + stack.shift(); // dy1
c2x = c1x + stack.shift(); // dx2
c2y = c1y + stack.shift(); // dy2
jpx = c2x + stack.shift(); // dx3
jpy = c2y + stack.shift(); // dy3
c3x = jpx + stack.shift(); // dx4
c3y = jpy + stack.shift(); // dy4
c4x = c3x + stack.shift(); // dx5
c4y = c3y + stack.shift(); // dy5
x = c4x + stack.shift(); // dx6
y = c4y + stack.shift(); // dy6
stack.shift(); // flex depth
curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
curveTo(c3x, c3y, c4x, c4y, x, y);
break;
case 34: // hflex
// |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |-
c1x = x + stack.shift(); // dx1
c1y = y; // dy1
c2x = c1x + stack.shift(); // dx2
c2y = c1y + stack.shift(); // dy2
jpx = c2x + stack.shift(); // dx3
jpy = c2y; // dy3
c3x = jpx + stack.shift(); // dx4
c3y = c2y; // dy4
c4x = c3x + stack.shift(); // dx5
c4y = y; // dy5
x = c4x + stack.shift(); // dx6
curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
curveTo(c3x, c3y, c4x, c4y, x, y);
break;
case 36: // hflex1
// |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |-
c1x = x + stack.shift(); // dx1
c1y = y + stack.shift(); // dy1
c2x = c1x + stack.shift(); // dx2
c2y = c1y + stack.shift(); // dy2
jpx = c2x + stack.shift(); // dx3
jpy = c2y; // dy3
c3x = jpx + stack.shift(); // dx4
c3y = c2y; // dy4
c4x = c3x + stack.shift(); // dx5
c4y = c3y + stack.shift(); // dy5
x = c4x + stack.shift(); // dx6
curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
curveTo(c3x, c3y, c4x, c4y, x, y);
break;
case 37: // flex1
// |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |-
c1x = x + stack.shift(); // dx1
c1y = y + stack.shift(); // dy1
c2x = c1x + stack.shift(); // dx2
c2y = c1y + stack.shift(); // dy2
jpx = c2x + stack.shift(); // dx3
jpy = c2y + stack.shift(); // dy3
c3x = jpx + stack.shift(); // dx4
c3y = jpy + stack.shift(); // dy4
c4x = c3x + stack.shift(); // dx5
c4y = c3y + stack.shift(); // dy5
if (Math.abs(c4x - x) > Math.abs(c4y - y)) {
x = c4x + stack.shift();
}
else {
y = c4y + stack.shift();
}
curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
curveTo(c3x, c3y, c4x, c4y, x, y);
break;
default:
console.warn('Glyph ' + index + ': unknown operator ' + (1200 + v));
stack.length = 0;
}
break;
case 14: // endchar
if (stack.length === 1 && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
haveWidth = true;
}
else if (stack.length === 4) {
glyfs[1] = {
glyphIndex: font.charset.indexOf(font.encoding[stack.pop()]),
transform: {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0}
};
glyfs[0] = {
glyphIndex: font.charset.indexOf(font.encoding[stack.pop()]),
transform: {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0}
};
glyfs[1].transform.f = stack.pop();
glyfs[1].transform.e = stack.pop();
}
else if (stack.length === 5) {
if (!haveWidth) {
width = stack.shift() + font.nominalWidthX;
}
haveWidth = true;
glyfs[1] = {
glyphIndex: font.charset.indexOf(font.encoding[stack.pop()]),
transform: {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0}
};
glyfs[0] = {
glyphIndex: font.charset.indexOf(font.encoding[stack.pop()]),
transform: {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0}
};
glyfs[1].transform.f = stack.pop();
glyfs[1].transform.e = stack.pop();
}
if (open) {
contours.push(contour);
open = false;
}
break;
case 18: // hstemhm
parseStems();
break;
case 19: // hintmask
case 20: // cntrmask
parseStems();
i += (nStems + 7) >> 3;
break;
case 21: // rmoveto
if (stack.length > 2 && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
haveWidth = true;
}
y += stack.pop();
x += stack.pop();
newContour(x, y);
break;
case 22: // hmoveto
if (stack.length > 1 && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
haveWidth = true;
}
x += stack.pop();
newContour(x, y);
break;
case 23: // vstemhm
parseStems();
break;
case 24: // rcurveline
while (stack.length > 2) {
c1x = x + stack.shift();
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x + stack.shift();
y = c2y + stack.shift();
curveTo(c1x, c1y, c2x, c2y, x, y);
}
x += stack.shift();
y += stack.shift();
lineTo(x, y);
break;
case 25: // rlinecurve
while (stack.length > 6) {
x += stack.shift();
y += stack.shift();
lineTo(x, y);
}
c1x = x + stack.shift();
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x + stack.shift();
y = c2y + stack.shift();
curveTo(c1x, c1y, c2x, c2y, x, y);
break;
case 26: // vvcurveto
if (stack.length % 2) {
x += stack.shift();
}
while (stack.length > 0) {
c1x = x;
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x;
y = c2y + stack.shift();
curveTo(c1x, c1y, c2x, c2y, x, y);
}
break;
case 27: // hhcurveto
if (stack.length % 2) {
y += stack.shift();
}
while (stack.length > 0) {
c1x = x + stack.shift();
c1y = y;
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x + stack.shift();
y = c2y;
curveTo(c1x, c1y, c2x, c2y, x, y);
}
break;
case 28: // shortint
b1 = code[i];
b2 = code[i + 1];
stack.push(((b1 << 24) | (b2 << 16)) >> 16);
i += 2;
break;
case 29: // callgsubr
codeIndex = stack.pop() + font.gsubrsBias;
subrCode = font.gsubrs[codeIndex];
if (subrCode) {
parse(subrCode);
}
break;
case 30: // vhcurveto
while (stack.length > 0) {
c1x = x;
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x + stack.shift();
y = c2y + (stack.length === 1 ? stack.shift() : 0);
curveTo(c1x, c1y, c2x, c2y, x, y);
if (stack.length === 0) {
break;
}
c1x = x + stack.shift();
c1y = y;
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
y = c2y + stack.shift();
x = c2x + (stack.length === 1 ? stack.shift() : 0);
curveTo(c1x, c1y, c2x, c2y, x, y);
}
break;
case 31: // hvcurveto
while (stack.length > 0) {
c1x = x + stack.shift();
c1y = y;
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
y = c2y + stack.shift();
x = c2x + (stack.length === 1 ? stack.shift() : 0);
curveTo(c1x, c1y, c2x, c2y, x, y);
if (stack.length === 0) {
break;
}
c1x = x;
c1y = y + stack.shift();
c2x = c1x + stack.shift();
c2y = c1y + stack.shift();
x = c2x + stack.shift();
y = c2y + (stack.length === 1 ? stack.shift() : 0);
curveTo(c1x, c1y, c2x, c2y, x, y);
}
break;
default:
if (v < 32) {
console.warn('Glyph ' + index + ': unknown operator ' + v);
}
else if (v < 247) {
stack.push(v - 139);
}
else if (v < 251) {
b1 = code[i];
i += 1;
stack.push((v - 247) * 256 + b1 + 108);
}
else if (v < 255) {
b1 = code[i];
i += 1;
stack.push(-(v - 251) * 256 - b1 - 108);
}
else {
b1 = code[i];
b2 = code[i + 1];
b3 = code[i + 2];
b4 = code[i + 3];
i += 4;
stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536);
}
}
}
}
parse(code);
const glyf = {
// 移除重复的起点和终点
contours: contours.map(contour => {
const last = contour.length - 1;
if (contour[0].x === contour[last].x && contour[0].y === contour[last].y) {
contour.splice(last, 1);
}
return contour;
}),
advanceWidth: width
};
if (glyfs.length) {
glyf.compound = true;
glyf.glyfs = glyfs;
}
return glyf;
}

View File

@ -1,23 +0,0 @@
/**
* @file cmap
* @author mengke01(kekee000@gmail.com)
*
* @see
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html
*/
import table from './table';
import parse from './cmap/parse';
import write from './cmap/write';
import sizeof from './cmap/sizeof';
export default table.create(
'cmap',
[],
{
write,
read: parse,
size: sizeof
}
);

View File

@ -1,253 +0,0 @@
/**
* @file 解析cmap表
* @author mengke01(kekee000@gmail.com)
*/
import readWindowsAllCodes from '../../util/readWindowsAllCodes';
/**
* 读取cmap子表
*
* @param {Reader} reader Reader对象
* @param {Object} ttf ttf对象
* @param {Object} subTable 子表对象
* @param {number} cmapOffset 子表的偏移
*/
function readSubTable(reader, ttf, subTable, cmapOffset) {
let i;
let l;
let glyphIdArray;
const startOffset = cmapOffset + subTable.offset;
let glyphCount;
subTable.format = reader.readUint16(startOffset);
// 0256 紧凑排列
if (subTable.format === 0) {
const format0 = subTable;
// 跳过format字段
format0.length = reader.readUint16();
format0.language = reader.readUint16();
glyphIdArray = [];
for (i = 0, l = format0.length - 6; i < l; i++) {
glyphIdArray.push(reader.readUint8());
}
format0.glyphIdArray = glyphIdArray;
}
else if (subTable.format === 2) {
const format2 = subTable;
// 跳过format字段
format2.length = reader.readUint16();
format2.language = reader.readUint16();
const subHeadKeys = [];
let maxSubHeadKey = 0;// 最大索引
let maxPos = -1; // 最大位置
for (let i = 0, l = 256; i < l; i++) {
subHeadKeys[i] = reader.readUint16() / 8;
if (subHeadKeys[i] > maxSubHeadKey) {
maxSubHeadKey = subHeadKeys[i];
maxPos = i;
}
}
const subHeads = [];
for (i = 0; i <= maxSubHeadKey; i++) {
subHeads[i] = {
firstCode: reader.readUint16(),
entryCount: reader.readUint16(),
idDelta: reader.readUint16(),
idRangeOffset: (reader.readUint16() - (maxSubHeadKey - i) * 8 - 2) / 2
};
}
glyphCount = (startOffset + format2.length - reader.offset) / 2;
const glyphs = [];
for (i = 0; i < glyphCount; i++) {
glyphs[i] = reader.readUint16();
}
format2.subHeadKeys = subHeadKeys;
format2.maxPos = maxPos;
format2.subHeads = subHeads;
format2.glyphs = glyphs;
}
// 双字节编码,非紧凑排列
else if (subTable.format === 4) {
const format4 = subTable;
// 跳过format字段
format4.length = reader.readUint16();
format4.language = reader.readUint16();
format4.segCountX2 = reader.readUint16();
format4.searchRange = reader.readUint16();
format4.entrySelector = reader.readUint16();
format4.rangeShift = reader.readUint16();
const segCount = format4.segCountX2 / 2;
// end code
const endCode = [];
for (i = 0; i < segCount; ++i) {
endCode.push(reader.readUint16());
}
format4.endCode = endCode;
format4.reservedPad = reader.readUint16();
// start code
const startCode = [];
for (i = 0; i < segCount; ++i) {
startCode.push(reader.readUint16());
}
format4.startCode = startCode;
// idDelta
const idDelta = [];
for (i = 0; i < segCount; ++i) {
idDelta.push(reader.readUint16());
}
format4.idDelta = idDelta;
format4.idRangeOffsetOffset = reader.offset;
// idRangeOffset
const idRangeOffset = [];
for (i = 0; i < segCount; ++i) {
idRangeOffset.push(reader.readUint16());
}
format4.idRangeOffset = idRangeOffset;
// 总长度 - glyphIdArray起始偏移/2
glyphCount = (format4.length - (reader.offset - startOffset)) / 2;
// 记录array offset
format4.glyphIdArrayOffset = reader.offset;
// glyphIdArray
glyphIdArray = [];
for (i = 0; i < glyphCount; ++i) {
glyphIdArray.push(reader.readUint16());
}
format4.glyphIdArray = glyphIdArray;
}
else if (subTable.format === 6) {
const format6 = subTable;
format6.length = reader.readUint16();
format6.language = reader.readUint16();
format6.firstCode = reader.readUint16();
format6.entryCount = reader.readUint16();
// 记录array offset
format6.glyphIdArrayOffset = reader.offset;
const glyphIndexArray = [];
const entryCount = format6.entryCount;
// 读取字符分组
for (i = 0; i < entryCount; ++i) {
glyphIndexArray.push(reader.readUint16());
}
format6.glyphIdArray = glyphIndexArray;
}
// defines segments for sparse representation in 4-byte character space
else if (subTable.format === 12) {
const format12 = subTable;
format12.reserved = reader.readUint16();
format12.length = reader.readUint32();
format12.language = reader.readUint32();
format12.nGroups = reader.readUint32();
const groups = [];
const nGroups = format12.nGroups;
// 读取字符分组
for (i = 0; i < nGroups; ++i) {
const group = {};
group.start = reader.readUint32();
group.end = reader.readUint32();
group.startId = reader.readUint32();
groups.push(group);
}
format12.groups = groups;
}
// format 14
else if (subTable.format === 14) {
const format14 = subTable;
format14.length = reader.readUint32();
const numVarSelectorRecords = reader.readUint32();
const groups = [];
let offset = reader.offset;
for (let i = 0; i < numVarSelectorRecords; i++) {
const varSelector = reader.readUint24(offset);
const defaultUVSOffset = reader.readUint32(offset + 3);
const nonDefaultUVSOffset = reader.readUint32(offset + 7);
offset += 11;
if (defaultUVSOffset) {
const numUnicodeValueRanges = reader.readUint32(startOffset + defaultUVSOffset);
for (let j = 0; j < numUnicodeValueRanges; j++) {
const startUnicode = reader.readUint24();
const additionalCount = reader.readUint8();
groups.push({
start: startUnicode,
end: startUnicode + additionalCount,
varSelector
});
}
}
if (nonDefaultUVSOffset) {
const numUVSMappings = reader.readUint32(startOffset + nonDefaultUVSOffset);
for (let j = 0; j < numUVSMappings; j++) {
const unicode = reader.readUint24();
const glyphId = reader.readUint16();
groups.push({
unicode,
glyphId,
varSelector
});
}
}
}
format14.groups = groups;
}
else {
console.warn('not support cmap format:' + subTable.format);
}
}
export default function parse(reader, ttf) {
const tcmap = {};
// eslint-disable-next-line no-invalid-this
const cmapOffset = this.offset;
reader.seek(cmapOffset);
tcmap.version = reader.readUint16(); // 编码方式
const numberSubtables = tcmap.numberSubtables = reader.readUint16(); // 表个数
const subTables = tcmap.tables = []; // 名字表
let offset = reader.offset;
// 使用offset读取以便于查找
for (let i = 0, l = numberSubtables; i < l; i++) {
const subTable = {};
subTable.platformID = reader.readUint16(offset);
subTable.encodingID = reader.readUint16(offset + 2);
subTable.offset = reader.readUint32(offset + 4);
readSubTable(reader, ttf, subTable, cmapOffset);
subTables.push(subTable);
offset += 8;
}
const cmap = readWindowsAllCodes(subTables, ttf);
return cmap;
}

View File

@ -1,153 +0,0 @@
/**
* @file 获取cmap表的大小
* @author mengke01(kekee000@gmail.com)
*/
/**
* 获取format4 delta值
* Delta is saved in signed int in cmap format 4 subtable,
* but can be in -0xFFFF..0 interval.
* -0x10000..-0x7FFF values are stored with offset.
*
* @param {number} delta delta值
* @return {number} delta值
*/
function encodeDelta(delta) {
return delta > 0x7FFF
? delta - 0x10000
: (delta < -0x7FFF ? delta + 0x10000 : delta);
}
/**
* 根据bound获取glyf segment
*
* @param {Array} glyfUnicodes glyf编码集合
* @param {number} bound 编码范围
* @return {Array} 码表
*/
function getSegments(glyfUnicodes, bound) {
let prevGlyph = null;
const result = [];
let segment = {};
glyfUnicodes.forEach((glyph) => {
if (bound === undefined || glyph.unicode <= bound) {
// 初始化编码头部这里unicode和graph id 都必须连续
if (prevGlyph === null
|| glyph.unicode !== prevGlyph.unicode + 1
|| glyph.id !== prevGlyph.id + 1
) {
if (prevGlyph !== null) {
segment.end = prevGlyph.unicode;
result.push(segment);
segment = {
start: glyph.unicode,
startId: glyph.id,
delta: encodeDelta(glyph.id - glyph.unicode)
};
}
else {
segment.start = glyph.unicode;
segment.startId = glyph.id;
segment.delta = encodeDelta(glyph.id - glyph.unicode);
}
}
prevGlyph = glyph;
}
});
// need to finish the last segment
if (prevGlyph !== null) {
segment.end = prevGlyph.unicode;
result.push(segment);
}
// 返回编码范围
return result;
}
/**
* 获取format0编码集合
*
* @param {Array} glyfUnicodes glyf编码集合
* @return {Array} 码表
*/
function getFormat0Segment(glyfUnicodes) {
const unicodes = [];
glyfUnicodes.forEach((u) => {
if (u.unicode !== undefined && u.unicode < 256) {
unicodes.push([u.unicode, u.id]);
}
});
// 按编码排序
unicodes.sort((a, b) => a[0] - b[0]);
return unicodes;
}
/**
* 对cmap数据进行预处理获取大小
*
* @param {Object} ttf ttf对象
* @return {number} 大小
*/
export default function sizeof(ttf) {
ttf.support.cmap = {};
let glyfUnicodes = [];
ttf.glyf.forEach((glyph, index) => {
let unicodes = glyph.unicode;
if (typeof glyph.unicode === 'number') {
unicodes = [glyph.unicode];
}
if (unicodes && unicodes.length) {
unicodes.forEach((unicode) => {
glyfUnicodes.push({
unicode,
id: unicode !== 0xFFFF ? index : 0
});
});
}
});
glyfUnicodes = glyfUnicodes.sort((a, b) => a.unicode - b.unicode);
ttf.support.cmap.unicodes = glyfUnicodes;
const unicodes2Bytes = glyfUnicodes;
ttf.support.cmap.format4Segments = getSegments(unicodes2Bytes, 0xFFFF);
ttf.support.cmap.format4Size = 24
+ ttf.support.cmap.format4Segments.length * 8;
ttf.support.cmap.format0Segments = getFormat0Segment(glyfUnicodes);
ttf.support.cmap.format0Size = 262;
// we need subtable 12 only if found unicodes with > 2 bytes.
const hasGLyphsOver2Bytes = unicodes2Bytes.some((glyph) => glyph.unicode > 0xFFFF);
if (hasGLyphsOver2Bytes) {
ttf.support.cmap.hasGLyphsOver2Bytes = hasGLyphsOver2Bytes;
const unicodes4Bytes = glyfUnicodes;
ttf.support.cmap.format12Segments = getSegments(unicodes4Bytes);
ttf.support.cmap.format12Size = 16
+ ttf.support.cmap.format12Segments.length * 12;
}
const size = 4 + (hasGLyphsOver2Bytes ? 32 : 24) // cmap header
+ ttf.support.cmap.format0Size // format 0
+ ttf.support.cmap.format4Size // format 4
+ (hasGLyphsOver2Bytes ? ttf.support.cmap.format12Size : 0); // format 12
return size;
}

View File

@ -1,172 +0,0 @@
/**
* @file 写cmap表
* @author mengke01(kekee000@gmail.com)
*/
/**
* 创建`子表0`
*
* @param {Writer} writer 写对象
* @param {Array} unicodes unicodes列表
* @return {Writer}
*/
function writeSubTable0(writer, unicodes) {
writer.writeUint16(0); // format
writer.writeUint16(262); // length
writer.writeUint16(0); // language
// Array of unicodes 0..255
let i = -1;
let unicode;
while ((unicode = unicodes.shift())) {
while (++i < unicode[0]) {
writer.writeUint8(0);
}
writer.writeUint8(unicode[1]);
i = unicode[0];
}
while (++i < 256) {
writer.writeUint8(0);
}
return writer;
}
/**
* 创建`子表4`
*
* @param {Writer} writer 写对象
* @param {Array} segments 分块编码列表
* @return {Writer}
*/
function writeSubTable4(writer, segments) {
writer.writeUint16(4); // format
writer.writeUint16(24 + segments.length * 8); // length
writer.writeUint16(0); // language
const segCount = segments.length + 1;
const maxExponent = Math.floor(Math.log(segCount) / Math.LN2);
const searchRange = 2 * Math.pow(2, maxExponent);
writer.writeUint16(segCount * 2); // segCountX2
writer.writeUint16(searchRange); // searchRange
writer.writeUint16(maxExponent); // entrySelector
writer.writeUint16(2 * segCount - searchRange); // rangeShift
// end list
segments.forEach((segment) => {
writer.writeUint16(segment.end);
});
writer.writeUint16(0xFFFF); // end code
writer.writeUint16(0); // reservedPad
// start list
segments.forEach((segment) => {
writer.writeUint16(segment.start);
});
writer.writeUint16(0xFFFF); // start code
// id delta
segments.forEach((segment) => {
writer.writeUint16(segment.delta);
});
writer.writeUint16(1);
// Array of range offsets, it doesn't matter when deltas present
for (let i = 0, l = segments.length; i < l; i++) {
writer.writeUint16(0);
}
writer.writeUint16(0); // rangeOffsetArray should be finished with 0
return writer;
}
/**
* 创建`子表12`
*
* @param {Writer} writer 写对象
* @param {Array} segments 分块编码列表
* @return {Writer}
*/
function writeSubTable12(writer, segments) {
writer.writeUint16(12); // format
writer.writeUint16(0); // reserved
writer.writeUint32(16 + segments.length * 12); // length
writer.writeUint32(0); // language
writer.writeUint32(segments.length); // nGroups
segments.forEach((segment) => {
writer.writeUint32(segment.start);
writer.writeUint32(segment.end);
writer.writeUint32(segment.startId);
});
return writer;
}
/**
* 写subtableheader
*
* @param {Writer} writer Writer对象
* @param {number} platform 平台
* @param {number} encoding 编码
* @param {number} offset 偏移
* @return {Writer}
*/
function writeSubTableHeader(writer, platform, encoding, offset) {
writer.writeUint16(platform); // platform
writer.writeUint16(encoding); // encoding
writer.writeUint32(offset); // offset
return writer;
}
/**
* 写cmap表数据
*
* @param {Object} writer 写入器
* @param {Object} ttf ttf对象
* @return {Object} 写入器
*/
export default function write(writer, ttf) {
const hasGLyphsOver2Bytes = ttf.support.cmap.hasGLyphsOver2Bytes;
// write table header.
writer.writeUint16(0); // version
writer.writeUint16(hasGLyphsOver2Bytes ? 4 : 3); // count
// header size
const subTableOffset = 4 + (hasGLyphsOver2Bytes ? 32 : 24);
const format4Size = ttf.support.cmap.format4Size;
const format0Size = ttf.support.cmap.format0Size;
// subtable 4, unicode
writeSubTableHeader(writer, 0, 3, subTableOffset);
// subtable 0, mac standard
writeSubTableHeader(writer, 1, 0, subTableOffset + format4Size);
// subtable 4, windows standard
writeSubTableHeader(writer, 3, 1, subTableOffset);
if (hasGLyphsOver2Bytes) {
writeSubTableHeader(writer, 3, 10, subTableOffset + format4Size + format0Size);
}
// write tables, order of table seem to be magic, it is taken from TTX tool
writeSubTable4(writer, ttf.support.cmap.format4Segments);
writeSubTable0(writer, ttf.support.cmap.format0Segments);
if (hasGLyphsOver2Bytes) {
writeSubTable12(writer, ttf.support.cmap.format12Segments);
}
return writer;
}

View File

@ -1,30 +0,0 @@
/**
* @file cvt表
* @author mengke01(kekee000@gmail.com)
*
* @reference: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cvt.html
*/
import table from './table';
export default table.create(
'cvt',
[],
{
read(reader, ttf) {
const length = ttf.tables.cvt.length;
return reader.readBytes(this.offset, length);
},
write(writer, ttf) {
if (ttf.cvt) {
writer.writeBytes(ttf.cvt, ttf.cvt.length);
}
},
size(ttf) {
return ttf.cvt ? ttf.cvt.length : 0;
}
}
);

View File

@ -1,50 +0,0 @@
/**
* @file directory , 读取和写入ttf表索引
* @author mengke01(kekee000@gmail.com)
*
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html
*/
import table from './table';
export default table.create(
'directory',
[],
{
read(reader, ttf) {
const tables = {};
const numTables = ttf.numTables;
const offset = this.offset;
for (let i = offset, l = numTables * 16; i < l; i += 16) {
const name = reader.readString(i, 4).trim();
tables[name] = {
name,
checkSum: reader.readUint32(i + 4),
offset: reader.readUint32(i + 8),
length: reader.readUint32(i + 12)
};
}
return tables;
},
write(writer, ttf) {
const tables = ttf.support.tables;
for (let i = 0, l = tables.length; i < l; i++) {
writer.writeString((tables[i].name + ' ').slice(0, 4));
writer.writeUint32(tables[i].checkSum);
writer.writeUint32(tables[i].offset);
writer.writeUint32(tables[i].length);
}
return writer;
},
size(ttf) {
return ttf.numTables * 16;
}
}
);

View File

@ -1,31 +0,0 @@
/**
* @file fpgm
* @author mengke01(kekee000@gmail.com)
*
* reference: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6fpgm.html
*/
import table from './table';
export default table.create(
'fpgm',
[],
{
read(reader, ttf) {
const length = ttf.tables.fpgm.length;
return reader.readBytes(this.offset, length);
},
write(writer, ttf) {
if (ttf.fpgm) {
writer.writeBytes(ttf.fpgm, ttf.fpgm.length);
}
},
size(ttf) {
return ttf.fpgm ? ttf.fpgm.length : 0;
}
}
);

View File

@ -1,31 +0,0 @@
/**
* @file gasp
* 对于需要hinting的字号需要这个表否则会导致错误
* @author mengke01(kekee000@gmail.com)
* reference: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gasp.html
*/
import table from './table';
export default table.create(
'gasp',
[],
{
read(reader, ttf) {
const length = ttf.tables.gasp.length;
return reader.readBytes(this.offset, length);
},
write(writer, ttf) {
if (ttf.gasp) {
writer.writeBytes(ttf.gasp, ttf.gasp.length);
}
},
size(ttf) {
return ttf.gasp ? ttf.gasp.length : 0;
}
}
);

View File

@ -1,112 +0,0 @@
/**
* @file glyf表
* @author mengke01(kekee000@gmail.com)
*
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html
*/
import table from './table';
import parse from './glyf/parse';
import write from './glyf/write';
import sizeof from './glyf/sizeof';
import {isEmptyObject} from '../../common/lang';
export default table.create(
'glyf',
[],
{
read(reader, ttf) {
const startOffset = this.offset;
const loca = ttf.loca;
const numGlyphs = ttf.maxp.numGlyphs;
const glyphs = [];
reader.seek(startOffset);
// subset
const subset = ttf.readOptions.subset;
if (subset && subset.length > 0) {
const subsetMap = {
0: true // 设置.notdef
};
subsetMap[0] = true;
// subset map
const cmap = ttf.cmap;
// unicode to index
Object.keys(cmap).forEach((c) => {
if (subset.indexOf(+c) > -1) {
const i = cmap[c];
subsetMap[i] = true;
}
});
ttf.subsetMap = subsetMap;
const parsedGlyfMap = {};
// 循环解析subset相关的glyf包括复合字形相关的字形
const travelsParse = function travels(subsetMap) {
const newSubsetMap = {};
Object.keys(subsetMap).forEach((i) => {
const index = +i;
parsedGlyfMap[index] = true;
// 当前的和下一个一样,或者最后一个无轮廓
if (loca[index] === loca[index + 1]) {
glyphs[index] = {
contours: []
};
}
else {
glyphs[index] = parse(reader, ttf, startOffset + loca[index]);
}
if (glyphs[index].compound) {
glyphs[index].glyfs.forEach((g) => {
if (!parsedGlyfMap[g.glyphIndex]) {
newSubsetMap[g.glyphIndex] = true;
}
});
}
});
if (!isEmptyObject(newSubsetMap)) {
travels(newSubsetMap);
}
};
travelsParse(subsetMap);
return glyphs;
}
// 解析字体轮廓, 前n-1个
let i;
let l;
for (i = 0, l = numGlyphs - 1; i < l; i++) {
// 当前的和下一个一样,或者最后一个无轮廓
if (loca[i] === loca[i + 1]) {
glyphs[i] = {
contours: []
};
}
else {
glyphs[i] = parse(reader, ttf, startOffset + loca[i]);
}
}
// 最后一个轮廓
if ((ttf.tables.glyf.length - loca[i]) < 5) {
glyphs[i] = {
contours: []
};
}
else {
glyphs[i] = parse(reader, ttf, startOffset + loca[i]);
}
return glyphs;
},
write,
size: sizeof
}
);

View File

@ -1,308 +0,0 @@
/**
* @file 解析glyf轮廓
* @author mengke01(kekee000@gmail.com)
*/
import glyFlag from '../../enum/glyFlag';
import componentFlag from '../../enum/componentFlag';
const MAX_INSTRUCTION_LENGTH = 5000; // 设置instructions阈值防止读取错误
const MAX_NUMBER_OF_COORDINATES = 20000; // 设置坐标最大个数阈值防止glyf读取错误
/**
* 读取简单字形
*
* @param {Reader} reader Reader对象
* @param {Object} glyf 空glyf
* @return {Object} 解析后的glyf
*/
function parseSimpleGlyf(reader, glyf) {
const offset = reader.offset;
// 轮廓点个数
const numberOfCoordinates = glyf.endPtsOfContours[
glyf.endPtsOfContours.length - 1
] + 1;
// 判断坐标是否超过最大个数
if (numberOfCoordinates > MAX_NUMBER_OF_COORDINATES) {
console.warn('error read glyf coordinates:' + offset);
return glyf;
}
// 获取flag标志
let i;
let length;
const flags = [];
let flag;
i = 0;
while (i < numberOfCoordinates) {
flag = reader.readUint8();
flags.push(flag);
i++;
// 标志位3重复flag
if ((flag & glyFlag.REPEAT) && i < numberOfCoordinates) {
// 重复个数
const repeat = reader.readUint8();
for (let j = 0; j < repeat; j++) {
flags.push(flag);
i++;
}
}
}
// 坐标集合
const coordinates = [];
const xCoordinates = [];
let prevX = 0;
let x;
for (i = 0, length = flags.length; i < length; ++i) {
x = 0;
flag = flags[i];
// 标志位1
// If set, the corresponding y-coordinate is 1 byte long, not 2
if (flag & glyFlag.XSHORT) {
x = reader.readUint8();
// 标志位5
x = (flag & glyFlag.XSAME) ? x : -1 * x;
}
// 与上一值一致
else if (flag & glyFlag.XSAME) {
x = 0;
}
// 新值
else {
x = reader.readInt16();
}
prevX += x;
xCoordinates[i] = prevX;
coordinates[i] = {
x: prevX,
y: 0
};
if (flag & glyFlag.ONCURVE) {
coordinates[i].onCurve = true;
}
}
const yCoordinates = [];
let prevY = 0;
let y;
for (i = 0, length = flags.length; i < length; i++) {
y = 0;
flag = flags[i];
if (flag & glyFlag.YSHORT) {
y = reader.readUint8();
y = (flag & glyFlag.YSAME) ? y : -1 * y;
}
else if (flag & glyFlag.YSAME) {
y = 0;
}
else {
y = reader.readInt16();
}
prevY += y;
yCoordinates[i] = prevY;
if (coordinates[i]) {
coordinates[i].y = prevY;
}
}
// 计算轮廓集合
if (coordinates.length) {
const endPtsOfContours = glyf.endPtsOfContours;
const contours = [];
contours.push(coordinates.slice(0, endPtsOfContours[0] + 1));
for (i = 1, length = endPtsOfContours.length; i < length; i++) {
contours.push(coordinates.slice(endPtsOfContours[i - 1] + 1, endPtsOfContours[i] + 1));
}
glyf.contours = contours;
}
return glyf;
}
/**
* 读取复合字形
*
* @param {Reader} reader Reader对象
* @param {Object} glyf glyf对象
* @return {Object} glyf对象
*/
function parseCompoundGlyf(reader, glyf) {
glyf.compound = true;
glyf.glyfs = [];
let flags;
let g;
// 读取复杂字形
do {
flags = reader.readUint16();
g = {};
g.flags = flags;
g.glyphIndex = reader.readUint16();
let arg1 = 0;
let arg2 = 0;
let scaleX = 16384;
let scaleY = 16384;
let scale01 = 0;
let scale10 = 0;
if (componentFlag.ARG_1_AND_2_ARE_WORDS & flags) {
arg1 = reader.readInt16();
arg2 = reader.readInt16();
}
else {
arg1 = reader.readInt8();
arg2 = reader.readInt8();
}
if (componentFlag.ROUND_XY_TO_GRID & flags) {
arg1 = Math.round(arg1);
arg2 = Math.round(arg2);
}
if (componentFlag.WE_HAVE_A_SCALE & flags) {
scaleX = reader.readInt16();
scaleY = scaleX;
}
else if (componentFlag.WE_HAVE_AN_X_AND_Y_SCALE & flags) {
scaleX = reader.readInt16();
scaleY = reader.readInt16();
}
else if (componentFlag.WE_HAVE_A_TWO_BY_TWO & flags) {
scaleX = reader.readInt16();
scale01 = reader.readInt16();
scale10 = reader.readInt16();
scaleY = reader.readInt16();
}
if (componentFlag.ARGS_ARE_XY_VALUES & flags) {
g.useMyMetrics = !!flags & componentFlag.USE_MY_METRICS;
g.overlapCompound = !!flags & componentFlag.OVERLAP_COMPOUND;
g.transform = {
a: Math.round(10000 * scaleX / 16384) / 10000,
b: Math.round(10000 * scale01 / 16384) / 10000,
c: Math.round(10000 * scale10 / 16384) / 10000,
d: Math.round(10000 * scaleY / 16384) / 10000,
e: arg1,
f: arg2
};
}
else {
g.points = [arg1, arg2];
g.transform = {
a: Math.round(10000 * scaleX / 16384) / 10000,
b: Math.round(10000 * scale01 / 16384) / 10000,
c: Math.round(10000 * scale10 / 16384) / 10000,
d: Math.round(10000 * scaleY / 16384) / 10000,
e: 0,
f: 0
};
}
glyf.glyfs.push(g);
} while (componentFlag.MORE_COMPONENTS & flags);
if (componentFlag.WE_HAVE_INSTRUCTIONS & flags) {
const length = reader.readUint16();
if (length < MAX_INSTRUCTION_LENGTH) {
const instructions = [];
for (let i = 0; i < length; ++i) {
instructions.push(reader.readUint8());
}
glyf.instructions = instructions;
}
else {
console.warn(length);
}
}
return glyf;
}
/**
* 解析glyf轮廓
*
* @param {Reader} reader 读取器
* @param {Object} ttf ttf对象
* @param {number=} offset 偏移
* @return {Object} glyf对象
*/
export default function parseGlyf(reader, ttf, offset) {
if (null != offset) {
reader.seek(offset);
}
const glyf = {};
let i;
let length;
let instructions;
// 边界值
const numberOfContours = reader.readInt16();
glyf.xMin = reader.readInt16();
glyf.yMin = reader.readInt16();
glyf.xMax = reader.readInt16();
glyf.yMax = reader.readInt16();
// 读取简单字形
if (numberOfContours >= 0) {
// endPtsOfConturs
glyf.endPtsOfContours = [];
if (numberOfContours > 0) {
for (i = 0; i < numberOfContours; i++) {
glyf.endPtsOfContours.push(reader.readUint16());
}
}
else {
delete glyf.xMin;
delete glyf.yMin;
delete glyf.xMax;
delete glyf.yMax;
}
// instructions
length = reader.readUint16();
if (length) {
// range错误
if (length < MAX_INSTRUCTION_LENGTH) {
instructions = [];
for (i = 0; i < length; ++i) {
instructions.push(reader.readUint8());
}
glyf.instructions = instructions;
}
else {
console.warn(length);
}
}
parseSimpleGlyf(reader, glyf);
delete glyf.endPtsOfContours;
}
else {
parseCompoundGlyf(reader, glyf);
}
return glyf;
}

View File

@ -1,257 +0,0 @@
/**
* @file 获取glyf的大小同时对glyf写入进行预处理
* @author mengke01(kekee000@gmail.com)
*/
import glyFlag from '../../enum/glyFlag';
/**
* 获取glyf的大小
*
* @param {Object} glyf glyf对象
* @param {Object} glyfSupport glyf相关统计
* @param {boolean} hinting 是否保留hints
* @param {boolean} writeZeroContoursGlyfData 是否写空轮廓 glyph
* @return {number} size大小
*/
function sizeofSimple(glyf, glyfSupport, hinting, writeZeroContoursGlyfData) {
if (!writeZeroContoursGlyfData && (!glyf.contours || !glyf.contours.length)) {
return 0;
}
// fixed header + endPtsOfContours
let result = 12
+ (glyf.contours || []).length * 2
+ (glyfSupport.flags || []).length;
(glyfSupport.xCoord || []).forEach((x) => {
result += 0 <= x && x <= 0xFF ? 1 : 2;
});
(glyfSupport.yCoord || []).forEach((y) => {
result += 0 <= y && y <= 0xFF ? 1 : 2;
});
return result + (hinting && glyf.instructions ? glyf.instructions.length : 0);
}
/**
* 复合图元size
*
* @param {Object} glyf glyf对象
* @param {boolean} hinting 是否保留hints, compound 图元暂时不做hinting
* @return {number} size大小
*/
// eslint-disable-next-line no-unused-vars
function sizeofCompound(glyf, hinting) {
let size = 10;
let transform;
glyf.glyfs.forEach((g) => {
transform = g.transform;
// flags + glyfIndex
size += 4;
// a, b, c, d, e
// xy values or points
if (transform.e < 0 || transform.e > 0x7F || transform.f < 0 || transform.f > 0x7F) {
size += 4;
}
else {
size += 2;
}
// 01 , 10
if (transform.b || transform.c) {
size += 8;
}
// scale
else if (transform.a !== 1 || transform.d !== 1) {
size += transform.a === transform.d ? 2 : 4;
}
});
return size;
}
/**
* 获取flags
*
* @param {Object} glyf glyf对象
* @param {Object} glyfSupport glyf相关统计
* @return {Array}
*/
function getFlags(glyf, glyfSupport) {
if (!glyf.contours || 0 === glyf.contours.length) {
return glyfSupport;
}
const flags = [];
const xCoord = [];
const yCoord = [];
const contours = glyf.contours;
let contour;
let prev;
let first = true;
for (let j = 0, cl = contours.length; j < cl; j++) {
contour = contours[j];
for (let i = 0, l = contour.length; i < l; i++) {
const point = contour[i];
if (first) {
xCoord.push(point.x);
yCoord.push(point.y);
first = false;
}
else {
xCoord.push(point.x - prev.x);
yCoord.push(point.y - prev.y);
}
flags.push(point.onCurve ? glyFlag.ONCURVE : 0);
prev = point;
}
}
// compress
const flagsC = [];
const xCoordC = [];
const yCoordC = [];
let x;
let y;
let prevFlag;
let repeatPoint = -1;
flags.forEach((flag, index) => {
x = xCoord[index];
y = yCoord[index];
// 第一个
if (index === 0) {
if (-0xFF <= x && x <= 0xFF) {
flag += glyFlag.XSHORT;
if (x >= 0) {
flag += glyFlag.XSAME;
}
x = Math.abs(x);
}
if (-0xFF <= y && y <= 0xFF) {
flag += glyFlag.YSHORT;
if (y >= 0) {
flag += glyFlag.YSAME;
}
y = Math.abs(y);
}
flagsC.push(prevFlag = flag);
xCoordC.push(x);
yCoordC.push(y);
}
// 后续
else {
if (x === 0) {
flag += glyFlag.XSAME;
}
else {
if (-0xFF <= x && x <= 0xFF) {
flag += glyFlag.XSHORT;
if (x > 0) {
flag += glyFlag.XSAME;
}
x = Math.abs(x);
}
xCoordC.push(x);
}
if (y === 0) {
flag += glyFlag.YSAME;
}
else {
if (-0xFF <= y && y <= 0xFF) {
flag += glyFlag.YSHORT;
if (y > 0) {
flag += glyFlag.YSAME;
}
y = Math.abs(y);
}
yCoordC.push(y);
}
// repeat
if (flag === prevFlag) {
// 记录重复个数
if (-1 === repeatPoint) {
repeatPoint = flagsC.length - 1;
flagsC[repeatPoint] |= glyFlag.REPEAT;
flagsC.push(1);
}
else {
++flagsC[repeatPoint + 1];
}
}
else {
repeatPoint = -1;
flagsC.push(prevFlag = flag);
}
}
});
glyfSupport.flags = flagsC;
glyfSupport.xCoord = xCoordC;
glyfSupport.yCoord = yCoordC;
return glyfSupport;
}
/**
* 对glyf数据进行预处理获取大小
*
* @param {Object} ttf ttf对象
* @return {number} 大小
*/
export default function sizeof(ttf) {
ttf.support.glyf = [];
let tableSize = 0;
const hinting = ttf.writeOptions ? ttf.writeOptions.hinting : false;
const writeZeroContoursGlyfData = ttf.writeOptions ? ttf.writeOptions.writeZeroContoursGlyfData : false;
ttf.glyf.forEach((glyf) => {
let glyfSupport = {};
glyfSupport = glyf.compound ? glyfSupport : getFlags(glyf, glyfSupport);
const glyfSize = glyf.compound
? sizeofCompound(glyf, hinting)
: sizeofSimple(glyf, glyfSupport, hinting, writeZeroContoursGlyfData);
let size = glyfSize;
// 4字节对齐
if (size % 4) {
size += 4 - size % 4;
}
glyfSupport.glyfSize = glyfSize;
glyfSupport.size = size;
ttf.support.glyf.push(glyfSupport);
tableSize += size;
});
ttf.support.glyf.tableSize = tableSize;
// 写header的indexToLocFormat
ttf.head.indexToLocFormat = tableSize > 65536 ? 1 : 0;
return ttf.support.glyf.tableSize;
}

View File

@ -1,163 +0,0 @@
/**
* @file 写glyf数据
* @author mengke01(kekee000@gmail.com)
*/
import componentFlag from '../../enum/componentFlag';
/**
* 写glyf
*
* @param {Object} writer 写入器
* @param {Object} ttf ttf对象
* @return {Object} 写入器
*/
export default function write(writer, ttf) {
const hinting = ttf.writeOptions ? ttf.writeOptions.hinting : false;
const writeZeroContoursGlyfData = ttf.writeOptions ? ttf.writeOptions.writeZeroContoursGlyfData : false;
ttf.glyf.forEach((glyf, index) => {
// 非复合图元没有轮廓则不写
if (!glyf.compound && !writeZeroContoursGlyfData && (!glyf.contours || !glyf.contours.length)) {
return;
}
// header
writer.writeInt16(glyf.compound ? -1 : (glyf.contours || []).length);
writer.writeInt16(glyf.xMin);
writer.writeInt16(glyf.yMin);
writer.writeInt16(glyf.xMax);
writer.writeInt16(glyf.yMax);
let i;
let l;
let flags;
// 复合图元
if (glyf.compound) {
for (i = 0, l = glyf.glyfs.length; i < l; i++) {
const g = glyf.glyfs[i];
flags = g.points
? 0 : (componentFlag.ARGS_ARE_XY_VALUES + componentFlag.ROUND_XY_TO_GRID); // xy values
// more components
if (i < l - 1) {
flags += componentFlag.MORE_COMPONENTS;
}
// use my metrics
flags += g.useMyMetrics ? componentFlag.USE_MY_METRICS : 0;
// overlap compound
flags += g.overlapCompound ? componentFlag.OVERLAP_COMPOUND : 0;
const transform = g.transform;
const a = transform.a;
const b = transform.b;
const c = transform.c;
const d = transform.d;
const e = g.points ? g.points[0] : transform.e;
const f = g.points ? g.points[1] : transform.f;
// xy values or points
// int 8 放不下则用int16放
if (e < 0 || e > 0x7F || f < 0 || f > 0x7F) {
flags += componentFlag.ARG_1_AND_2_ARE_WORDS;
}
if (b || c) {
flags += componentFlag.WE_HAVE_A_TWO_BY_TWO;
}
else if ((a !== 1 || d !== 1) && a === d) {
flags += componentFlag.WE_HAVE_A_SCALE;
}
else if (a !== 1 || d !== 1) {
flags += componentFlag.WE_HAVE_AN_X_AND_Y_SCALE;
}
writer.writeUint16(flags);
writer.writeUint16(g.glyphIndex);
if (componentFlag.ARG_1_AND_2_ARE_WORDS & flags) {
writer.writeInt16(e);
writer.writeInt16(f);
}
else {
writer.writeUint8(e);
writer.writeUint8(f);
}
if (componentFlag.WE_HAVE_A_SCALE & flags) {
writer.writeInt16(Math.round(a * 16384));
}
else if (componentFlag.WE_HAVE_AN_X_AND_Y_SCALE & flags) {
writer.writeInt16(Math.round(a * 16384));
writer.writeInt16(Math.round(d * 16384));
}
else if (componentFlag.WE_HAVE_A_TWO_BY_TWO & flags) {
writer.writeInt16(Math.round(a * 16384));
writer.writeInt16(Math.round(b * 16384));
writer.writeInt16(Math.round(c * 16384));
writer.writeInt16(Math.round(d * 16384));
}
}
}
else {
let endPtsOfContours = -1;
(glyf.contours || []).forEach((contour) => {
endPtsOfContours += contour.length;
writer.writeUint16(endPtsOfContours);
});
// instruction
if (hinting && glyf.instructions) {
const instructions = glyf.instructions;
writer.writeUint16(instructions.length);
for (i = 0, l = instructions.length; i < l; i++) {
writer.writeUint8(instructions[i]);
}
}
else {
writer.writeUint16(0);
}
// 获取暂存中的flags
flags = ttf.support.glyf[index].flags || [];
for (i = 0, l = flags.length; i < l; i++) {
writer.writeUint8(flags[i]);
}
const xCoord = ttf.support.glyf[index].xCoord || [];
for (i = 0, l = xCoord.length; i < l; i++) {
if (0 <= xCoord[i] && xCoord[i] <= 0xFF) {
writer.writeUint8(xCoord[i]);
}
else {
writer.writeInt16(xCoord[i]);
}
}
const yCoord = ttf.support.glyf[index].yCoord || [];
for (i = 0, l = yCoord.length; i < l; i++) {
if (0 <= yCoord[i] && yCoord[i] <= 0xFF) {
writer.writeUint8(yCoord[i]);
}
else {
writer.writeInt16(yCoord[i]);
}
}
}
// 4字节对齐
const glyfSize = ttf.support.glyf[index].glyfSize;
if (glyfSize % 4) {
writer.writeEmpty(4 - glyfSize % 4);
}
});
return writer;
}

View File

@ -1,30 +0,0 @@
/**
* @file head表
* @author mengke01(kekee000@gmail.com)
*/
import table from './table';
import struct from './struct';
export default table.create(
'head',
[
['version', struct.Fixed],
['fontRevision', struct.Fixed],
['checkSumAdjustment', struct.Uint32],
['magickNumber', struct.Uint32],
['flags', struct.Uint16],
['unitsPerEm', struct.Uint16],
['created', struct.LongDateTime],
['modified', struct.LongDateTime],
['xMin', struct.Int16],
['yMin', struct.Int16],
['xMax', struct.Int16],
['yMax', struct.Int16],
['macStyle', struct.Uint16],
['lowestRecPPEM', struct.Uint16],
['fontDirectionHint', struct.Int16],
['indexToLocFormat', struct.Int16],
['glyphDataFormat', struct.Int16]
]
);

View File

@ -1,31 +0,0 @@
/**
* @file hhea
* @author mengke01(kekee000@gmail.com)
*
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6hhea.html
*/
import table from './table';
import struct from './struct';
export default table.create(
'hhea',
[
['version', struct.Fixed],
['ascent', struct.Int16],
['descent', struct.Int16],
['lineGap', struct.Int16],
['advanceWidthMax', struct.Uint16],
['minLeftSideBearing', struct.Int16],
['minRightSideBearing', struct.Int16],
['xMaxExtent', struct.Int16],
['caretSlopeRise', struct.Int16],
['caretSlopeRun', struct.Int16],
['caretOffset', struct.Int16],
['reserved0', struct.Int16],
['reserved1', struct.Int16],
['reserved2', struct.Int16],
['reserved3', struct.Int16],
['metricDataFormat', struct.Int16],
['numOfLongHorMetrics', struct.Uint16]
]
);

View File

@ -1,85 +0,0 @@
/**
* @file hmtx
* @author mengke01(kekee000@gmail.com)
*
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6hmtx.html
*/
import table from './table';
export default table.create(
'hmtx',
[],
{
read(reader, ttf) {
const offset = this.offset;
reader.seek(offset);
const numOfLongHorMetrics = ttf.hhea.numOfLongHorMetrics;
const hMetrics = [];
let i;
let hMetric;
for (i = 0; i < numOfLongHorMetrics; ++i) {
hMetric = {};
hMetric.advanceWidth = reader.readUint16();
hMetric.leftSideBearing = reader.readInt16();
hMetrics.push(hMetric);
}
// 最后一个宽度
const advanceWidth = hMetrics[numOfLongHorMetrics - 1].advanceWidth;
const numOfLast = ttf.maxp.numGlyphs - numOfLongHorMetrics;
// 获取后续的hmetrics
for (i = 0; i < numOfLast; ++i) {
hMetric = {};
hMetric.advanceWidth = advanceWidth;
hMetric.leftSideBearing = reader.readInt16();
hMetrics.push(hMetric);
}
return hMetrics;
},
write(writer, ttf) {
let i;
const numOfLongHorMetrics = ttf.hhea.numOfLongHorMetrics;
for (i = 0; i < numOfLongHorMetrics; ++i) {
writer.writeUint16(ttf.glyf[i].advanceWidth);
writer.writeInt16(ttf.glyf[i].leftSideBearing);
}
// 最后一个宽度
const numOfLast = ttf.glyf.length - numOfLongHorMetrics;
for (i = 0; i < numOfLast; ++i) {
writer.writeInt16(ttf.glyf[numOfLongHorMetrics + i].leftSideBearing);
}
return writer;
},
size(ttf) {
// 计算同最后一个advanceWidth相等的元素个数
let numOfLast = 0;
// 最后一个advanceWidth
const advanceWidth = ttf.glyf[ttf.glyf.length - 1].advanceWidth;
for (let i = ttf.glyf.length - 2; i >= 0; i--) {
if (advanceWidth === ttf.glyf[i].advanceWidth) {
numOfLast++;
}
else {
break;
}
}
ttf.hhea.numOfLongHorMetrics = ttf.glyf.length - numOfLast;
return 4 * ttf.hhea.numOfLongHorMetrics + 2 * numOfLast;
}
}
);

View File

@ -1,30 +0,0 @@
/**
* @file kern
* @author fr33z00(https://github.com/fr33z00)
*
* @reference: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html
*/
import table from './table';
export default table.create(
'kern',
[],
{
read(reader, ttf) {
const length = ttf.tables.kern.length;
return reader.readBytes(this.offset, length);
},
write(writer, ttf) {
if (ttf.kern) {
writer.writeBytes(ttf.kern, ttf.kern.length);
}
},
size(ttf) {
return ttf.kern ? ttf.kern.length : 0;
}
}
);

View File

@ -1,30 +0,0 @@
/**
* @file kerx
* @author mengke01(kekee000@gmail.com)
*
* @reference: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kerx.html
*/
import table from './table';
export default table.create(
'kerx',
[],
{
read(reader, ttf) {
const length = ttf.tables.kerx.length;
return reader.readBytes(this.offset, length);
},
write(writer, ttf) {
if (ttf.kerx) {
writer.writeBytes(ttf.kerx, ttf.kerx.length);
}
},
size(ttf) {
return ttf.kerx ? ttf.kerx.length : 0;
}
}
);

View File

@ -1,67 +0,0 @@
/**
* @file loca表
* @author mengke01(kekee000@gmail.com)
*/
import table from './table';
import struct from './struct';
export default table.create(
'loca',
[],
{
read(reader, ttf) {
let offset = this.offset;
const indexToLocFormat = ttf.head.indexToLocFormat;
// indexToLocFormat有2字节和4字节的区别
const type = struct.names[(indexToLocFormat === 0) ? struct.Uint16 : struct.Uint32];
const size = (indexToLocFormat === 0) ? 2 : 4; // 字节大小
const sizeRatio = (indexToLocFormat === 0) ? 2 : 1; // 真实地址偏移
const wordOffset = [];
reader.seek(offset);
const numGlyphs = ttf.maxp.numGlyphs;
for (let i = 0; i < numGlyphs; ++i) {
wordOffset.push(reader.read(type, offset, false) * sizeRatio);
offset += size;
}
return wordOffset;
},
write(writer, ttf) {
const glyfSupport = ttf.support.glyf;
let offset = ttf.support.glyf.offset || 0;
const indexToLocFormat = ttf.head.indexToLocFormat;
const sizeRatio = (indexToLocFormat === 0) ? 0.5 : 1;
const numGlyphs = ttf.glyf.length;
for (let i = 0; i < numGlyphs; ++i) {
if (indexToLocFormat) {
writer.writeUint32(offset);
}
else {
writer.writeUint16(offset);
}
offset += glyfSupport[i].size * sizeRatio;
}
// write extra
if (indexToLocFormat) {
writer.writeUint32(offset);
}
else {
writer.writeUint16(offset);
}
return writer;
},
size(ttf) {
const locaCount = ttf.glyf.length + 1;
return ttf.head.indexToLocFormat ? locaCount * 4 : locaCount * 2;
}
}
);

View File

@ -1,39 +0,0 @@
/**
* @file maxp
* @author mengke01(kekee000@gmail.com)
*/
import table from './table';
import struct from './struct';
export default table.create(
'maxp',
[
['version', struct.Fixed],
['numGlyphs', struct.Uint16],
['maxPoints', struct.Uint16],
['maxContours', struct.Uint16],
['maxCompositePoints', struct.Uint16],
['maxCompositeContours', struct.Uint16],
['maxZones', struct.Uint16],
['maxTwilightPoints', struct.Uint16],
['maxStorage', struct.Uint16],
['maxFunctionDefs', struct.Uint16],
['maxInstructionDefs', struct.Uint16],
['maxStackElements', struct.Uint16],
['maxSizeOfInstructions', struct.Uint16],
['maxComponentElements', struct.Uint16],
['maxComponentDepth', struct.Int16]
],
{
write(writer, ttf) {
table.write.call(this, writer, ttf.support);
return writer;
},
size() {
return 32;
}
}
);

View File

@ -1,166 +0,0 @@
/**
* @file name表
* @author mengke01(kekee000@gmail.com)
*/
import table from './table';
import nameIdTbl from '../enum/nameId';
import string from '../util/string';
import platformTbl from '../enum/platform';
import {mac, win} from '../enum/encoding';
export default table.create(
'name',
[],
{
read(reader) {
let offset = this.offset;
reader.seek(offset);
const nameTbl = {};
nameTbl.format = reader.readUint16();
nameTbl.count = reader.readUint16();
nameTbl.stringOffset = reader.readUint16();
const nameRecordTbl = [];
const count = nameTbl.count;
let i;
let nameRecord;
for (i = 0; i < count; ++i) {
nameRecord = {};
nameRecord.platform = reader.readUint16();
nameRecord.encoding = reader.readUint16();
nameRecord.language = reader.readUint16();
nameRecord.nameId = reader.readUint16();
nameRecord.length = reader.readUint16();
nameRecord.offset = reader.readUint16();
nameRecordTbl.push(nameRecord);
}
offset += nameTbl.stringOffset;
// 读取字符名字
for (i = 0; i < count; ++i) {
nameRecord = nameRecordTbl[i];
nameRecord.name = reader.readBytes(offset + nameRecord.offset, nameRecord.length);
}
const names = {};
// mac 下的english name
let platform = platformTbl.Macintosh;
let encoding = mac.Default;
let language = 0;
// 如果有windows 下的 english则用windows下的 name
if (nameRecordTbl.some((record) => record.platform === platformTbl.Microsoft
&& record.encoding === win.UCS2
&& record.language === 1033)) {
platform = platformTbl.Microsoft;
encoding = win.UCS2;
language = 1033;
}
for (i = 0; i < count; ++i) {
nameRecord = nameRecordTbl[i];
if (nameRecord.platform === platform
&& nameRecord.encoding === encoding
&& nameRecord.language === language
&& nameIdTbl[nameRecord.nameId]) {
names[nameIdTbl[nameRecord.nameId]] = language === 0
? string.getUTF8String(nameRecord.name)
: string.getUCS2String(nameRecord.name);
}
}
return names;
},
write(writer, ttf) {
const nameRecordTbl = ttf.support.name;
writer.writeUint16(0); // format
writer.writeUint16(nameRecordTbl.length); // count
writer.writeUint16(6 + nameRecordTbl.length * 12); // string offset
// write name tbl header
let offset = 0;
nameRecordTbl.forEach((nameRecord) => {
writer.writeUint16(nameRecord.platform);
writer.writeUint16(nameRecord.encoding);
writer.writeUint16(nameRecord.language);
writer.writeUint16(nameRecord.nameId);
writer.writeUint16(nameRecord.name.length);
writer.writeUint16(offset); // offset
offset += nameRecord.name.length;
});
// write name tbl strings
nameRecordTbl.forEach((nameRecord) => {
writer.writeBytes(nameRecord.name);
});
return writer;
},
size(ttf) {
const names = ttf.name;
let nameRecordTbl = [];
// 写入name信息
// 这里为了简化书写,仅支持英文编码字符,
// 中文编码字符将被转化成url encode
let size = 6;
Object.keys(names).forEach((name) => {
const id = nameIdTbl.names[name];
const utf8Bytes = string.toUTF8Bytes(names[name]);
const usc2Bytes = string.toUCS2Bytes(names[name]);
if (undefined !== id) {
// mac
nameRecordTbl.push({
nameId: id,
platform: 1,
encoding: 0,
language: 0,
name: utf8Bytes
});
// windows
nameRecordTbl.push({
nameId: id,
platform: 3,
encoding: 1,
language: 1033,
name: usc2Bytes
});
// 子表大小
size += 12 * 2 + utf8Bytes.length + usc2Bytes.length;
}
});
const namingOrder = ['platform', 'encoding', 'language', 'nameId'];
nameRecordTbl = nameRecordTbl.sort((a, b) => {
let l = 0;
namingOrder.some(name => {
const o = a[name] - b[name];
if (o) {
l = o;
return true;
}
return false;
});
return l;
});
// 保存预处理信息
ttf.support.name = nameRecordTbl;
return size;
}
}
);

View File

@ -1,154 +0,0 @@
/**
* @file post
* @author mengke01(kekee000@gmail.com)
*
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
*/
import table from './table';
import struct from './struct';
import string from '../util/string';
import unicodeName from '../enum/unicodeName';
const Posthead = table.create(
'posthead',
[
['format', struct.Fixed],
['italicAngle', struct.Fixed],
['underlinePosition', struct.Int16],
['underlineThickness', struct.Int16],
['isFixedPitch', struct.Uint32],
['minMemType42', struct.Uint32],
['maxMemType42', struct.Uint32],
['minMemType1', struct.Uint32],
['maxMemType1', struct.Uint32]
]
);
export default table.create(
'post',
[],
{
read(reader, ttf) {
const format = reader.readFixed(this.offset);
// 读取表头
const tbl = new Posthead(this.offset).read(reader, ttf);
// format2
if (format === 2) {
const numberOfGlyphs = reader.readUint16();
const glyphNameIndex = [];
for (let i = 0; i < numberOfGlyphs; ++i) {
glyphNameIndex.push(reader.readUint16());
}
const pascalStringOffset = reader.offset;
const pascalStringLength = ttf.tables.post.length - (pascalStringOffset - this.offset);
const pascalStringBytes = reader.readBytes(reader.offset, pascalStringLength);
tbl.nameIndex = glyphNameIndex; // 设置glyf名字索引
tbl.names = string.getPascalString(pascalStringBytes); // glyf名字数组
}
// deprecated
else if (format === 2.5) {
tbl.format = 3;
}
return tbl;
},
write(writer, ttf) {
const post = ttf.post || {
format: 3
};
// write header
writer.writeFixed(post.format); // format
writer.writeFixed(post.italicAngle || 0); // italicAngle
writer.writeInt16(post.underlinePosition || 0); // underlinePosition
writer.writeInt16(post.underlineThickness || 0); // underlineThickness
writer.writeUint32(post.isFixedPitch || 0); // isFixedPitch
writer.writeUint32(post.minMemType42 || 0); // minMemType42
writer.writeUint32(post.maxMemType42 || 0); // maxMemType42
writer.writeUint32(post.minMemType1 || 0); // minMemType1
writer.writeUint32(post.maxMemType1 || 0); // maxMemType1
// version 3 不设置post信息
if (post.format === 2) {
const numberOfGlyphs = ttf.glyf.length;
writer.writeUint16(numberOfGlyphs); // numberOfGlyphs
// write glyphNameIndex
const nameIndex = ttf.support.post.nameIndex;
for (let i = 0, l = nameIndex.length; i < l; i++) {
writer.writeUint16(nameIndex[i]);
}
// write names
ttf.support.post.names.forEach((name) => {
writer.writeBytes(name);
});
}
},
size(ttf) {
const numberOfGlyphs = ttf.glyf.length;
ttf.post = ttf.post || {};
ttf.post.format = ttf.post.format || 3;
ttf.post.maxMemType1 = numberOfGlyphs;
// version 3 不设置post信息
if (ttf.post.format === 3 || ttf.post.format === 1) {
return 32;
}
// version 2
let size = 34 + numberOfGlyphs * 2; // header + numberOfGlyphs + numberOfGlyphs * 2
const glyphNames = [];
const nameIndexArr = [];
let nameIndex = 0;
// 获取 name的大小
for (let i = 0; i < numberOfGlyphs; i++) {
// .notdef
if (i === 0) {
nameIndexArr.push(0);
}
else {
const glyf = ttf.glyf[i];
const unicode = glyf.unicode ? glyf.unicode[0] : 0;
const unicodeNameIndex = unicodeName[unicode];
if (undefined !== unicodeNameIndex) {
nameIndexArr.push(unicodeNameIndex);
}
else {
// 这里需要注意,"" 有可能是"\3" length不为0但是是空字符串
const name = glyf.name;
if (!name || name.charCodeAt(0) < 32) {
nameIndexArr.push(258 + nameIndex++);
glyphNames.push([0]);
size++;
}
else {
nameIndexArr.push(258 + nameIndex++);
const bytes = string.toPascalStringBytes(name); // pascal string bytes
glyphNames.push(bytes);
size += bytes.length;
}
}
}
}
ttf.support.post = {
nameIndex: nameIndexArr,
names: glyphNames
};
return size;
}
}
);

View File

@ -1,30 +0,0 @@
/**
* @file prep表
* @author mengke01(kekee000@gmail.com)
*
* @reference: http://www.microsoft.com/typography/otspec140/prep.htm
*/
import table from './table';
export default table.create(
'prep',
[],
{
read(reader, ttf) {
const length = ttf.tables.prep.length;
return reader.readBytes(this.offset, length);
},
write(writer, ttf) {
if (ttf.prep) {
writer.writeBytes(ttf.prep, ttf.prep.length);
}
},
size(ttf) {
return ttf.prep ? ttf.prep.length : 0;
}
}
);

View File

@ -1,38 +0,0 @@
/**
* @file ttf基本数据结构
* @author mengke01(kekee000@gmail.com)
*
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html
*/
const struct = {
Int8: 1,
Uint8: 2,
Int16: 3,
Uint16: 4,
Int32: 5,
Uint32: 6,
Fixed: 7, // 32-bit signed fixed-point number (16.16)
FUnit: 8, // Smallest measurable distance in the em space
// 16-bit signed fixed number with the low 14 bits of fraction
F2Dot14: 11,
// The long internal format of a date in seconds since 12:00 midnight,
// January 1, 1904. It is represented as a signed 64-bit integer.
LongDateTime: 12,
// extend data type
Char: 13,
String: 14,
Bytes: 15,
Uint24: 20
};
// 反转名字查找
const names = {};
Object.keys(struct).forEach((key) => {
names[struct[key]] = key;
});
struct.names = names;
export default struct;

View File

@ -1,30 +0,0 @@
/**
* @file otf字体格式支持的表
* @author mengke01(kekee000@gmail.com)
*/
import head from './head';
import maxp from './maxp';
import cmap from './cmap';
import name from './name';
import hhea from './hhea';
import hmtx from './hmtx';
import post from './post';
import OS2 from './OS2';
import CFF from './CFF';
import GPOS from './GPOS';
import kern from './kern';
export default {
head,
maxp,
cmap,
name,
hhea,
hmtx,
post,
'OS/2': OS2,
CFF,
GPOS,
kern
};

View File

@ -1,42 +0,0 @@
/**
* @file ttf读取和写入支持的表
* @author mengke01(kekee000@gmail.com)
*/
import head from './head';
import maxp from './maxp';
import loca from './loca';
import cmap from './cmap';
import glyf from './glyf';
import name from './name';
import hhea from './hhea';
import hmtx from './hmtx';
import post from './post';
import OS2 from './OS2';
import fpgm from './fpgm';
import cvt from './cvt';
import prep from './prep';
import gasp from './gasp';
import GPOS from './GPOS';
import kern from './kern';
import kerx from './kerx';
export default {
head,
maxp,
loca,
cmap,
glyf,
name,
hhea,
hmtx,
post,
'OS/2': OS2,
fpgm,
cvt,
prep,
gasp,
GPOS,
kern,
kerx
};

View File

@ -1,224 +0,0 @@
/**
* @file ttf表基类
* @author mengke01(kekee000@gmail.com)
*/
import struct from './struct';
import error from '../error';
/* eslint-disable no-invalid-this */
/**
* 读取表结构
*
* @param {Reader} reader reader对象
* @return {Object} 当前对象
*/
function read(reader) {
const offset = this.offset;
if (undefined !== offset) {
reader.seek(offset);
}
const me = this;
this.struct.forEach((item) => {
const name = item[0];
const type = item[1];
let typeName = null;
switch (type) {
case struct.Int8:
case struct.Uint8:
case struct.Int16:
case struct.Uint16:
case struct.Int32:
case struct.Uint32:
typeName = struct.names[type];
me[name] = reader.read(typeName);
break;
case struct.Fixed:
me[name] = reader.readFixed();
break;
case struct.LongDateTime:
me[name] = reader.readLongDateTime();
break;
case struct.Bytes:
me[name] = reader.readBytes(reader.offset, item[2] || 0);
break;
case struct.Char:
me[name] = reader.readChar();
break;
case struct.String:
me[name] = reader.readString(reader.offset, item[2] || 0);
break;
default:
error.raise(10003, name, type);
}
});
return this.valueOf();
}
/**
* 写表结构
*
* @param {Object} writer writer对象
* @param {Object} ttf 已解析的ttf对象
*
* @return {Writer} 返回writer对象
*/
function write(writer, ttf) {
const table = ttf[this.name];
if (!table) {
error.raise(10203, this.name);
}
this.struct.forEach((item) => {
const name = item[0];
const type = item[1];
let typeName = null;
switch (type) {
case struct.Int8:
case struct.Uint8:
case struct.Int16:
case struct.Uint16:
case struct.Int32:
case struct.Uint32:
typeName = struct.names[type];
writer.write(typeName, table[name]);
break;
case struct.Fixed:
writer.writeFixed(table[name]);
break;
case struct.LongDateTime:
writer.writeLongDateTime(table[name]);
break;
case struct.Bytes:
writer.writeBytes(table[name], item[2] || 0);
break;
case struct.Char:
writer.writeChar(table[name]);
break;
case struct.String:
writer.writeString(table[name], item[2] || 0);
break;
default:
error.raise(10003, name, type);
}
});
return writer;
}
/**
* 获取ttf表的size大小
*
* @param {string} name 表名
* @return {number} 表大小
*/
function size() {
let sz = 0;
this.struct.forEach((item) => {
const type = item[1];
switch (type) {
case struct.Int8:
case struct.Uint8:
sz += 1;
break;
case struct.Int16:
case struct.Uint16:
sz += 2;
break;
case struct.Int32:
case struct.Uint32:
case struct.Fixed:
sz += 4;
break;
case struct.LongDateTime:
sz += 8;
break;
case struct.Bytes:
sz += item[2] || 0;
break;
case struct.Char:
sz += 1;
break;
case struct.String:
sz += item[2] || 0;
break;
default:
error.raise(10003, name, type);
}
});
return sz;
}
/**
* 获取对象的值
*
* @return {*} 当前对象的值
*/
function valueOf() {
const val = {};
const me = this;
this.struct.forEach(item => {
val[item[0]] = me[item[0]];
});
return val;
}
export default {
read,
write,
size,
valueOf,
/**
* 创建一个表结构
*
* @param {string} name 表名
* @param {Array<[string, number]>} struct 表结构
* @param {Object} proto 原型
* @return {Function} 表构造函数
*/
create(name, struct, proto) {
class Table {
constructor(offset) {
this.name = name;
this.struct = struct;
this.offset = offset;
}
}
Table.prototype.read = read;
Table.prototype.write = write;
Table.prototype.size = size;
Table.prototype.valueOf = valueOf;
Object.assign(Table.prototype, proto);
return Table;
}
};

Some files were not shown because too many files have changed in this diff Show More