Typr库

Typr 库 greasy fork 镜像

このスクリプトは単体で利用できません。右のようなメタデータを含むスクリプトから、ライブラリとして読み込まれます: // @require https://update.greasyfork.org/scripts/466179/1189955/Typr%E5%BA%93.js

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。

var Typr = {};

Typr["parse"] = function(buff)
{
	var readFont = function(data, idx, offset,tmap) {
		var bin = Typr["B"];
		
		var T = Typr["T"];
		var prsr = {
			"cmap":T.cmap,
			"head":T.head,
			"hhea":T.hhea,
			"maxp":T.maxp,
			"hmtx":T.hmtx,
			"name":T.name,
			"OS/2":T.OS2,
			"post":T.post,
			
			"loca":T.loca,
			"kern":T.kern,
			"glyf":T.glyf,
			
			"CFF ":T.CFF,
			/*
			"GPOS",
			"GSUB",
			"GDEF",*/
			
			"SVG ":T.SVG
			//"VORG",
		};
		var obj = {"_data":data, "_index":idx, "_offset":offset};
		
		for(var t in prsr) {
			var tab = Typr["findTable"](data, t, offset);
			if(tab) {
				var off=tab[0], tobj = tmap[off];
				if(tobj==null) tobj = prsr[t].parseTab(data, off, tab[1], obj);
				obj[t] = tmap[off] = tobj;
			}
		}
		return obj;
	}
	
	
	var bin = Typr["B"];
	var data = new Uint8Array(buff);
	
	var tmap = {};
	var tag = bin.readASCII(data, 0, 4);  
	if(tag=="ttcf") {
		var offset = 4;
		var majV = bin.readUshort(data, offset);  offset+=2;
		var minV = bin.readUshort(data, offset);  offset+=2;
		var numF = bin.readUint  (data, offset);  offset+=4;
		var fnts = [];
		for(var i=0; i<numF; i++) {
			var foff = bin.readUint  (data, offset);  offset+=4;
			fnts.push(readFont(data, i, foff,tmap));
		}
		return fnts;
	}
	else return [readFont(data, 0, 0,tmap)];
}


Typr["findTable"] = function(data, tab, foff)
{
	var bin = Typr["B"];
	var numTables = bin.readUshort(data, foff+4);
	var offset = foff+12;
	for(var i=0; i<numTables; i++)
	{
		var tag      = bin.readASCII(data, offset, 4); 
		var checkSum = bin.readUint (data, offset+ 4);
		var toffset  = bin.readUint (data, offset+ 8); 
		var length   = bin.readUint (data, offset+12);
		if(tag==tab) return [toffset,length];
		offset+=16;
	}
	return null;
}
/*
Typr["splitBy"] = function(data,tag) {
	data = new Uint8Array(data);  console.log(data.slice(0,64));
	var bin = Typr["B"];
	var ttcf = bin.readASCII(data, 0, 4);  if(ttcf!="ttcf") return {};
	
	var offset = 8;
	var numF = bin.readUint  (data, offset);  offset+=4;
	var colls = [], used={};
	for(var i=0; i<numF; i++) {
		var foff = bin.readUint  (data, offset);  offset+=4;
		var toff = Typr["findTable"](data,tag,foff)[0];
		if(used[toff]==null) used[toff] = [];
		used[toff].push([foff,bin.readUshort(data,foff+4)]);  // font offset, numTables
	}
	for(var toff in used) {
		var offs = used[toff];
		var hlen = 12+4*offs.length;
		var out = new Uint8Array(hlen);		
		for(var i=0; i<8; i++) out[i]=data[i];
		bin.writeUint(out,8,offs.length);
		
		for(var i=0; i<offs.length; i++) hlen += 12+offs[i][1]*16;
		
		var hdrs = [out], tabs = [], hoff=out.length, toff=hlen, noffs={};
		for(var i=0; i<offs.length; i++) {
			bin.writeUint(out, 12+i*4, hoff);  hoff+=12+offs[i][1]*16;
			toff = Typr["_cutFont"](data, offs[i][0], hdrs, tabs, toff, noffs);
		}
		colls.push(Typr["_joinArrs"](hdrs.concat(tabs)));
	}
	return colls;
}

Typr["splitFonts"] = function(data) {
	data = new Uint8Array(data);
	var bin = Typr["B"];
	var ttcf = bin.readASCII(data, 0, 4);  if(ttcf!="ttcf") return {};
	
	var offset = 8;
	var numF = bin.readUint  (data, offset);  offset+=4;
	var fnts = [];
	for(var i=0; i<numF; i++) {
		var foff = bin.readUint  (data, offset);  offset+=4;
		fnts.push(Typr._cutFont(data, foff));
		break;
	}
	return fnts;
}

Typr["_cutFont"] = function(data,foff,hdrs,tabs,toff, noffs) {
	var bin = Typr["B"];
	var numTables = bin.readUshort(data, foff+4);
	
	var out = new Uint8Array(12+numTables*16);  hdrs.push(out);
	for(var i=0; i<12; i++) out[i]=data[foff+i];  //console.log(out);
	
	var off = 12;
	for(var i=0; i<numTables; i++)
	{
		var tag      = bin.readASCII(data, foff+off, 4); 
		var checkSum = bin.readUint (data, foff+off+ 4);
		var toffset  = bin.readUint (data, foff+off+ 8); 
		var length   = bin.readUint (data, foff+off+12);
		
		while((length&3)!=0) length++;
		
		for(var j=0; j<16; j++) out[off+j]=data[foff+off+j];
		
		if(noffs[toffset]!=null) bin.writeUint(out,off+8,noffs[toffset]);
		else {
			noffs[toffset] = toff;
			bin.writeUint(out, off+8, toff);  
			tabs.push(new Uint8Array(data.buffer, toffset, length));  toff+=length;
		}
		off+=16;
	}
	return toff;
}
Typr["_joinArrs"] = function(tabs) {
	var len = 0;
	for(var i=0; i<tabs.length; i++) len+=tabs[i].length;
	var out = new Uint8Array(len), ooff=0;
	for(var i=0; i<tabs.length; i++) {
		var tab = tabs[i];
		for(var j=0; j<tab.length; j++) out[ooff+j]=tab[j];
		ooff+=tab.length;
	}
	return out;
}
*/

Typr["T"]={};





