pdfkit/lib/font/afm.js
2018-11-29 08:14:45 -08:00

227 lines
7.0 KiB
JavaScript

import fs from 'fs';
const WIN_ANSI_MAP = {
402: 131,
8211: 150,
8212: 151,
8216: 145,
8217: 146,
8218: 130,
8220: 147,
8221: 148,
8222: 132,
8224: 134,
8225: 135,
8226: 149,
8230: 133,
8364: 128,
8240: 137,
8249: 139,
8250: 155,
710: 136,
8482: 153,
338: 140,
339: 156,
732: 152,
352: 138,
353: 154,
376: 159,
381: 142,
382: 158
};
const characters = `\
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
space exclam quotedbl numbersign
dollar percent ampersand quotesingle
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
grave 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 .notdef
Euro .notdef quotesinglbase florin
quotedblbase ellipsis dagger daggerdbl
circumflex perthousand Scaron guilsinglleft
OE .notdef Zcaron .notdef
.notdef quoteleft quoteright quotedblleft
quotedblright bullet endash emdash
tilde trademark scaron guilsinglright
oe .notdef zcaron ydieresis
space exclamdown cent sterling
currency yen brokenbar section
dieresis copyright ordfeminine guillemotleft
logicalnot hyphen registered macron
degree plusminus twosuperior threesuperior
acute mu paragraph periodcentered
cedilla onesuperior ordmasculine guillemotright
onequarter onehalf threequarters questiondown
Agrave Aacute Acircumflex Atilde
Adieresis Aring AE Ccedilla
Egrave Eacute Ecircumflex Edieresis
Igrave Iacute Icircumflex Idieresis
Eth Ntilde Ograve Oacute
Ocircumflex Otilde Odieresis multiply
Oslash Ugrave Uacute Ucircumflex
Udieresis Yacute Thorn germandbls
agrave aacute acircumflex atilde
adieresis aring ae ccedilla
egrave eacute ecircumflex edieresis
igrave iacute icircumflex idieresis
eth ntilde ograve oacute
ocircumflex otilde odieresis divide
oslash ugrave uacute ucircumflex
udieresis yacute thorn ydieresis\
`.split(/\s+/);
class AFMFont {
static open(filename) {
return new AFMFont(fs.readFileSync(filename, 'utf8'));
}
constructor(contents) {
this.contents = contents;
this.attributes = {};
this.glyphWidths = {};
this.boundingBoxes = {};
this.kernPairs = {};
this.parse();
// todo: remove charWidths since appears to not be used
this.charWidths = new Array(256);
for (let char = 0; char <= 255; char++) {
this.charWidths[char] = this.glyphWidths[characters[char]];
}
this.bbox = (this.attributes['FontBBox'].split(/\s+/).map((e) => +e));
this.ascender = +(this.attributes['Ascender'] || 0);
this.descender = +(this.attributes['Descender'] || 0);
this.xHeight = +(this.attributes['XHeight'] || 0);
this.capHeight = +(this.attributes['CapHeight'] || 0);
this.lineGap = (this.bbox[3] - this.bbox[1]) - (this.ascender - this.descender);
}
parse() {
let section = '';
for (let line of this.contents.split('\n')) {
var match;
var a;
if (match = line.match(/^Start(\w+)/)) {
section = match[1];
continue;
} else if (match = line.match(/^End(\w+)/)) {
section = '';
continue;
}
switch (section) {
case 'FontMetrics':
match = line.match(/(^\w+)\s+(.*)/);
var key = match[1];
var value = match[2];
if (a = this.attributes[key]) {
if (!Array.isArray(a)) { a = (this.attributes[key] = [a]); }
a.push(value);
} else {
this.attributes[key] = value;
}
break;
case 'CharMetrics':
if (!/^CH?\s/.test(line)) { continue; }
var name = line.match(/\bN\s+(\.?\w+)\s*;/)[1];
this.glyphWidths[name] = +line.match(/\bWX\s+(\d+)\s*;/)[1];
break;
case 'KernPairs':
match = line.match(/^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/);
if (match) {
this.kernPairs[match[1] + '\0' + match[2]] = parseInt(match[3]);
}
break;
}
}
}
encodeText(text) {
const res = [];
for (let i = 0, end = text.length, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) {
let char = text.charCodeAt(i);
char = WIN_ANSI_MAP[char] || char;
res.push(char.toString(16));
}
return res;
}
glyphsForString(string) {
const glyphs = [];
for (let i = 0, end = string.length, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) {
const charCode = string.charCodeAt(i);
glyphs.push(this.characterToGlyph(charCode));
}
return glyphs;
}
characterToGlyph(character) {
return characters[WIN_ANSI_MAP[character] || character] || '.notdef';
}
widthOfGlyph(glyph) {
return this.glyphWidths[glyph] || 0;
}
getKernPair(left, right) {
return this.kernPairs[left + '\0' + right] || 0;
}
advancesForGlyphs(glyphs) {
const advances = [];
for (let index = 0; index < glyphs.length; index++) {
const left = glyphs[index];
const right = glyphs[index + 1];
advances.push(this.widthOfGlyph(left) + this.getKernPair(left, right));
}
return advances;
}
};
export default AFMFont;