Typr["B"] = {
	readFixed : function(data, o)
	{
		return ((data[o]<<8) | data[o+1]) +  (((data[o+2]<<8)|data[o+3])/(256*256+4));
	},
	readF2dot14 : function(data, o)
	{
		var num = Typr["B"].readShort(data, o);
		return num / 16384;
	},
	readInt : function(buff, p)
	{
		//if(p>=buff.length) throw "error";
		var a = Typr["B"].t.uint8;
		a[0] = buff[p+3];
		a[1] = buff[p+2];
		a[2] = buff[p+1];
		a[3] = buff[p];
		return Typr["B"].t.int32[0];
	},
	
	readInt8 : function(buff, p)
	{
		//if(p>=buff.length) throw "error";
		var a = Typr["B"].t.uint8;
		a[0] = buff[p];
		return Typr["B"].t.int8[0];
	},
	readShort : function(buff, p)
	{
		//if(p>=buff.length) throw "error";
		var a = Typr["B"].t.uint8;
		a[1] = buff[p]; a[0] = buff[p+1];
		return Typr["B"].t.int16[0];
	},
	readUshort : function(buff, p)
	{
		//if(p>=buff.length) throw "error";
		return (buff[p]<<8) | buff[p+1];
	},
	writeUshort : function(buff, p, n)
	{
		buff[p] = (n>>8)&255;  buff[p+1] = n&255;
	},
	readUshorts : function(buff, p, len)
	{
		var arr = [];
		for(var i=0; i<len; i++) {
			var v = Typr["B"].readUshort(buff, p+i*2);  //if(v==932) console.log(p+i*2);
			arr.push(v);
		}
		return arr;
	},
	readUint : function(buff, p)
	{
		//if(p>=buff.length) throw "error";
		var a = Typr["B"].t.uint8;
		a[3] = buff[p];  a[2] = buff[p+1];  a[1] = buff[p+2];  a[0] = buff[p+3];
		return Typr["B"].t.uint32[0];
	},
	writeUint: function(buff, p, n)
	{
		buff[p] = (n>>24)&255;  buff[p+1] = (n>>16)&255;  buff[p+2] = (n>>8)&255;  buff[p+3] = (n>>0)&255;
	},
	readUint64 : function(buff, p)
	{
		//if(p>=buff.length) throw "error";
		return (Typr["B"].readUint(buff, p)*(0xffffffff+1)) + Typr["B"].readUint(buff, p+4);
	},
	readASCII : function(buff, p, l)	// l : length in Characters (not Bytes)
	{
		//if(p>=buff.length) throw "error";
		var s = "";
		for(var i = 0; i < l; i++) s += String.fromCharCode(buff[p+i]);
		return s;
	},
	writeASCII : function(buff, p, s)	// l : length in Characters (not Bytes)
	{
		for(var i = 0; i < s.length; i++)	
			buff[p+i] = s.charCodeAt(i);
	},
	readUnicode : function(buff, p, l)
	{
		//if(p>=buff.length) throw "error";
		var s = "";
		for(var i = 0; i < l; i++)	
		{
			var c = (buff[p++]<<8) | buff[p++];
			s += String.fromCharCode(c);
		}
		return s;
	},
	_tdec : window["TextDecoder"] ? new window["TextDecoder"]() : null,
	readUTF8 : function(buff, p, l) {
		var tdec = Typr["B"]._tdec;
		if(tdec && p==0 && l==buff.length) return tdec["decode"](buff);
		return Typr["B"].readASCII(buff,p,l);
	},
	readBytes : function(buff, p, l)
	{
		//if(p>=buff.length) throw "error";
		var arr = [];
		for(var i=0; i<l; i++) arr.push(buff[p+i]);
		return arr;
	},
	readASCIIArray : function(buff, p, l)	// l : length in Characters (not Bytes)
	{
		//if(p>=buff.length) throw "error";
		var s = [];
		for(var i = 0; i < l; i++)	
			s.push(String.fromCharCode(buff[p+i]));
		return s;
	}, 
	t : function() {
		var ab = new ArrayBuffer(8);
		return {
			buff   : ab,
			int8   : new Int8Array  (ab),
			uint8  : new Uint8Array (ab),
			int16  : new Int16Array (ab),
			uint16 : new Uint16Array(ab),
			int32  : new Int32Array (ab),
			uint32 : new Uint32Array(ab)
		}  
	}()
};






	Typr["T"].CFF = {
		parseTab : function(data, offset, length)
		{
			var bin = Typr["B"];
			var CFF = Typr["T"].CFF;
			
			data = new Uint8Array(data.buffer, offset, length);
			offset = 0;
			
			// Header
			var major = data[offset];  offset++;
			var minor = data[offset];  offset++;
			var hdrSize = data[offset];  offset++;
			var offsize = data[offset];  offset++;
			//console.log(major, minor, hdrSize, offsize);
			
			// Name INDEX
			var ninds = [];
			offset = CFF.readIndex(data, offset, ninds);
			var names = [];
			
			for(var i=0; i<ninds.length-1; i++) names.push(bin.readASCII(data, offset+ninds[i], ninds[i+1]-ninds[i]));
			offset += ninds[ninds.length-1];
			
			
			// Top DICT INDEX
			var tdinds = [];
			offset = CFF.readIndex(data, offset, tdinds);  //console.log(tdinds);
			// Top DICT Data
			var topDicts = [];
			for(var i=0; i<tdinds.length-1; i++) topDicts.push( CFF.readDict(data, offset+tdinds[i], offset+tdinds[i+1]) );
			offset += tdinds[tdinds.length-1];
			var topdict = topDicts[0];
			//console.log(topdict);
			
			// String INDEX
			var sinds = [];
			offset = CFF.readIndex(data, offset, sinds);
			// String Data
			var strings = [];
			for(var i=0; i<sinds.length-1; i++) strings.push(bin.readASCII(data, offset+sinds[i], sinds[i+1]-sinds[i]));
			offset += sinds[sinds.length-1];
			
			// Global Subr INDEX  (subroutines)		
			CFF.readSubrs(data, offset, topdict);
			
			// charstrings
			
			if(topdict["CharStrings"]) topdict["CharStrings"] = CFF.readBytes(data, topdict["CharStrings"]);
			
			// CID font
			if(topdict["ROS"]) {
				offset = topdict["FDArray"];
				var fdind = [];
				offset = CFF.readIndex(data, offset, fdind);
				
				topdict["FDArray"] = [];
				for(var i=0; i<fdind.length-1; i++) {
					var dict = CFF.readDict(data, offset+fdind[i], offset+fdind[i+1]);
					CFF._readFDict(data, dict, strings);
					topdict["FDArray"].push( dict );
				}
				offset += fdind[fdind.length-1];
				
				offset = topdict["FDSelect"];
				topdict["FDSelect"] = [];
				var fmt = data[offset];  offset++;
				if(fmt==3) {
					var rns = bin.readUshort(data, offset);  offset+=2;
					for(var i=0; i<rns+1; i++) {
						topdict["FDSelect"].push(bin.readUshort(data, offset), data[offset+2]);  offset+=3;
					}
				}
				else throw fmt;
			}
			
			// Encoding
			//if(topdict["Encoding"]) topdict["Encoding"] = CFF.readEncoding(data, topdict["Encoding"], topdict["CharStrings"].length);
			
			// charset
			if(topdict["charset"] ) topdict["charset"]  = CFF.readCharset (data, topdict["charset"] , topdict["CharStrings"].length);
			
			CFF._readFDict(data, topdict, strings);
			return topdict;
		},
		
		_readFDict : function(data, dict, ss) {
			var CFF = Typr["T"].CFF;
			var offset;
			if(dict["Private"]) {
				offset = dict["Private"][1];
				dict["Private"] = CFF.readDict(data, offset, offset+dict["Private"][0]);
				if(dict["Private"]["Subrs"]) CFF.readSubrs(data, offset+dict["Private"]["Subrs"], dict["Private"]);
			}
			for(var p in dict) if(["FamilyName","FontName","FullName","Notice","version","Copyright"].indexOf(p)!=-1)  dict[p]=ss[dict[p] -426 + 35];
		},
		
		readSubrs : function(data, offset, obj)
		{
			obj["Subrs"] = Typr["T"].CFF.readBytes(data, offset);
			
			var bias, nSubrs = obj["Subrs"].length+1;
			if (false) bias = 0;
			else if (nSubrs <  1240) bias = 107;
			else if (nSubrs < 33900) bias = 1131;
			else bias = 32768;
			obj["Bias"] = bias;
		},
		readBytes : function(data, offset) {
			var bin = Typr["B"];
			var arr = [];
			offset = Typr["T"].CFF.readIndex(data, offset, arr);
			
			var subrs = [], arl = arr.length-1, no = data.byteOffset+offset;
			for(var i=0; i<arl; i++) {
				var ari = arr[i];
				subrs.push(new Uint8Array(data.buffer, no+ari, arr[i+1]-ari));
			}
			return subrs;
		},
		
		tableSE : [
		  0,   0,   0,   0,   0,   0,   0,   0,
		  0,   0,   0,   0,   0,   0,   0,   0,
		  0,   0,   0,   0,   0,   0,   0,   0,
		  0,   0,   0,   0,   0,   0,   0,   0,
		  1,   2,   3,   4,   5,   6,   7,   8,
		  9,  10,  11,  12,  13,  14,  15,  16,
		 17,  18,  19,  20,  21,  22,  23,  24,
		 25,  26,  27,  28,  29,  30,  31,  32,
		 33,  34,  35,  36,  37,  38,  39,  40,
		 41,  42,  43,  44,  45,  46,  47,  48,
		 49,  50,  51,  52,  53,  54,  55,  56,
		 57,  58,  59,  60,  61,  62,  63,  64,
		 65,  66,  67,  68,  69,  70,  71,  72,
		 73,  74,  75,  76,  77,  78,  79,  80,
		 81,  82,  83,  84,  85,  86,  87,  88,
		 89,  90,  91,  92,  93,  94,  95,   0,
		  0,   0,   0,   0,   0,   0,   0,   0,
		  0,   0,   0,   0,   0,   0,   0,   0,
		  0,   0,   0,   0,   0,   0,   0,   0,
		  0,   0,   0,   0,   0,   0,   0,   0,
		  0,  96,  97,  98,  99, 100, 101, 102,
		103, 104, 105, 106, 107, 108, 109, 110,
		  0, 111, 112, 113, 114,   0, 115, 116,
		117, 118, 119, 120, 121, 122,   0, 123,
		  0, 124, 125, 126, 127, 128, 129, 130,
		131,   0, 132, 133,   0, 134, 135, 136,
		137,   0,   0,   0,   0,   0,   0,   0,
		  0,   0,   0,   0,   0,   0,   0,   0,
		  0, 138,   0, 139,   0,   0,   0,   0,
		140, 141, 142, 143,   0,   0,   0,   0,
		  0, 144,   0,   0,   0, 145,   0,   0,
		146, 147, 148, 149,   0,   0,   0,   0
		],
	  
		glyphByUnicode : function(cff, code)
		{
			for(var i=0; i<cff["charset"].length; i++) if(cff["charset"][i]==code) return i;
			return -1;
		},
		
		glyphBySE : function(cff, charcode)	// glyph by standard encoding
		{
			if ( charcode < 0 || charcode > 255 ) return -1;
			return Typr["T"].CFF.glyphByUnicode(cff, Typr["T"].CFF.tableSE[charcode]);		
		},
		
		/*readEncoding : function(data, offset, num)
		{
			var bin = Typr["B"];
			
			var array = ['.notdef'];
			var format = data[offset];  offset++;
			//console.log("Encoding");
			//console.log(format);
			
			if(format==0)
			{
				var nCodes = data[offset];  offset++;
				for(var i=0; i<nCodes; i++)  array.push(data[offset+i]);
			}
			/*
			else if(format==1 || format==2)
			{
				while(charset.length<num)
				{
					var first = bin.readUshort(data, offset);  offset+=2;
					var nLeft=0;
					if(format==1) {  nLeft = data[offset];  offset++;  }
					else          {  nLeft = bin.readUshort(data, offset);  offset+=2;  }
					for(var i=0; i<=nLeft; i++)  {  charset.push(first);  first++;  }
				}
			}
			
			else throw "error: unknown encoding format: " + format;
			
			return array;
		},*/

		readCharset : function(data, offset, num)
		{
			var bin = Typr["B"];
			
			var charset = ['.notdef'];
			var format = data[offset];  offset++;
			
			if(format==0)
			{
				for(var i=0; i<num; i++) 
				{
					var first = bin.readUshort(data, offset);  offset+=2;
					charset.push(first);
				}
			}
			else if(format==1 || format==2)
			{
				while(charset.length<num)
				{
					var first = bin.readUshort(data, offset);  offset+=2;
					var nLeft=0;
					if(format==1) {  nLeft = data[offset];  offset++;  }
					else          {  nLeft = bin.readUshort(data, offset);  offset+=2;  }
					for(var i=0; i<=nLeft; i++)  {  charset.push(first);  first++;  }
				}
			}
			else throw "error: format: " + format;
			
			return charset;
		},

		readIndex : function(data, offset, inds)
		{
			var bin = Typr["B"];
			
			var count = bin.readUshort(data, offset)+1;  offset+=2;
			var offsize = data[offset];  offset++;
			
			if     (offsize==1) for(var i=0; i<count; i++) inds.push( data[offset+i] );
			else if(offsize==2) for(var i=0; i<count; i++) inds.push( bin.readUshort(data, offset+i*2) );
			else if(offsize==3) for(var i=0; i<count; i++) inds.push( bin.readUint  (data, offset+i*3 - 1) & 0x00ffffff );
			else if(offsize==4) for(var i=0; i<count; i++) inds.push( bin.readUint  (data, offset+i*4) );
			else if(count!=1) throw "unsupported offset size: " + offsize + ", count: " + count;
			
			offset += count*offsize;
			return offset-1;
		},
		
		getCharString : function(data, offset, o)
		{
			var bin = Typr["B"];
			
			var b0 = data[offset], b1 = data[offset+1], b2 = data[offset+2], b3 = data[offset+3], b4=data[offset+4];
			var vs = 1;
			var op=null, val=null;
			// operand
			if(b0<=20) { op = b0;  vs=1;  }
			if(b0==12) { op = b0*100+b1;  vs=2;  }
			//if(b0==19 || b0==20) { op = b0/*+" "+b1*/;  vs=2; }
			if(21 <=b0 && b0<= 27) { op = b0;  vs=1; }
			if(b0==28) { val = bin.readShort(data,offset+1);  vs=3; }
			if(29 <=b0 && b0<= 31) { op = b0;  vs=1; }
			if(32 <=b0 && b0<=246) { val = b0-139;  vs=1; }
			if(247<=b0 && b0<=250) { val = (b0-247)*256+b1+108;  vs=2; }
			if(251<=b0 && b0<=254) { val =-(b0-251)*256-b1-108;  vs=2; }
			if(b0==255) {  val = bin.readInt(data, offset+1)/0xffff;  vs=5;   }
			
			o.val = val!=null ? val : "o"+op;
			o.size = vs;
		},
		
		readCharString : function(data, offset, length)
		{
			var end = offset + length;
			var bin = Typr["B"];
			var arr = [];
			
			while(offset<end)
			{
				var b0 = data[offset], b1 = data[offset+1], b2 = data[offset+2], b3 = data[offset+3], b4=data[offset+4];
				var vs = 1;
				var op=null, val=null;
				// operand
				if(b0<=20) { op = b0;  vs=1;  }
				if(b0==12) { op = b0*100+b1;  vs=2;  }
				if(b0==19 || b0==20) { op = b0/*+" "+b1*/;  vs=2; }
				if(21 <=b0 && b0<= 27) { op = b0;  vs=1; }
				if(b0==28) { val = bin.readShort(data,offset+1);  vs=3; }
				if(29 <=b0 && b0<= 31) { op = b0;  vs=1; }
				if(32 <=b0 && b0<=246) { val = b0-139;  vs=1; }
				if(247<=b0 && b0<=250) { val = (b0-247)*256+b1+108;  vs=2; }
				if(251<=b0 && b0<=254) { val =-(b0-251)*256-b1-108;  vs=2; }
				if(b0==255) {  val = bin.readInt(data, offset+1)/0xffff;  vs=5;   }
				
				arr.push(val!=null ? val : "o"+op);
				offset += vs;	

				//var cv = arr[arr.length-1];
				//if(cv==undefined) throw "error";
				//console.log()
			}	
			return arr;
		},

		readDict : function(data, offset, end)
		{
			var bin = Typr["B"];
			//var dict = [];
			var dict = {};
			var carr = [];
			
			while(offset<end)
			{
				var b0 = data[offset], b1 = data[offset+1], b2 = data[offset+2], b3 = data[offset+3], b4=data[offset+4];
				var vs = 1;
				var key=null, val=null;
				// operand
				if(b0==28) { val = bin.readShort(data,offset+1);  vs=3; }
				if(b0==29) { val = bin.readInt  (data,offset+1);  vs=5; }
				if(32 <=b0 && b0<=246) { val = b0-139;  vs=1; }
				if(247<=b0 && b0<=250) { val = (b0-247)*256+b1+108;  vs=2; }
				if(251<=b0 && b0<=254) { val =-(b0-251)*256-b1-108;  vs=2; }
				if(b0==255) {  val = bin.readInt(data, offset+1)/0xffff;  vs=5;  throw "unknown number";  }
				
				if(b0==30) 
				{  
					var nibs = [];
					vs = 1;
					while(true)
					{
						var b = data[offset+vs];  vs++;
						var nib0 = b>>4, nib1 = b&0xf;
						if(nib0 != 0xf) nibs.push(nib0);  if(nib1!=0xf) nibs.push(nib1);
						if(nib1==0xf) break;
					}
					var s = "";
					var chars = [0,1,2,3,4,5,6,7,8,9,".","e","e-","reserved","-","endOfNumber"];
					for(var i=0; i<nibs.length; i++) s += chars[nibs[i]];
					//console.log(nibs);
					val = parseFloat(s);
				}
				
				if(b0<=21)	// operator
				{
					var keys = ["version", "Notice", "FullName", "FamilyName", "Weight", "FontBBox", "BlueValues", "OtherBlues", "FamilyBlues","FamilyOtherBlues",
						"StdHW", "StdVW", "escape", "UniqueID", "XUID", "charset", "Encoding", "CharStrings", "Private", "Subrs", 
						"defaultWidthX", "nominalWidthX"];
						
					key = keys[b0];  vs=1;
					if(b0==12) { 
						var keys = [ "Copyright", "isFixedPitch", "ItalicAngle", "UnderlinePosition", "UnderlineThickness", "PaintType", "CharstringType", "FontMatrix", "StrokeWidth", "BlueScale",
						"BlueShift", "BlueFuzz", "StemSnapH", "StemSnapV", "ForceBold", "","", "LanguageGroup", "ExpansionFactor", "initialRandomSeed",
						"SyntheticBase", "PostScript", "BaseFontName", "BaseFontBlend", "","","",  "","","",
						"ROS", "CIDFontVersion", "CIDFontRevision", "CIDFontType", "CIDCount", "UIDBase", "FDArray", "FDSelect", "FontName"];
						key = keys[b1];  vs=2; 
					}
				}
				
				if(key!=null) {  dict[key] = carr.length==1 ? carr[0] : carr;  carr=[]; }
				else  carr.push(val);  
				
				offset += vs;		
			}	
			return dict;
		}
	};


Typr["T"].cmap = {
	parseTab : function(data, offset, length)
	{
		var obj = {tables:[],ids:{},off:offset};
		data = new Uint8Array(data.buffer, offset, length);
		offset = 0;

		var offset0 = offset;
		var bin = Typr["B"], rU = bin.readUshort, cmap = Typr["T"].cmap;
		var version   = rU(data, offset);  offset += 2;
		var numTables = rU(data, offset);  offset += 2;
		
		//console.log(version, numTables);
		
		var offs = [];
		
		
		for(var i=0; i<numTables; i++)
		{
			var platformID = rU(data, offset);  offset += 2;
			var encodingID = rU(data, offset);  offset += 2;
			var noffset = bin.readUint(data, offset);       offset += 4;
			
			var id = "p"+platformID+"e"+encodingID;
			
			//console.log("cmap subtable", platformID, encodingID, noffset);
			
			
			var tind = offs.indexOf(noffset);
			
			if(tind==-1)
			{
				tind = obj.tables.length;
				var subt = {};
				offs.push(noffset);
				//var time = Date.now();
				var format = subt.format = rU(data, noffset);
				if     (format== 0) subt = cmap.parse0(data, noffset, subt);
				//else if(format== 2) subt.off = noffset;
				else if(format== 4) subt = cmap.parse4(data, noffset, subt);
				else if(format== 6) subt = cmap.parse6(data, noffset, subt);
				else if(format==12) subt = cmap.parse12(data,noffset, subt);
				//console.log(format, Date.now()-time);
				//else console.log("unknown format: "+format, platformID, encodingID, noffset);
				obj.tables.push(subt);
			}
			
			if(obj.ids[id]!=null) throw "multiple tables for one platform+encoding";
			obj.ids[id] = tind;
		}
		return obj;
	},

	parse0 : function(data, offset, obj)
	{
		var bin = Typr["B"];
		offset += 2;
		var len    = bin.readUshort(data, offset);  offset += 2;
		var lang   = bin.readUshort(data, offset);  offset += 2;
		obj.map = [];
		for(var i=0; i<len-6; i++) obj.map.push(data[offset+i]);
		return obj;
	},

	parse4 : function(data, offset, obj)
	{
		var bin = Typr["B"], rU = bin.readUshort, rUs = bin.readUshorts;
		var offset0 = offset;
		offset+=2;
		var length   = rU(data, offset);  offset+=2;
		var language = rU(data, offset);  offset+=2;
		var segCountX2 = rU(data, offset);  offset+=2;
		var segCount = segCountX2>>>1;
		obj.searchRange = rU(data, offset);  offset+=2;
		obj.entrySelector = rU(data, offset);  offset+=2;
		obj.rangeShift = rU(data, offset);  offset+=2;
		obj.endCount   = rUs(data, offset, segCount);  offset += segCount*2;
		offset+=2;
		obj.startCount = rUs(data, offset, segCount);  offset += segCount*2;
		obj.idDelta = [];
		for(var i=0; i<segCount; i++) {obj.idDelta.push(bin.readShort(data, offset));  offset+=2;}
		obj.idRangeOffset = rUs(data, offset, segCount);  offset += segCount*2;
		obj.glyphIdArray  = rUs(data, offset, ((offset0+length)-offset)>>>1);  //offset += segCount*2;
		return obj;
	},

	parse6 : function(data, offset, obj)
	{
		var bin = Typr["B"];
		var offset0 = offset;
		offset+=2;
		var length = bin.readUshort(data, offset);  offset+=2;
		var language = bin.readUshort(data, offset);  offset+=2;
		obj.firstCode = bin.readUshort(data, offset);  offset+=2;
		var entryCount = bin.readUshort(data, offset);  offset+=2;
		obj.glyphIdArray = [];
		for(var i=0; i<entryCount; i++) {obj.glyphIdArray.push(bin.readUshort(data, offset));  offset+=2;}
		
		return obj;
	},

	parse12 : function(data, offset, obj)
	{
		var bin = Typr["B"], rU = bin.readUint;
		var offset0 = offset;
		offset+=4;
		var length = rU(data, offset);  offset+=4;
		var lang   = rU(data, offset);  offset+=4;
		var nGroups= rU(data, offset)*3;  offset+=4;
		
		var gps = obj.groups = new Uint32Array(nGroups);//new Uint32Array(data.slice(offset, offset+nGroups*12).buffer);  
		
		for(var i=0; i<nGroups; i+=3) {
			gps[i  ] = rU(data, offset+(i<<2)  );
			gps[i+1] = rU(data, offset+(i<<2)+4);
			gps[i+2] = rU(data, offset+(i<<2)+8);
		}
		return obj;
	}
};

Typr["T"].glyf = {
	parseTab : function(data, offset, length, font)
	{
		var obj = [], ng=font["maxp"]["numGlyphs"];
		for(var g=0; g<ng; g++) obj.push(null);
		return obj;
	},

	_parseGlyf : function(font, g)
	{
		var bin = Typr["B"];
		var data = font["_data"], loca=font["loca"];
		
		if(loca[g]==loca[g+1]) return null;
		
		var offset = Typr["findTable"](data, "glyf", font["_offset"])[0] + loca[g];
		
		var gl = {};
			
		gl.noc  = bin.readShort(data, offset);  offset+=2;		// number of contours
		gl.xMin = bin.readShort(data, offset);  offset+=2;
		gl.yMin = bin.readShort(data, offset);  offset+=2;
		gl.xMax = bin.readShort(data, offset);  offset+=2;
		gl.yMax = bin.readShort(data, offset);  offset+=2;
		
		if(gl.xMin>=gl.xMax || gl.yMin>=gl.yMax) return null;
			
		if(gl.noc>0)
		{
			gl.endPts = [];
			for(var i=0; i<gl.noc; i++) { gl.endPts.push(bin.readUshort(data,offset)); offset+=2; }
			
			var instructionLength = bin.readUshort(data,offset); offset+=2;
			if((data.length-offset)<instructionLength) return null;
			gl.instructions = bin.readBytes(data, offset, instructionLength);   offset+=instructionLength;
			
			var crdnum = gl.endPts[gl.noc-1]+1;
			gl.flags = [];
			for(var i=0; i<crdnum; i++ ) 
			{ 
				var flag = data[offset];  offset++; 
				gl.flags.push(flag); 
				if((flag&8)!=0)
				{
					var rep = data[offset];  offset++;
					for(var j=0; j<rep; j++) { gl.flags.push(flag); i++; }
				}
			}
			gl.xs = [];
			for(var i=0; i<crdnum; i++) {
				var i8=((gl.flags[i]&2)!=0), same=((gl.flags[i]&16)!=0);  
				if(i8) { gl.xs.push(same ? data[offset] : -data[offset]);  offset++; }
				else
				{
					if(same) gl.xs.push(0);
					else { gl.xs.push(bin.readShort(data, offset));  offset+=2; }
				}
			}
			gl.ys = [];
			for(var i=0; i<crdnum; i++) {
				var i8=((gl.flags[i]&4)!=0), same=((gl.flags[i]&32)!=0);  
				if(i8) { gl.ys.push(same ? data[offset] : -data[offset]);  offset++; }
				else
				{
					if(same) gl.ys.push(0);
					else { gl.ys.push(bin.readShort(data, offset));  offset+=2; }
				}
			}
			var x = 0, y = 0;
			for(var i=0; i<crdnum; i++) { x += gl.xs[i]; y += gl.ys[i];  gl.xs[i]=x;  gl.ys[i]=y; }
			//console.log(endPtsOfContours, instructionLength, instructions, flags, xCoordinates, yCoordinates);
		}
		else
		{
			var ARG_1_AND_2_ARE_WORDS	= 1<<0;
			var ARGS_ARE_XY_VALUES		= 1<<1;
			var ROUND_XY_TO_GRID		= 1<<2;
			var WE_HAVE_A_SCALE			= 1<<3;
			var RESERVED				= 1<<4;
			var MORE_COMPONENTS			= 1<<5;
			var WE_HAVE_AN_X_AND_Y_SCALE= 1<<6;
			var WE_HAVE_A_TWO_BY_TWO	= 1<<7;
			var WE_HAVE_INSTRUCTIONS	= 1<<8;
			var USE_MY_METRICS			= 1<<9;
			var OVERLAP_COMPOUND		= 1<<10;
			var SCALED_COMPONENT_OFFSET	= 1<<11;
			var UNSCALED_COMPONENT_OFFSET	= 1<<12;
			
			gl.parts = [];
			var flags;
			do {
				flags = bin.readUshort(data, offset);  offset += 2;
				var part = { m:{a:1,b:0,c:0,d:1,tx:0,ty:0}, p1:-1, p2:-1 };  gl.parts.push(part);
				part.glyphIndex = bin.readUshort(data, offset);  offset += 2;
				if ( flags & ARG_1_AND_2_ARE_WORDS) {
					var arg1 = bin.readShort(data, offset);  offset += 2;
					var arg2 = bin.readShort(data, offset);  offset += 2;
				} else {
					var arg1 = bin.readInt8(data, offset);  offset ++;
					var arg2 = bin.readInt8(data, offset);  offset ++;
				}
				
				if(flags & ARGS_ARE_XY_VALUES) { part.m.tx = arg1;  part.m.ty = arg2; }
				else  {  part.p1=arg1;  part.p2=arg2;  }
				//part.m.tx = arg1;  part.m.ty = arg2;
				//else { throw "params are not XY values"; }
				
				if ( flags & WE_HAVE_A_SCALE ) {
					part.m.a = part.m.d = bin.readF2dot14(data, offset);  offset += 2;    
				} else if ( flags & WE_HAVE_AN_X_AND_Y_SCALE ) {
					part.m.a = bin.readF2dot14(data, offset);  offset += 2; 
					part.m.d = bin.readF2dot14(data, offset);  offset += 2; 
				} else if ( flags & WE_HAVE_A_TWO_BY_TWO ) {
					part.m.a = bin.readF2dot14(data, offset);  offset += 2; 
					part.m.b = bin.readF2dot14(data, offset);  offset += 2; 
					part.m.c = bin.readF2dot14(data, offset);  offset += 2; 
					part.m.d = bin.readF2dot14(data, offset);  offset += 2; 
				}
			} while ( flags & MORE_COMPONENTS ) 
			if (flags & WE_HAVE_INSTRUCTIONS){
				var numInstr = bin.readUshort(data, offset);  offset += 2;
				gl.instr = [];
				for(var i=0; i<numInstr; i++) { gl.instr.push(data[offset]);  offset++; }
			}
		}
		return gl;
	}
};

Typr["T"].head = {
	parseTab : function(data, offset, length)
	{
		var bin = Typr["B"];
		var obj = {};
		var tableVersion = bin.readFixed(data, offset);  offset += 4;
		
		obj["fontRevision"] = bin.readFixed(data, offset);  offset += 4;
		var checkSumAdjustment = bin.readUint(data, offset);  offset += 4;
		var magicNumber = bin.readUint(data, offset);  offset += 4;
		obj["flags"] = bin.readUshort(data, offset);  offset += 2;
		obj["unitsPerEm"] = bin.readUshort(data, offset);  offset += 2;
		obj["created"]  = bin.readUint64(data, offset);  offset += 8;
		obj["modified"] = bin.readUint64(data, offset);  offset += 8;
		obj["xMin"] = bin.readShort(data, offset);  offset += 2;
		obj["yMin"] = bin.readShort(data, offset);  offset += 2;
		obj["xMax"] = bin.readShort(data, offset);  offset += 2;
		obj["yMax"] = bin.readShort(data, offset);  offset += 2;
		obj["macStyle"] = bin.readUshort(data, offset);  offset += 2;
		obj["lowestRecPPEM"] = bin.readUshort(data, offset);  offset += 2;
		obj["fontDirectionHint"] = bin.readShort(data, offset);  offset += 2;
		obj["indexToLocFormat"]  = bin.readShort(data, offset);  offset += 2;
		obj["glyphDataFormat"]   = bin.readShort(data, offset);  offset += 2;
		return obj;
	}
};

Typr["T"].hhea = {
	parseTab : function(data, offset, length)
	{
		var bin = Typr["B"];
		var obj = {};
		var tableVersion = bin.readFixed(data, offset);  offset += 4;
		
		var keys = ["ascender","descender","lineGap",
			"advanceWidthMax","minLeftSideBearing","minRightSideBearing","xMaxExtent",
			"caretSlopeRise","caretSlopeRun","caretOffset",
			"res0","res1","res2","res3",
			"metricDataFormat","numberOfHMetrics" ];
			
		for(var i=0; i<	keys.length; i++) {
			var key = keys[i];
			var func = (key=="advanceWidthMax" || key=="numberOfHMetrics")?bin.readUshort:bin.readShort;
			obj[key]=func(data,offset+i*2);
		}
		return obj;
	}
};


Typr["T"].hmtx = {
	parseTab : function(data, offset, length, font)
	{
		var bin = Typr["B"];
		var aWidth = [];
		var lsBearing = [];
		
		var nG = font["maxp"]["numGlyphs"], nH = font["hhea"]["numberOfHMetrics"];
		var aw = 0, lsb = 0, i=0;
		while(i<nH) {  aw=bin.readUshort(data, offset+(i<<2));  lsb=bin.readShort(data, offset+(i<<2)+2);  aWidth.push(aw);  lsBearing.push(lsb);  i++;  }
		while(i<nG) {  aWidth.push(aw);  lsBearing.push(lsb);  i++;  }
		
		return {aWidth:aWidth, lsBearing:lsBearing};
	}
};


Typr["T"].kern = {
	parseTab : function(data, offset, length, font)
	{
		var bin = Typr["B"], kern=Typr["T"].kern;
		
		var version = bin.readUshort(data, offset);
		if(version==1) return kern.parseV1(data, offset, length, font);
		var nTables = bin.readUshort(data, offset+2);  offset+=4;
		
		var map = {glyph1: [], rval:[]};
		for(var i=0; i<nTables; i++)
		{
			offset+=2;	// skip version
			var length  = bin.readUshort(data, offset);  offset+=2;
			var coverage = bin.readUshort(data, offset);  offset+=2;
			var format = coverage>>>8;
			/* I have seen format 128 once, that's why I do */ format &= 0xf;
			if(format==0) offset = kern.readFormat0(data, offset, map);
			//else throw "unknown kern table format: "+format;
		}
		return map;
	},

	parseV1 : function(data, offset, length, font)
	{
		var bin = Typr["B"], kern=Typr["T"].kern;
		
		var version = bin.readFixed(data, offset);   // 0x00010000 
		var nTables = bin.readUint (data, offset+4);  offset+=8;
		
		var map = {glyph1: [], rval:[]};
		for(var i=0; i<nTables; i++)
		{
			var length = bin.readUint(data, offset);   offset+=4;
			var coverage = bin.readUshort(data, offset);  offset+=2;
			var tupleIndex = bin.readUshort(data, offset);  offset+=2;
			var format = coverage&0xff;
			if(format==0) offset = kern.readFormat0(data, offset, map);
			//else throw "unknown kern table format: "+format;
		}
		return map;
	},

	readFormat0 : function(data, offset, map)
	{
		var bin = Typr["B"], rUs = bin.readUshort;
		var pleft = -1;
		var nPairs        = rUs(data, offset);
		var searchRange   = rUs(data, offset+2);
		var entrySelector = rUs(data, offset+4);
		var rangeShift    = rUs(data, offset+6);  offset+=8;
		for(var j=0; j<nPairs; j++)
		{
			var left  = rUs(data, offset);  offset+=2;
			var right = rUs(data, offset);  offset+=2;
			var value = bin.readShort (data, offset);  offset+=2;
			if(left!=pleft) { map.glyph1.push(left);  map.rval.push({ glyph2:[], vals:[] }) }
			var rval = map.rval[map.rval.length-1];
			rval.glyph2.push(right);   rval.vals.push(value);
			pleft = left;
		}
		return offset;
	}
};


Typr["T"].loca = {
	parseTab : function(data, offset, length, font)
	{
		var bin = Typr["B"];
		var obj = [];
		
		var ver = font["head"]["indexToLocFormat"];
		var len = font["maxp"]["numGlyphs"]+1;
		
		if(ver==0) for(var i=0; i<len; i++) obj.push(bin.readUshort(data, offset+(i<<1))<<1);
		if(ver==1) for(var i=0; i<len; i++) obj.push(bin.readUint  (data, offset+(i<<2))   );
		
		return obj;
	}
};


Typr["T"].maxp = {
	parseTab : function(data, offset, length)
	{
		//console.log(data.length, offset, length);
		
		var bin = Typr["B"], rU=bin.readUshort;
		var obj = {};
		
		// both versions 0.5 and 1.0
		var ver = bin.readUint(data, offset); offset += 4;
		
		obj["numGlyphs"] = rU(data, offset);  offset += 2;
		
		// only 1.0
		/*
		if(ver == 0x00010000) {
			obj.maxPoints             = rU(data, offset);  offset += 2;
			obj.maxContours           = rU(data, offset);  offset += 2;
			obj.maxCompositePoints    = rU(data, offset);  offset += 2;
			obj.maxCompositeContours  = rU(data, offset);  offset += 2;
			obj.maxZones              = rU(data, offset);  offset += 2;
			obj.maxTwilightPoints     = rU(data, offset);  offset += 2;
			obj.maxStorage            = rU(data, offset);  offset += 2;
			obj.maxFunctionDefs       = rU(data, offset);  offset += 2;
			obj.maxInstructionDefs    = rU(data, offset);  offset += 2;
			obj.maxStackElements      = rU(data, offset);  offset += 2;
			obj.maxSizeOfInstructions = rU(data, offset);  offset += 2;
			obj.maxComponentElements  = rU(data, offset);  offset += 2;
			obj.maxComponentDepth     = rU(data, offset);  offset += 2;
		}
		*/
		
		return obj;
	}
};


Typr["T"].name = {
	parseTab : function(data, offset, length)
	{
		var bin = Typr["B"];
		var obj = {};
		var format = bin.readUshort(data, offset);  offset += 2;
		var count  = bin.readUshort(data, offset);  offset += 2;
		var stringOffset = bin.readUshort(data, offset);  offset += 2;
		
		//console.log(format,count);
		
		var names = [
			"copyright",
			"fontFamily",
			"fontSubfamily",
			"ID",
			"fullName",
			"version",
			"postScriptName",
			"trademark",
			"manufacturer",
			"designer",
			"description",
			"urlVendor",
			"urlDesigner",
			"licence",
			"licenceURL",
			"---",
			"typoFamilyName",
			"typoSubfamilyName",
			"compatibleFull",
			"sampleText",
			"postScriptCID",
			"wwsFamilyName",
			"wwsSubfamilyName",
			"lightPalette",
			"darkPalette"
		];
		
		var offset0 = offset;
		var rU = bin.readUshort;
		
		for(var i=0; i<count; i++)
		{
			var platformID = rU(data, offset);  offset += 2;
			var encodingID = rU(data, offset);  offset += 2;
			var languageID = rU(data, offset);  offset += 2;
			var nameID     = rU(data, offset);  offset += 2;
			var slen       = rU(data, offset);  offset += 2;
			var noffset    = rU(data, offset);  offset += 2;
			//console.log(platformID, encodingID, languageID.toString(16), nameID, length, noffset);
			
			
			var soff = offset0 + count*12 + noffset;
			var str;
			if(false){}
			else if(platformID == 0) str = bin.readUnicode(data, soff, slen/2);
			else if(platformID == 3 && encodingID == 0) str = bin.readUnicode(data, soff, slen/2);
			else if(encodingID == 0) str = bin.readASCII  (data, soff, slen);
			else if(encodingID == 1) str = bin.readUnicode(data, soff, slen/2);
			else if(encodingID == 3) str = bin.readUnicode(data, soff, slen/2);
			else if(encodingID == 4) str = bin.readUnicode(data, soff, slen/2);
			else if(encodingID ==10) str = bin.readUnicode(data, soff, slen/2);
			
			else if(platformID == 1) { str = bin.readASCII(data, soff, slen);  console.log("reading unknown MAC encoding "+encodingID+" as ASCII") }
			else {
				console.log("unknown encoding "+encodingID + ", platformID: "+platformID);
				str = bin.readASCII(data, soff, slen);
			}
			
			var tid = "p"+platformID+","+(languageID).toString(16);//Typr._platforms[platformID];
			if(obj[tid]==null) obj[tid] = {};
			obj[tid][names[nameID]] = str;
			obj[tid]["_lang"] = languageID;
			//console.log(tid, obj[tid]);
		}
		/*
		if(format == 1)
		{
			var langTagCount = bin.readUshort(data, offset);  offset += 2;
			for(var i=0; i<langTagCount; i++)
			{
				var length  = bin.readUshort(data, offset);  offset += 2;
				var noffset = bin.readUshort(data, offset);  offset += 2;
			}
		}
		*/
		
		//console.log(obj);
		var psn = "postScriptName";
		
		for(var p in obj) if(obj[p][psn]!=null && obj[p]["_lang"]==0x0409) return obj[p];		// United States
		for(var p in obj) if(obj[p][psn]!=null && obj[p]["_lang"]==0x0000) return obj[p];		// Universal
		for(var p in obj) if(obj[p][psn]!=null && obj[p]["_lang"]==0x0c0c) return obj[p];		// Canada
		for(var p in obj) if(obj[p][psn]!=null) return obj[p];
		
		var out;
		for(var p in obj) { out=obj[p]; break; }
		console.log("returning name table with languageID "+ out._lang);
		if(out[psn]==null && out["ID"]!=null) out[psn]=out["ID"];
		return out;
	}
}

Typr["T"].OS2 = {
	parseTab : function(data, offset, length)
	{
		var bin = Typr["B"];
		var ver = bin.readUshort(data, offset); offset += 2;
		
		var OS2 = Typr["T"].OS2;
		
		var obj = {};
		if     (ver==0) OS2.version0(data, offset, obj);
		else if(ver==1) OS2.version1(data, offset, obj);
		else if(ver==2 || ver==3 || ver==4) OS2.version2(data, offset, obj);
		else if(ver==5) OS2.version5(data, offset, obj);
		else throw "unknown OS/2 table version: "+ver;
		
		return obj;
	},

	version0 : function(data, offset, obj)
	{
		var bin = Typr["B"];
		obj["xAvgCharWidth"] = bin.readShort(data, offset); offset += 2;
		obj["usWeightClass"] = bin.readUshort(data, offset); offset += 2;
		obj["usWidthClass"]  = bin.readUshort(data, offset); offset += 2;
		obj["fsType"] = bin.readUshort(data, offset); offset += 2;
		obj["ySubscriptXSize"] = bin.readShort(data, offset); offset += 2;
		obj["ySubscriptYSize"] = bin.readShort(data, offset); offset += 2;
		obj["ySubscriptXOffset"] = bin.readShort(data, offset); offset += 2;
		obj["ySubscriptYOffset"] = bin.readShort(data, offset); offset += 2; 
		obj["ySuperscriptXSize"] = bin.readShort(data, offset); offset += 2; 
		obj["ySuperscriptYSize"] = bin.readShort(data, offset); offset += 2; 
		obj["ySuperscriptXOffset"] = bin.readShort(data, offset); offset += 2;
		obj["ySuperscriptYOffset"] = bin.readShort(data, offset); offset += 2;
		obj["yStrikeoutSize"] = bin.readShort(data, offset); offset += 2;
		obj["yStrikeoutPosition"] = bin.readShort(data, offset); offset += 2;
		obj["sFamilyClass"] = bin.readShort(data, offset); offset += 2;
		obj["panose"] = bin.readBytes(data, offset, 10);  offset += 10;
		obj["ulUnicodeRange1"]	= bin.readUint(data, offset);  offset += 4;
		obj["ulUnicodeRange2"]	= bin.readUint(data, offset);  offset += 4;
		obj["ulUnicodeRange3"]	= bin.readUint(data, offset);  offset += 4;
		obj["ulUnicodeRange4"]	= bin.readUint(data, offset);  offset += 4;
		obj["achVendID"] = bin.readASCII(data, offset, 4);  offset += 4;
		obj["fsSelection"]	 = bin.readUshort(data, offset); offset += 2;
		obj["usFirstCharIndex"] = bin.readUshort(data, offset); offset += 2;
		obj["usLastCharIndex"] = bin.readUshort(data, offset); offset += 2;
		obj["sTypoAscender"] = bin.readShort(data, offset); offset += 2;
		obj["sTypoDescender"] = bin.readShort(data, offset); offset += 2;
		obj["sTypoLineGap"] = bin.readShort(data, offset); offset += 2;
		obj["usWinAscent"] = bin.readUshort(data, offset); offset += 2;
		obj["usWinDescent"] = bin.readUshort(data, offset); offset += 2;
		return offset;
	},

	version1 : function(data, offset, obj)
	{
		var bin = Typr["B"];
		offset = Typr["T"].OS2.version0(data, offset, obj);
		
		obj["ulCodePageRange1"] = bin.readUint(data, offset); offset += 4;
		obj["ulCodePageRange2"] = bin.readUint(data, offset); offset += 4;
		return offset;
	},

	version2 : function(data, offset, obj)
	{
		var bin = Typr["B"], rU=bin.readUshort;
		offset = Typr["T"].OS2.version1(data, offset, obj);
		
		obj["sxHeight"]     = bin.readShort(data, offset); offset += 2;
		obj["sCapHeight"]   = bin.readShort(data, offset); offset += 2;
		obj["usDefault"]    = rU(data, offset); offset += 2;
		obj["usBreak"]      = rU(data, offset); offset += 2;
		obj["usMaxContext"] = rU(data, offset); offset += 2;
		return offset;
	},

	version5 : function(data, offset, obj)
	{
		var rU = Typr["B"].readUshort;
		offset = Typr["T"].OS2.version2(data, offset, obj);

		obj["usLowerOpticalPointSize"] = rU(data, offset); offset += 2;
		obj["usUpperOpticalPointSize"] = rU(data, offset); offset += 2;
		return offset;
	}
}

Typr["T"].post = {
	parseTab : function(data, offset, length)
	{
		var bin = Typr["B"];
		var obj = {};
		
		obj["version"]            = bin.readFixed(data, offset);  offset+=4;
		obj["italicAngle"]        = bin.readFixed(data, offset);  offset+=4;
		obj["underlinePosition"]  = bin.readShort(data, offset);  offset+=2;
		obj["underlineThickness"] = bin.readShort(data, offset);  offset+=2;

		return obj;
	}
};
Typr["T"].SVG = {
	parseTab : function(data, offset, length)
	{
		var bin = Typr["B"];
		var obj = { entries: []};

		var offset0 = offset;

		var tableVersion = bin.readUshort(data, offset);	offset += 2;
		var svgDocIndexOffset = bin.readUint(data, offset);	offset += 4;
		var reserved = bin.readUint(data, offset); offset += 4;

		offset = svgDocIndexOffset + offset0;

		var numEntries = bin.readUshort(data, offset);	offset += 2;

		for(var i=0; i<numEntries; i++)
		{
			var startGlyphID = bin.readUshort(data, offset);  offset += 2;
			var endGlyphID   = bin.readUshort(data, offset);  offset += 2;
			var svgDocOffset = bin.readUint  (data, offset);  offset += 4;
			var svgDocLength = bin.readUint  (data, offset);  offset += 4;

			var sbuf = new Uint8Array(data.buffer, offset0 + svgDocOffset + svgDocIndexOffset, svgDocLength);
			var svg = bin.readUTF8(sbuf, 0, sbuf.length);
			
			for(var f=startGlyphID; f<=endGlyphID; f++) {
				obj.entries[f] = svg;
			}
		}
		return obj;
	}
};






Typr["U"] = {
	"shape" : function(font,str,ltr) {
		
		var getGlyphPosition = function(font, gls,i1,ltr)
		{
			var g1=gls[i1],g2=gls[i1+1], kern=font["kern"];
			if(kern) {
				var ind1 = kern.glyph1.indexOf(g1);
				if(ind1!=-1)
				{
					var ind2 = kern.rval[ind1].glyph2.indexOf(g2);
					if(ind2!=-1) return [0,0,kern.rval[ind1].vals[ind2],0];
				}
			}
			//console.log("no kern");
			return [0,0,0,0];
		}
		
		
		var gls = [];
		for(var i=0; i<str.length; i++) {
			var cc = str.codePointAt(i);  if(cc>0xffff) i++;
			gls.push(Typr["U"]["codeToGlyph"](font, cc));
		}
		var shape = [];
		var x = 0, y = 0;
		
		for(var i=0; i<gls.length; i++) {
			var padj = getGlyphPosition(font, gls,i,ltr);
			var gid = gls[i];
			var ax=font["hmtx"].aWidth[gid]+padj[2];
			shape.push({"g":gid, "cl":i, "dx":0, "dy":0, "ax":ax, "ay":0});
			x+=ax;
		}
		return shape;
	},
	
	"shapeToPath" : function(font,shape,clr) {
		var tpath = {cmds:[], crds:[]};
		var x = 0, y = 0;
		
		for(var i=0; i<shape.length; i++) {
			var it = shape[i]
			var path = Typr["U"]["glyphToPath"](font, it["g"]), crds=path["crds"];
			for(var j=0; j<crds.length; j+=2) {
				tpath.crds.push(crds[j  ] + x + it["dx"]);
				tpath.crds.push(crds[j+1] + y + it["dy"]);
			}
			if(clr) tpath.cmds.push(clr);
			for(var j=0; j<path["cmds"].length; j++) tpath.cmds.push(path["cmds"][j]);
			var clen = tpath.cmds.length;
			if(clr) if(clen!=0 && tpath.cmds[clen-1]!="X") tpath.cmds.push("X");  // SVG fonts might contain "X". Then, nothing would stroke non-SVG glyphs.
			
			x += it["ax"];  y+= it["ay"];
		}
		return {"cmds":tpath.cmds, "crds":tpath.crds};
	},

	"codeToGlyph" : function(font, code)
	{
		var cmap = font["cmap"];
		//console.log(cmap);
		// "p3e10" for NotoEmoji-Regular.ttf
		var tind = -1, pps=["p3e10","p0e4","p3e1","p1e0","p0e3","p0e1"/*,"p3e3"*/];
		for(var i=0; i<pps.length; i++) if(cmap.ids[pps[i]]!=null) {  tind=cmap.ids[pps[i]];  break;  }
		if(tind==-1) throw "no familiar platform and encoding!";
		
		
		// find the greatest index with a value <=v
		var arrSearch = function(arr, k, v) {
			var l=0, r=Math.floor(arr.length/k);
			while(l+1!=r) {  var mid = l + ((r-l)>>>1);   if(arr[mid*k]<=v) l=mid;  else r=mid;  }
			return l*k;
		}
		
		var tab = cmap.tables[tind], fmt=tab.format, gid = -1;  //console.log(fmt); throw "e";
		
		if(fmt==0) {
			if(code>=tab.map.length) gid = 0;
			else gid = tab.map[code];
		}
		/*else if(fmt==2) {
			var data=font["_data"], off = cmap.off+tab.off+6, bin=Typr["B"];
			var shKey = bin.readUshort(data,off + 2*(code>>>8));
			var shInd = off + 256*2 + shKey*8;
			
			var firstCode = bin.readUshort(data,shInd);
			var entryCount= bin.readUshort(data,shInd+2);
			var idDelta   = bin.readShort (data,shInd+4);
			var idRangeOffset = bin.readUshort(data,shInd+6);
			
			if(firstCode<=code && code<=firstCode+entryCount) {
				// not completely correct
				gid = bin.readUshort(data, shInd+6+idRangeOffset + (code&255)*2);
			}
			else gid=0;
			//if(code>256) console.log(code,(code>>>8),shKey,firstCode,entryCount,idDelta,idRangeOffset);
			
			//throw "e";
			//console.log(tab,  bin.readUshort(data,off));
			//throw "e";
		}*/
		else if(fmt==4) {
			var sind = -1, ec = tab.endCount;
			if(code>ec[ec.length-1]) sind=-1;
			else {
				// smallest index with code <= value
				sind = arrSearch(ec,1,code);
				if(ec[sind]<code) sind++;
			}
			if(sind==-1) gid = 0;
			else if(code<tab.startCount[sind]) gid = 0;
			else {
				var gli = 0;
				if(tab.idRangeOffset[sind]!=0) gli = tab.glyphIdArray[(code-tab.startCount[sind]) + (tab.idRangeOffset[sind]>>1) - (tab.idRangeOffset.length-sind)];
				else                           gli = code + tab.idDelta[sind];
				gid = (gli & 0xFFFF);
			}
		}
		else if(fmt==6) {
			var off = code-tab.firstCode, arr=tab.glyphIdArray;
			if(off<0 || off>=arr.length) gid=0;
			else gid = arr[off];
		}
		else if(fmt==12) {
			var grp = tab.groups;  //console.log(grp);  throw "e";
			
			if(code>grp[grp.length-2]) gid = 0;
			else {
				var i = arrSearch(grp,3,code);
				if(grp[i]<=code && code<=grp[i+1]) {  gid = grp[i+2] + (code-grp[i]);  }
				if(gid==-1) gid=0;
			}
		}
		else throw "unknown cmap table format "+tab.format;
		
		//*
		var SVG = font["SVG "], loca = font["loca"];
		// if the font claims to have a Glyph for a character, but the glyph is empty, and the character is not "white", it is a lie!
		if(gid!=0 && font["CFF "]==null && (SVG==null || SVG.entries[gid]==null) && loca[gid]==loca[gid+1]  // loca not present in CFF or SVG fonts
			&& [0x9,0xa,0xb,0xc,0xd,0x20,0x85,0xa0,0x1680,0x2028,0x2029,0x202f,0x3000,
				0x180e,0x200b,0x200c,0x200d,0x2060,0xfeff].indexOf(code)==-1 && !(0x2000<=code && code<=0x200a))  gid=0;
		//*/
		
		return gid;
	},

	"glyphToPath" : function(font, gid)
	{
		var path = { cmds:[], crds:[] };
		var SVG = font["SVG "], CFF = font["CFF "];
		var U = Typr["U"];
		if(SVG && SVG.entries[gid]) {
			var p = SVG.entries[gid];  
			if(p!=null) {
				if(typeof p == "string") {  p = U["SVG"].toPath(p);  SVG.entries[gid]=p;  }
				path=p;
			}
		}
		else if(CFF) {
			var pdct = CFF["Private"];
			var state = {x:0,y:0,stack:[],nStems:0,haveWidth:false,width: pdct ? pdct["defaultWidthX"] : 0,open:false};
			if(CFF["ROS"]) {
				var gi = 0;
				while(CFF["FDSelect"][gi+2]<=gid) gi+=2;
				pdct = CFF["FDArray"][CFF["FDSelect"][gi+1]]["Private"];
			}
			U["_drawCFF"](CFF["CharStrings"][gid], state, CFF, pdct, path);
		}
		else if(font["glyf"]) {  U["_drawGlyf"](gid, font, path);  }
		return {"cmds":path.cmds, "crds":path.crds};
	},

	"_drawGlyf" : function(gid, font, path)
	{
		var gl = font["glyf"][gid];
		if(gl==null) gl = font["glyf"][gid] = Typr["T"].glyf._parseGlyf(font, gid);
		if(gl!=null){
			if(gl.noc>-1) Typr["U"]["_simpleGlyph"](gl, path);
			else          Typr["U"]["_compoGlyph"] (gl, font, path);
		}
	},
	"_simpleGlyph" : function(gl, p)
	{
		var P = Typr["U"]["P"];
		for(var c=0; c<gl.noc; c++) {
			var i0 = (c==0) ? 0 : (gl.endPts[c-1] + 1);
			var il = gl.endPts[c];
			
			for(var i=i0; i<=il; i++) {
				var pr = (i==i0)?il:(i-1);
				var nx = (i==il)?i0:(i+1);
				var onCurve = gl.flags[i]&1;
				var prOnCurve = gl.flags[pr]&1;
				var nxOnCurve = gl.flags[nx]&1;
				
				var x = gl.xs[i], y = gl.ys[i];
				
				if(i==i0) { 
					if(onCurve) {
						if(prOnCurve) P.MoveTo(p, gl.xs[pr], gl.ys[pr]); 
						else          {  P.MoveTo(p,x,y);  continue;  /*  will do CurveTo at il  */  }
					}
					else {
						if(prOnCurve) P.MoveTo(p,  gl.xs[pr],       gl.ys[pr]        );
						else          P.MoveTo(p, Math.floor((gl.xs[pr]+x)*0.5), Math.floor((gl.ys[pr]+y)*0.5)   ); 
					}
				}
				if(onCurve) {
					if(prOnCurve) P.LineTo(p,x,y);
				}
				else {
					if(nxOnCurve) P.qCurveTo(p, x, y, gl.xs[nx], gl.ys[nx]); 
					else          P.qCurveTo(p, x, y, Math.floor((x+gl.xs[nx])*0.5), Math.floor((y+gl.ys[nx])*0.5) ); 
				}
			}
			P.ClosePath(p);
		}
	},
	"_compoGlyph" : function(gl, font, p) {
		for(var j=0; j<gl.parts.length; j++) {
			var path = { cmds:[], crds:[] };
			var prt = gl.parts[j];
			Typr["U"]["_drawGlyf"](prt.glyphIndex, font, path);
			
			var m = prt.m;
			for(var i=0; i<path.crds.length; i+=2) {
				var x = path.crds[i  ], y = path.crds[i+1];
				p.crds.push(x*m.a + y*m.b + m.tx);
				p.crds.push(x*m.c + y*m.d + m.ty);
			}
			for(var i=0; i<path.cmds.length; i++) p.cmds.push(path.cmds[i]);
		}
	},

	"pathToSVG" : function(path, prec)
	{
		var cmds = path["cmds"], crds = path["crds"];
		if(prec==null) prec = 5;
		var out = [], co = 0, lmap = {"M":2,"L":2,"Q":4,"C":6};
		for(var i=0; i<cmds.length; i++)
		{
			var cmd = cmds[i], cn = co+(lmap[cmd]?lmap[cmd]:0);  
			out.push(cmd);
			while(co<cn) {  var c = crds[co++];  out.push(parseFloat(c.toFixed(prec))+(co==cn?"":" "));  }
		}
		return out.join("");
	},
	"SVGToPath" : function(d) {
		var pth = {cmds:[], crds:[]};
		Typr["U"]["SVG"].svgToPath(d, pth);
		return {"cmds":pth.cmds, "crds":pth.crds};
	},

	"pathToContext" : function(path, ctx) {
		var c = 0, cmds = path["cmds"], crds = path["crds"];
		
		for(var j=0; j<cmds.length; j++) {
			var cmd = cmds[j];
			if     (cmd=="M") {
				ctx.moveTo(crds[c], crds[c+1]);
				c+=2;
			}
			else if(cmd=="L") {
				ctx.lineTo(crds[c], crds[c+1]);
				c+=2;
			}
			else if(cmd=="C") {
				ctx.bezierCurveTo(crds[c], crds[c+1], crds[c+2], crds[c+3], crds[c+4], crds[c+5]);
				c+=6;
			}
			else if(cmd=="Q") {
				ctx.quadraticCurveTo(crds[c], crds[c+1], crds[c+2], crds[c+3]);
				c+=4;
			}
			else if(cmd.charAt(0)=="#") {
				ctx.beginPath();
				ctx.fillStyle = cmd;
			}
			else if(cmd=="Z") {
				ctx.closePath();
			}
			else if(cmd=="X") {
				ctx.fill();
			}
		}
	},

	"P" : {
		MoveTo    : function(p, x, y)        {  p.cmds.push("M");  p.crds.push(x,y);  },
		LineTo    : function(p, x, y)        {  p.cmds.push("L");  p.crds.push(x,y);  },
		CurveTo   : function(p, a,b,c,d,e,f) {  p.cmds.push("C");  p.crds.push(a,b,c,d,e,f);  },
		qCurveTo  : function(p, a,b,c,d)     {  p.cmds.push("Q");  p.crds.push(a,b,c,d);  },
		ClosePath : function(p)              {  p.cmds.push("Z");  }
	},

	"_drawCFF" : function(cmds, state, font, pdct, p)
	{
		var stack = state.stack;
		var nStems = state.nStems, haveWidth=state.haveWidth, width=state.width, open=state.open;
		var i=0;
		var x=state.x, y=state.y, c1x=0, c1y=0, c2x=0, c2y=0, c3x=0, c3y=0, c4x=0, c4y=0, jpx=0, jpy=0;
		var CFF = Typr["T"].CFF, P = Typr["U"]["P"];
		
		var nominalWidthX = pdct["nominalWidthX"];
		var o = {val:0,size:0};
		//console.log(cmds);
		while(i<cmds.length)
		{
			CFF.getCharString(cmds, i, o);
			var v = o.val;
			i += o.size;
				
			if(false) {}
			else if(v=="o1" || v=="o18")  //  hstem || hstemhm
			{
				var hasWidthArg;

				// The number of stem operators on the stack is always even.
				// If the value is uneven, that means a width is specified.
				hasWidthArg = stack.length % 2 !== 0;
				if (hasWidthArg && !haveWidth) {
					width = stack.shift() + nominalWidthX;
				}

				nStems += stack.length >> 1;
				stack.length = 0;
				haveWidth = true;
			}
			else if(v=="o3" || v=="o23")  // vstem || vstemhm
			{
				var hasWidthArg;

				// The number of stem operators on the stack is always even.
				// If the value is uneven, that means a width is specified.
				hasWidthArg = stack.length % 2 !== 0;
				if (hasWidthArg && !haveWidth) {
					width = stack.shift() + nominalWidthX;
				}

				nStems += stack.length >> 1;
				stack.length = 0;
				haveWidth = true;
			}
			else if(v=="o4")
			{
				if (stack.length > 1 && !haveWidth) {
							width = stack.shift() + nominalWidthX;
							haveWidth = true;
						}
				if(open) P.ClosePath(p);

						y += stack.pop();
						P.MoveTo(p,x,y);   open=true;
			}
			else if(v=="o5")
			{
				while (stack.length > 0) {
							x += stack.shift();
							y += stack.shift();
							P.LineTo(p, x, y);
						}
			}
			else if(v=="o6" || v=="o7")  // hlineto || vlineto
			{
				var count = stack.length;
				var isX = (v == "o6");
				
				for(var j=0; j<count; j++) {
					var sval = stack.shift();
					
					if(isX) x += sval;  else  y += sval;
					isX = !isX;
					P.LineTo(p, x, y);
				}
			}
			else if(v=="o8" || v=="o24")	// rrcurveto || rcurveline
			{
				var count = stack.length;
				var index = 0;
				while(index+6 <= count) {
					c1x = x + stack.shift();
					c1y = y + stack.shift();
					c2x = c1x + stack.shift();
					c2y = c1y + stack.shift();
					x = c2x + stack.shift();
					y = c2y + stack.shift();
					P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
					index+=6;
				}
				if(v=="o24")
				{
					x += stack.shift();
					y += stack.shift();
					P.LineTo(p, x, y);
				}
			}
			else if(v=="o11")  break;
			else if(v=="o1234" || v=="o1235" || v=="o1236" || v=="o1237")//if((v+"").slice(0,3)=="o12")
			{
				if(v=="o1234")
				{
					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
					P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
					P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);
					
				}
				if(v=="o1235")
				{
					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
					P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
					P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);
				}
				if(v=="o1236")
				{
					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
					P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
					P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);
				}
				if(v=="o1237")
				{
					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();
					}
					P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
					P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);
				}
			}
			else if(v=="o14")
			{
				if (stack.length > 0 && !haveWidth) {
							width = stack.shift() + font["nominalWidthX"];
							haveWidth = true;
						}
				if(stack.length==4) // seac = standard encoding accented character
				{
				
					var asb = 0;
					var adx = stack.shift();
					var ady = stack.shift();
					var bchar = stack.shift();
					var achar = stack.shift();
				
					
					var bind = CFF.glyphBySE(font, bchar);
					var aind = CFF.glyphBySE(font, achar);
					
					//console.log(bchar, bind);
					//console.log(achar, aind);
					//state.x=x; state.y=y; state.nStems=nStems; state.haveWidth=haveWidth; state.width=width;  state.open=open;
					
					Typr["U"]["_drawCFF"](font["CharStrings"][bind], state,font,pdct,p);
					state.x = adx; state.y = ady;
					Typr["U"]["_drawCFF"](font["CharStrings"][aind], state,font,pdct,p);
					
					//x=state.x; y=state.y; nStems=state.nStems; haveWidth=state.haveWidth; width=state.width;  open=state.open;
				}
				if(open) {  P.ClosePath(p);  open=false;  }
			}		
			else if(v=="o19" || v=="o20") 
			{ 
				var hasWidthArg;

				// The number of stem operators on the stack is always even.
				// If the value is uneven, that means a width is specified.
				hasWidthArg = stack.length % 2 !== 0;
				if (hasWidthArg && !haveWidth) {
					width = stack.shift() + nominalWidthX;
				}

				nStems += stack.length >> 1;
				stack.length = 0;
				haveWidth = true;
				
				i += (nStems + 7) >> 3;
			}
			
			else if(v=="o21") {
				if (stack.length > 2 && !haveWidth) {
							width = stack.shift() + nominalWidthX;
							haveWidth = true;
						}

						y += stack.pop();
						x += stack.pop();
						
						if(open) P.ClosePath(p);
						P.MoveTo(p,x,y);   open=true;
			}
			else if(v=="o22")
			{
				 if (stack.length > 1 && !haveWidth) {
							width = stack.shift() + nominalWidthX;
							haveWidth = true;
						}
						
						x += stack.pop();
						
						if(open) P.ClosePath(p);
						P.MoveTo(p,x,y);   open=true;                    
			}
			else if(v=="o25")
			{
				while (stack.length > 6) {
							x += stack.shift();
							y += stack.shift();
							P.LineTo(p, 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();
						P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
			}
			else if(v=="o26") 
			{
				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();
							P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
						}

			}
			else if(v=="o27")
			{
				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;
							P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
						}
			}
			else if(v=="o10" || v=="o29")	// callsubr || callgsubr
			{
				var obj = (v=="o10" ? pdct : font);
				if(stack.length==0) { console.log("error: empty stack");  }
				else {
					var ind = stack.pop();
					var subr = obj["Subrs"][ ind + obj["Bias"] ];
					state.x=x; state.y=y; state.nStems=nStems; state.haveWidth=haveWidth; state.width=width;  state.open=open;
					Typr["U"]["_drawCFF"](subr, state,font,pdct,p);
					x=state.x; y=state.y; nStems=state.nStems; haveWidth=state.haveWidth; width=state.width;  open=state.open;
				}
			}
			else if(v=="o30" || v=="o31")   // vhcurveto || hvcurveto
			{
				var count, count1 = stack.length;
				var index = 0;
				var alternate = v == "o31";
				
				count  = count1 & ~2;
				index += count1 - count;
				
				while ( index < count ) 
				{
					if(alternate)
					{
						c1x = x + stack.shift();
						c1y = y;
						c2x = c1x + stack.shift();
						c2y = c1y + stack.shift();
						y = c2y + stack.shift();
						if(count-index == 5) {  x = c2x + stack.shift();  index++;  }
						else x = c2x;
						alternate = false;
					}
					else
					{
						c1x = x;
						c1y = y + stack.shift();
						c2x = c1x + stack.shift();
						c2y = c1y + stack.shift();
						x = c2x + stack.shift();
						if(count-index == 5) {  y = c2y + stack.shift();  index++;  }
						else y = c2y;
						alternate = true;
					}
					P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
					index += 4;
				}
			}
			
			else if((v+"").charAt(0)=="o") {   console.log("Unknown operation: "+v, cmds); throw v;  }
			else stack.push(v);
		}
		//console.log(cmds);
		state.x=x; state.y=y; state.nStems=nStems; state.haveWidth=haveWidth; state.width=width; state.open=open;
	},


	"SVG" : function() {
		var M = {
			getScale : function(m) {  return Math.sqrt(Math.abs(m[0]*m[3]-m[1]*m[2]));  },
			translate: function(m,x,y) {  M.concat(m, [1,0,0,1,x,y]);  },
			rotate   : function(m,a  ) {  M.concat(m, [Math.cos(a), -Math.sin(a), Math.sin(a), Math.cos(a),0,0]);  },
			scale    : function(m,x,y) {  M.concat(m, [x,0,0,y,0,0]);  },
			concat   : function(m,w  ) {  
				var a=m[0],b=m[1],c=m[2],d=m[3],tx=m[4],ty=m[5];
				m[0] = (a *w[0])+(b *w[2]);       m[1] = (a *w[1])+(b *w[3]);
				m[2] = (c *w[0])+(d *w[2]);       m[3] = (c *w[1])+(d *w[3]);
				m[4] = (tx*w[0])+(ty*w[2])+w[4];  m[5] = (tx*w[1])+(ty*w[3])+w[5]; 
			},
			invert   : function(m    ) {  
				var a=m[0],b=m[1],c=m[2],d=m[3],tx=m[4],ty=m[5], adbc=a*d-b*c;
				m[0] = d/adbc;  m[1] = -b/adbc;  m[2] =-c/adbc;  m[3] =  a/adbc;
				m[4] = (c*ty - d*tx)/adbc;  m[5] = (b*tx - a*ty)/adbc;
			},
			multPoint: function(m, p ) {  var x=p[0],y=p[1];  return [x*m[0]+y*m[2]+m[4],   x*m[1]+y*m[3]+m[5]];  },
			multArray: function(m, a ) {  for(var i=0; i<a.length; i+=2) {  var x=a[i],y=a[i+1];  a[i]=x*m[0]+y*m[2]+m[4];  a[i+1]=x*m[1]+y*m[3]+m[5];  }  }
		}
		
		function _bracketSplit(str, lbr, rbr) {
			var out = [], pos=0, ci = 0, lvl = 0;
			while(true) {  //throw "e";
				var li = str.indexOf(lbr, ci);
				var ri = str.indexOf(rbr, ci);
				if(li==-1 && ri==-1) break;
				if(ri==-1 || (li!=-1 && li<ri)) {
					if(lvl==0) {  out.push(str.slice(pos,li).trim());  pos=li+1;  }
					lvl++;  ci=li+1;
				}
				else if(li==-1 || (ri!=-1 && ri<li)) {
					lvl--;
					if(lvl==0) {  out.push(str.slice(pos,ri).trim());  pos=ri+1;  }
					ci=ri+1;
				}
			}
			return out;
		}
		//"cssMap": 
		function cssMap(str) {
			var pts = _bracketSplit(str, "{", "}");
			var css = {};
			for(var i=0; i<pts.length; i+=2) {
				var cn = pts[i].split(",");
				for(var j=0; j<cn.length; j++) {
					var cnj = cn[j].trim();  if(css[cnj]==null) css[cnj]="";
					css[cnj] += pts[i+1];
				}
			}
			return css;
		}
		//"readTrnf" 
		function readTrnf(trna) {
			var pts = _bracketSplit(trna, "(",")");
			var m = [1,0,0,1,0,0];
			for(var i=0; i<pts.length; i+=2) {  var om=m;  m=_readTrnsAttr(pts[i], pts[i+1]);  M.concat(m,om);  }
			return m;
		}
		
		function _readTrnsAttr(fnc, vls) {
			//console.log(vls);
			//vls = vls.replace(/\-/g, " -").trim();
			var m = [1,0,0,1,0,0], gotSep = true;
			for(var i=0; i<vls.length; i++) {	// matrix(.99915 0 0 .99915.418.552)   matrix(1 0 0-.9474-22.535 271.03)
				var ch = vls.charAt(i);
				if(ch=="," || ch==" ") gotSep = true;
				else if(ch==".") {
					if(!gotSep) {  vls = vls.slice(0,i) + ","+vls.slice(i);  i++;  }     gotSep = false;
				}
				else if(ch=="-" && i>0 && vls[i-1]!="e") {  vls = vls.slice(0,i) + " "+vls.slice(i);  i++;  gotSep=true;  }
			}
			
			vls = vls.split(/\s*[\s,]\s*/).map(parseFloat);
			if(false) {}
			else if(fnc=="translate") {  if(vls.length==1) M.translate(m,vls[0],     0);  else M.translate(m,vls[0],vls[1]);  }
			else if(fnc=="scale"    ) {  if(vls.length==1) M.scale    (m,vls[0],vls[0]);  else M.scale    (m,vls[0],vls[1]);  }
			else if(fnc=="rotate"   ) {  var tx=0,ty=0;  if(vls.length!=1) { tx=vls[1];  ty=vls[2];  }  M.translate(m,-tx,-ty);  M.rotate(m,-Math.PI*vls[0]/180);  M.translate(m,tx,ty);  }
			else if(fnc=="matrix"   ) m = vls;
			else console.log("unknown transform: ", fnc);
			return m;
		}
		
		function toPath(str)
		{
			var pth = {cmds:[], crds:[]};
			if(str==null) return pth;
			
			var prsr = new DOMParser();
			var doc = prsr["parseFromString"](str,"image/svg+xml");
			
			//var svg = doc.firstChild;  while(svg.tagName!="svg") svg = svg.nextSibling;
			var svg = doc.getElementsByTagName("svg")[0];
			var vb = svg.getAttribute("viewBox");
			if(vb) vb = vb.trim().split(" ").map(parseFloat);  else   vb = [0,0,1000,1000];
			_toPath(svg.children, pth);
			for(var i=0; i<pth.crds.length; i+=2) {
				var x = pth.crds[i], y = pth.crds[i+1];
				x -= vb[0];
				y -= vb[1];
				y = -y;
				pth.crds[i] = x;
				pth.crds[i+1] = y;
			}
			return pth;
		}

		function _toPath(nds, pth, fill) {
			for(var ni=0; ni<nds.length; ni++) {
				var nd = nds[ni], tn = nd.tagName;
				var cfl = nd.getAttribute("fill");  if(cfl==null) cfl = fill;
				if(tn=="g") {
					var tp = {crds:[], cmds:[]};
					_toPath(nd.children, tp, cfl);
					var trf = nd.getAttribute("transform");
					if(trf) {
						var m = readTrnf(trf);
						M.multArray(m, tp.crds);
					}
					pth.crds=pth.crds.concat(tp.crds);
					pth.cmds=pth.cmds.concat(tp.cmds);
				}
				else if(tn=="path" || tn=="circle" || tn=="ellipse") {
					pth.cmds.push(cfl?cfl:"#000000");
					var d;
					if(tn=="path") d = nd.getAttribute("d");  //console.log(d);
					if(tn=="circle" || tn=="ellipse") {
						var vls=[0,0,0,0], nms=["cx","cy","rx","ry","r"];
						for(var i=0; i<5; i++) {  var V=nd.getAttribute(nms[i]);  if(V) {  V=parseFloat(V);  if(i<4) vls[i]=V;  else vls[2]=vls[3]=V;  }  }
						var cx=vls[0],cy=vls[1],rx=vls[2],ry=vls[3];
						d = ["M",cx-rx,cy,"a",rx,ry,0,1,0,rx*2,0,"a",rx,ry,0,1,0,-rx*2,0].join(" ");
					}
					svgToPath(d, pth);  pth.cmds.push("X");
				}
				else if(tn=="defs") {}
				else console.log(tn, nd);
			}
		}

		function _tokens(d) {
			var ts = [], off = 0, rn=false, cn="", pc="";  // reading number, current number, prev char
			while(off<d.length){
				var cc=d.charCodeAt(off), ch = d.charAt(off);  off++;
				var isNum = (48<=cc && cc<=57) || ch=="." || ch=="-" || ch=="e" || ch=="E";
				
				if(rn) {
					if( (ch=="-" && pc!="e") || (ch=="." && cn.indexOf(".")!=-1)) {  ts.push(parseFloat(cn));  cn=ch;  }
					else if(isNum) cn+=ch;
					else {  ts.push(parseFloat(cn));  if(ch!="," && ch!=" ") ts.push(ch);  rn=false;  }
				}
				else {
					if(isNum) {  cn=ch;  rn=true;  }
					else if(ch!="," && ch!=" ") ts.push(ch);
				}
				pc = ch;
			}
			if(rn) ts.push(parseFloat(cn));
			return ts;
		}
		
		function _reps(ts, off, ps) {
			var i = off;
			while(i<ts.length) {  if((typeof ts[i]) == "string") break;  i+=ps;  }
			return (i-off)/ps;
		}

		function svgToPath(d, pth) {	
			var ts = _tokens(d);
			var i = 0, x = 0, y = 0, ox = 0, oy = 0, oldo=pth.crds.length;
			var pc = {"M":2,"L":2,"H":1,"V":1,   "T":2,"S":4, "A":7,   "Q":4, "C":6};
			var cmds = pth.cmds, crds = pth.crds;
			
			while(i<ts.length) {
				var cmd = ts[i];  i++;
				var cmu = cmd.toUpperCase();
				
				if(cmu=="Z") {  cmds.push("Z");  x=ox;  y=oy;  }
				else {
					var ps = pc[cmu], reps = _reps(ts, i, ps);
				
					for(var j=0; j<reps; j++) {
						// If a moveto is followed by multiple pairs of coordinates, the subsequent pairs are treated as implicit lineto commands.
						if(j==1 && cmu=="M") {  cmd=(cmd==cmu)?"L":"l";  cmu="L";  }
						
						var xi = 0, yi = 0;   if(cmd!=cmu) {  xi=x;  yi=y;  }
						
						if(false) {}
						else if(cmu=="M") {  x = xi+ts[i++];  y = yi+ts[i++];  cmds.push("M");  crds.push(x,y);  ox=x;  oy=y; }
						else if(cmu=="L") {  x = xi+ts[i++];  y = yi+ts[i++];  cmds.push("L");  crds.push(x,y);  }
						else if(cmu=="H") {  x = xi+ts[i++];                   cmds.push("L");  crds.push(x,y);  }
						else if(cmu=="V") {  y = yi+ts[i++];                   cmds.push("L");  crds.push(x,y);  }
						else if(cmu=="Q") {
							var x1=xi+ts[i++], y1=yi+ts[i++], x2=xi+ts[i++], y2=yi+ts[i++];
							cmds.push("Q");  crds.push(x1,y1,x2,y2);  x=x2;  y=y2;
						}
						else if(cmu=="T") {
							var co = Math.max(crds.length-2, oldo);
							var x1 = x+x-crds[co], y1 = y+y-crds[co+1];
							var x2=xi+ts[i++], y2=yi+ts[i++];  
							cmds.push("Q");  crds.push(x1,y1,x2,y2);  x=x2;  y=y2;
						}
						else if(cmu=="C") {
							var x1=xi+ts[i++], y1=yi+ts[i++], x2=xi+ts[i++], y2=yi+ts[i++], x3=xi+ts[i++], y3=yi+ts[i++];
							cmds.push("C");  crds.push(x1,y1,x2,y2,x3,y3);  x=x3;  y=y3;
						}
						else if(cmu=="S") {
							var co = Math.max(crds.length-(cmds[cmds.length-1]=="C"?4:2), oldo);
							var x1 = x+x-crds[co], y1 = y+y-crds[co+1];
							var x2=xi+ts[i++], y2=yi+ts[i++], x3=xi+ts[i++], y3=yi+ts[i++];  
							cmds.push("C");  crds.push(x1,y1,x2,y2,x3,y3);  x=x3;  y=y3;
						}
						else if(cmu=="A") {  // convert SVG Arc to four cubic bézier segments "C"
							var x1 = x, y1 = y;
							var rx = ts[i++], ry = ts[i++];
							var phi = ts[i++]*(Math.PI/180), fA = ts[i++], fS = ts[i++];
							var x2 = xi+ts[i++], y2 = yi+ts[i++];
							if(x2==x && y2==y && rx==0 && ry==0) continue;
							
							var hdx = (x1-x2)/2, hdy = (y1-y2)/2;
							var cosP = Math.cos(phi), sinP = Math.sin(phi);
							var x1A =  cosP * hdx + sinP * hdy;
							var y1A = -sinP * hdx + cosP * hdy;
							
							var rxS = rx*rx, ryS = ry*ry;
							var x1AS  = x1A*x1A, y1AS = y1A*y1A;
							var frc = (rxS*ryS  - rxS*y1AS - ryS*x1AS)  /  (rxS*y1AS + ryS*x1AS);
							var coef = (fA!=fS ? 1 : -1) * Math.sqrt(  Math.max(frc,0)  );
							var cxA =  coef * (rx * y1A) / ry;
							var cyA = -coef * (ry * x1A) / rx;
							
							var cx = cosP*cxA - sinP*cyA + (x1+x2)/2;
							var cy = sinP*cxA + cosP*cyA + (y1+y2)/2;
							
							var angl = function(ux,uy,vx,vy) {  var lU = Math.sqrt(ux*ux+uy*uy), lV = Math.sqrt(vx*vx+vy*vy);
									var num = (ux*vx+uy*vy) / (lU*lV);  //console.log(num, Math.acos(num));
									return (ux*vy-uy*vx>=0?1:-1) * Math.acos( Math.max(-1, Math.min(1, num)) );  }
							
							var vX = (x1A-cxA)/rx, vY = (y1A-cyA)/ry;
							var theta1 = angl( 1, 0, vX,vY);
							var dtheta = angl(vX,vY, (-x1A-cxA)/rx, (-y1A-cyA)/ry);
							dtheta = dtheta % (2*Math.PI);
							
							var arc = function(gst,x,y,r,a0,a1, neg) {
								var rotate = function(m, a) {  var si=Math.sin(a), co=Math.cos(a);
									var a=m[0],b=m[1],c=m[2],d=m[3];
									m[0] = (a *co)+(b *si);   m[1] = (-a *si)+(b *co);
									m[2] = (c *co)+(d *si);   m[3] = (-c *si)+(d *co);
								}
								var multArr= function(m,a) {
									for(var j=0; j<a.length; j+=2) {
										var x=a[j], y=a[j+1];
										a[j  ] = m[0]*x + m[2]*y + m[4];
										a[j+1] = m[1]*x + m[3]*y + m[5];
									}
								}
								var concatA= function(a,b) {  for(var j=0; j<b.length; j++) a.push(b[j]);  }
								var concatP= function(p,r) {  concatA(p.cmds,r.cmds);  concatA(p.crds,r.crds);  }
								// circle from a0 counter-clock-wise to a1
								if(neg) while(a1>a0) a1-=2*Math.PI;
								else    while(a1<a0) a1+=2*Math.PI;
								var th = (a1-a0)/4;
								
								var x0 = Math.cos(th/2), y0 = -Math.sin(th/2);
								var x1 = (4-x0)/3, y1 = y0==0 ? y0 : (1-x0)*(3-x0)/(3*y0);
								var x2 = x1, y2 = -y1;
								var x3 = x0, y3 = -y0;
								
								var ps = [x1,y1,x2,y2,x3,y3];
								
								var pth = {cmds:["C","C","C","C"], crds:ps.slice(0)};
								var rot = [1,0,0,1,0,0];  rotate(rot,-th);
								for(var j=0; j<3; j++) {  multArr(rot,ps);  concatA(pth.crds,ps);  }
								
								rotate(rot, -a0+th/2);  rot[0]*=r;  rot[1]*=r;  rot[2]*=r;  rot[3]*=r;  rot[4]=x;  rot[5]=y; 
								multArr(rot, pth.crds);
								multArr(gst.ctm, pth.crds);
								concatP(gst.pth, pth);
							}
							
							var gst = {pth:pth, ctm:[rx*cosP,rx*sinP,-ry*sinP,ry*cosP,cx,cy]};
							arc(gst, 0,0, 1, theta1, theta1+dtheta, fS==0);
							x=x2;  y=y2;
						}
						else console.log("Unknown SVG command "+cmd);
					}
				}
			}
		};
		return {  "cssMap":cssMap, "readTrnf":readTrnf, svgToPath:svgToPath, toPath:toPath };
	}(),
	
	


	"initHB": function(hurl,resp) {
	var codeLength = function(code) {
		var len=0;
		if     ((code&(0xffffffff-(1<< 7)+1))==0) {  len=1;  }
		else if((code&(0xffffffff-(1<<11)+1))==0) {  len=2;  }
		else if((code&(0xffffffff-(1<<16)+1))==0) {  len=3;  }
		else if((code&(0xffffffff-(1<<21)+1))==0) {  len=4;  }
		return len;
	}
	var te = new window["TextEncoder"]("utf8");
	
	fetch(hurl)
		.then(function (x  ) { return x["arrayBuffer"](); })
		.then(function (ab ) { return WebAssembly["instantiate"](ab); })
		.then(function (res) {
			console.log("HB ready");
			var exp = res["instance"]["exports"], mem=exp["memory"];
			mem["grow"](700); // each page is 64kb in size
			var heapu8  = new Uint8Array (mem.buffer);
			var u32 = new Uint32Array(mem.buffer);
			var i32 = new Int32Array (mem.buffer);
			var __lastFnt, blob,blobPtr,face,font;
			
			Typr["U"]["shapeHB"] = (function () {
				
				var toJson = function (ptr) {
					var length = exp["hb_buffer_get_length"](ptr);
					var result = [];
					var iPtr32 = exp["hb_buffer_get_glyph_infos"](ptr, 0) >>>2;
					var pPtr32 = exp["hb_buffer_get_glyph_positions"](ptr, 0) >>>2;
					for(var i=0; i<length; ++i) {
						var a=iPtr32+i*5, b=pPtr32+i*5;
					  result.push({
						"g" : u32[a + 0],
						"cl": u32[a + 2],
						"ax": i32[b + 0],
						"ay": i32[b + 1],
						"dx": i32[b + 2],
						"dy": i32[b + 3]
					  });
					}
					return result;
				}
				return function (fnt, str, ltr) {
					var fdata = fnt["_data"], fn = fnt["name"]["postScriptName"];
					
					if(__lastFnt!=fn) {
						if(blob!=null) {  
							exp["hb_blob_destroy"](blob);
							exp["free"](blobPtr);
							exp["hb_face_destroy"](face);
							exp["hb_font_destroy"](font);
						}
						blobPtr = exp["malloc"](fdata.byteLength);  heapu8.set(fdata, blobPtr);
						blob = exp["hb_blob_create"](blobPtr, fdata.byteLength, 2, 0, 0);
						face = exp["hb_face_create"](blob, 0);
						font = exp["hb_font_create"](face)
						__lastFnt = fn;
					}
					
					var buffer = exp["hb_buffer_create"]();
					var bytes = te["encode"](str);
					var len=bytes.length, strp = exp["malloc"](len);  heapu8.set(bytes, strp);
					exp["hb_buffer_add_utf8"](buffer, strp, len, 0, len);
					exp["free"](strp);
					
					exp["hb_buffer_set_direction"](buffer,ltr?4:5);
					exp["hb_buffer_guess_segment_properties"](buffer);
					exp["hb_shape"](font, buffer, 0, 0);
					var json = toJson(buffer)//buffer["json"]();
					exp["hb_buffer_destroy"](buffer);
					
					var arr = json.slice(0);  if(!ltr) arr.reverse();
					var ci=0, bi=0;  // character index, binary index
					for(var i=1; i<arr.length; i++) {
						var gl = arr[i], cl=gl["cl"];
						while(true) {
							var cpt = str.codePointAt(ci), cln = codeLength(cpt);
							if(bi+cln <=cl) {  bi+=cln;  ci += cpt<=0xffff ? 1 : 2;  }
							else break;
						}
						//while(bi+codeLength(str.charCodeAt(ci)) <=cl) {  bi+=codeLength(str.charCodeAt(ci));  ci++;  }
						gl["cl"]=ci;
					}
					return json;
				}
			}());
			resp();
		});	
	}
}