text: const UDOC = require('./UDOC').UDOC;
  const pako = require('pako');
  	
  	function FromPS ()
  	{
  	}
  	
  	FromPS.Parse = function(buff, genv)
  	{
  		buff = new Uint8Array(buff);
  		var off = 0;  while(!(buff[off]==37 && buff[off+1]==33)) off++;
  		var str = FromPS.B.readASCII(buff, off, buff.length-off);
  		var lines = str.split(/[\n\r]+/);
  		
  		var crds = null;
  		var epsv = null;
  		
  		for(var li=0; li<lines.length; li++)
  		{
  			var line = lines[li].trim();
  			if(line.charAt(0)=="%") {
  				while(line.charAt(0)=="%") line = line.slice(1);
  				var pts = line.split(":");
  				if(pts[0]=="BoundingBox")  crds = pts[1].trim().split(/[ ]+/).map(parseFloat); 
  				if(line.indexOf("!PS-Adobe-3.0 EPSF-3.0")!=-1) epsv=line;
  			}
  		}
  		
  		if(epsv==null || crds==null) crds = [0,0,595, 842];
  		
  		var os = [];	// operand stack
  		var ds = FromPS._getDictStack([],{});
  		var es = [{  typ:"file", val: {  buff:buff, off:off  }  }];	// execution stack
  		var gs = [];
  		var env = FromPS._getEnv(crds);
  		var time = Date.now();
  		var repeat = true;
  		while(repeat) repeat = FromPS.step(os, ds, es, gs, env, genv);
  		
  		if(env.pgOpen) genv.ShowPage();
  		genv.Done();
  		//FromPS.interpret(file, os, ds, es, [], gst, genv);
  		console.log(Date.now()-time);
  	}
  	FromPS._getDictStack = function(adefs, aprcs) {
  		var defs = [
  			"def","begin","end","currentfile","currentdict","known","version",
  			"currentpacking","setpacking","currentoverprint","setoverprint","currentglobal","setglobal",
  			"currentsystemparams","setsystemparams","currentuserparams","setuserparams","currentpagedevice","setpagedevice",
  			"currentflat",
  			"currentlinewidth","currentpoint","currentscreen","setscreen","currenthalftone",
  			"currentblackgeneration","currentundercolorremoval","currentcolortransfer",
  			"internaldict",
  			"dict","string","readstring","readhexstring","readline","getinterval","putinterval","token",
  			"array","aload","astore","length","maxlength","matrix","mark","counttomark","cleartomark","dictstack","countdictstack",
  			"makefont","scalefont","stringwidth",
  			
  			"setfont", "setgray","currentgray", "setrgbcolor","currentrgbcolor","sethsbcolor", "setlinewidth", "setstrokeadjust","setflat","setlinecap",
  			"setlinejoin","setmiterlimit","setdash",
  			"clip","eoclip","clippath","pathbbox",
  			"newpath", "stroke", "fill", "eofill", "closepath","flattenpath","showpage","print",
  			"moveto", "lineto", "curveto", "arc","arcn", 
  			"show","ashow","xshow","yshow","xyshow","widthshow","awidthshow","charpath",
  			"cshow",
  			"rmoveto","rlineto","rcurveto",
  			"translate","rotate","scale","concat","concatmatrix","invertmatrix","currentmatrix","defaultmatrix","setmatrix",
  			
  			"save","restore","gsave", "grestore","grestoreall",
  			"usertime","readtime",
  			"save", "restore","flush","flushfile","readonly","executeonly",
  			
  			"findresource","defineresource","undefineresource","image","imagemask","colorimage",
  			
  			"xcheck","status","cachestatus","setcachelimit","type",
  			
  			"if","ifelse","exec","stopped","dup","exch","copy","roll","index","pop","put","get","load","where","store","repeat","for","forall","loop","exit",
  			"bind",
  			"cvi","cvr","cvs","cvx","cvn","cvlit",
  			"add","sub","mul","div","idiv","bitshift","mod","exp","atan",
  			"neg","abs","floor","round","truncate","sqrt","ln","sin","cos",
  			"srand","rand","==","transform","itransform","dtransform","idtransform",
  			"eq","ge","gt","le","lt","ne",
  			"and","or","not",
  			"filter",
  			
  			"begincmap","endcmap", "begincodespacerange","endcodespacerange", "beginbfrange","endbfrange","beginbfchar","endbfchar"
  		].concat(adefs);
  		
  		var withCtx = ["image", "colorimage", "repeat", "for","forall","loop"];
  		for(var i=0; i<withCtx.length; i++) defs.push(withCtx[i]+"---");
  		
  		var prcs = FromPS.makeProcs({ 
  			"findfont"    : "/Font findresource",
  			"definefont"  : "/Font defineresource",
  			"undefinefont": "/Font undefineresource"
  		});
  		for(var p in aprcs) prcs[p] = aprcs[p];
  		
  		var systemdict = {}, globaldict = {}, userdict = {}, statusdict = {};
  		systemdict["systemdict"] = {typ:"dict", val:systemdict};
  		systemdict["globaldict"] = {typ:"dict", val:globaldict};
  		systemdict["userdict"  ] = {typ:"dict", val:userdict  };
  		systemdict["statusdict"] = {typ:"dict", val:statusdict};
  		systemdict["$error"    ] = {typ:"dict", val:{}};
  		systemdict["errordict" ] = {typ:"dict", val:FromPS.makeProcs({"handleerror":""})};
  		systemdict["null"]   = {typ:"null", val:null};
  		
  		for(var i=0; i<defs.length; i++) systemdict[defs[i]] = {  typ:"operator", val:defs[i]  };
  		for(var p in prcs)               systemdict[p] = prcs[p];
  		
  		return [ systemdict,	globaldict, userdict ];  // dictionary stack
  	}
  	FromPS._getEnv   = function(crds) {
  		return {
  			bb:crds,
  			gst : UDOC.getState(crds),
  			packing:false, overprint:false, global:false, systemparams:{"MaxPatternCache":5000},userparams:{},pagedevice:{},
  			cmnum:0, fnt:null,
  			res:{},
  			pgOpen:false,
  			funs: FromPS.makeProcs({ 
  				"blackgeneration"   : "",
  				"undercolorremoval" : "pop 0"
  			})
  		}
  	}
  	
  	FromPS.makeProcs = function(prcs) {
  		var out = {};
  		for(var p in prcs) {
  			var pts = prcs[p].replace(/  +/g, " ").split(" ");
  			out[p] = {typ:"procedure", val:[]};
  			for(var i=0; i<pts.length; i++) out[p].val.push({typ:"name",val:pts[i]});
  		}
  		return out;
  	}
  	
  	FromPS.addProc = function(obj, es) {  
  		if(obj.val.length==0) return;
  		if(obj.off!=null && obj.off!=obj.val.length)   es.push({typ:"procedure", val:obj.val, off:0}); 
  		else {  obj.off=0;  es.push( obj );  }
  	}
  	
  	FromPS._f32 = new Float32Array(1);
  	FromPS.step = function(os, ds, es, gs, env, genv, Oprs) 
  	{
  		var otime = Date.now(), f32 = FromPS._f32;
  		var getToken = FromPS.getToken;
  		
  		var gst = env.gst;
  		
  		var tok = getToken(es, ds);  if(tok==null) return false;
  		var typ = tok.typ, val = tok.val;
  		
  		
  		
  		//console.log(tok, os.slice(0));
  		
  		//for(var i=0; i<os.length; i++)  if(os[i].typ=="real" && isNaN(os[i].val)) throw "e";
  		
  		/*ocnt++;
  		//if(ocnt>2*lcnt) {  lcnt=ocnt;  console.log(ocnt, os.length, file.stk.length);  };
  		if(ocnt>8000000) {  
  			for(var key in opoc) if(opoc[key][1]<1000) delete opoc[key];
  			console.log(Date.now()-otime, opoc);  throw "e";  
  		} */
  		
  		if(["integer","real","dict","boolean","string","array","procedure","null"].indexOf(typ)!=-1) {  os.push(tok);  return true;  }
  	
  		if(typ!="name" && typ!="operator") throw "e";
  		
  		//if(opoc[val]==null) opoc[val]=[0,0];  opoc[val][0]++;  opoc[val][1]=ocnt;
  			
  		if(val.charAt(0)=="/") {
  			if(val.charAt(1)=="/") throw "e";
  			else os.push(tok);
  		}
  		else if(val=="{") {
  			var ars = [], car = {typ:"procedure", val:[] };
  			
  			var ltok=getToken(es,ds); 
  			while(true) {  
  				if     (ltok.val=="{") {  var ncr = {typ:"procedure", val:[]};  car.val.push(ncr);  ars.push(car);  car=ncr;  }
  				else if(ltok.val=="}") {  if(ars.length==0) break;  car = ars.pop();  }		
  				else car.val.push(ltok);
  				ltok=getToken(es,ds);  
  			}
  			os.push( car );
  		}
  		else if(val=="[" || val=="<<") os.push( {typ:"mark"} );
  		else if(val=="]" || val==">>") {
  			var arr = [];  while(os.length!=0) {  var o=os.pop();  if(o.typ=="mark") break;  arr.push(o);  }
  			arr.reverse(); 
  			if(val=="]") os.push( {typ:"array", val:arr } ); 
  			else { 
  				var ndct = {};  for(var i=0; i<arr.length; i+=2) ndct[arr[i].val.slice(1)] = arr[i+1];
  				os.push( {typ:"dict", val:ndct } ); 
  			}
  		}
  		else {
  			var obj = FromPS.getFromStacks(val, ds);
  			
  			//if(val=="rl^") {  console.log(val, os.slice(0));    }
  			if(obj==null) {  console.log("unknown operator", val, os, ds);  throw "e";  }
  			else if(obj.typ=="procedure") FromPS.addProc(obj, es); //{  obj.off=0;  es.push(obj);  }
  			/*
  			else if(op.typ=="string") {
  				var prc=[], sdta = {buff:op.val, off:0, stk:[]}, tk = getToken(sdta);  while(tk!=null) {  prc.push(tk);  tk=getToken(sdta);  }
  				FromPS.addProcedure(prc, file);
  			}*/
  			else if(["array","string","dict","null","integer","real","boolean","state","font","name"].indexOf(obj.typ)!=-1) os.push(obj);
  			else if(obj.typ=="operator")
  			{
  				var op = obj.val;
  				//console.log(op);
  				//if(omap[op]) op = omap[op];
  				
  				if(op=="def") {  var nv = os.pop(), nn = os.pop();  nn=nn.val.slice(1);  ds[ds.length-1][nn] = nv;  }
  				else if(op=="internaldict") {  var l=os.pop().val;  os.push({typ:"dict"  , val:{}});  }
  				else if(op=="dict"   ) {  var l=os.pop().val;  os.push({typ:"dict"  , val:{}, maxl:l });  }
  				else if(op=="string" ) {  var l=os.pop().val;  os.push({typ:"string", val:new Array(l) });  }
  				else if(op=="readstring" || op=="readhexstring") {
  					var str = os.pop(), l=str.val.length, fl = os.pop().val;  //console.log(op, str);  throw "e";
  					if(op=="readstring") {  for(var i=0; i<l; i++) str.val[i]=fl.buff[fl.off+i];   fl.off+=l;  }
  					else {
  						var nv = FromPS.readHex(fl, l);
  						for(var i=0; i<nv.length; i++) str.val[i]=nv[i];
  					}
  					os.push(str, {typ:"boolean",val:true});
  				}
  				else if(op=="readline") {
  					var str = os.pop(), fl = os.pop().val, i=0;
  					if(FromPS.isEOL(fl.buff[fl.off])) fl.off++;
  					while(true)  {
  						var cc = fl.buff[fl.off];  fl.off++;
  						if(FromPS.isEOL(cc)) break;
  						str.val[i]=cc;   i++;
  					}
  					if(i<str.val.length && str.val[i]!=null) str.val[i]=null;
  					os.push(str, {typ:"boolean",val:true});
  				}
  				else if(op=="getinterval") {
  					var cnt = os.pop().val, idx = os.pop().val, src = os.pop(), out=[];
  					if(src.typ=="string") for(var i=0; i<cnt; i++) out.push(src.val[idx+i]);
  					else throw "e";
  					//console.log(idx,cnt,out.slice(0));
  					os.push({typ:src.typ, val:out});
  				}
  				else if(op=="putinterval") {
  					var src=os.pop(), idx=os.pop().val, tgt=os.pop();  //console.log(tgt,idx,src);
  					if(idx+src.val.length>=tgt.val.length) {}  //throw "e";
  					else if(src.typ=="string") for(var i=0; i<src.val.length; i++) tgt.val[idx+i] = src.val[i];
  					else throw "e";
  					//console.log(src.val, tgt.val, idx);  throw "e";
  				}
  				else if(op=="token") {
  					var src = os.pop();  if(src.typ!="string") throw "e";
  					var arr = [];
  					for(var i=0; i<src.val.length; i++) {  var bv=src.val[i];  if(bv==null) break;  arr.push(bv);  }
  					var nfl = {  buff:new Uint8Array(arr), off:0   }, tok = getToken([{typ:"file",val:nfl}], ds);
  					var ns = [];  for(var i=nfl.off; i<arr.length; i++) ns.push(arr[i]);
  					os.push({typ:"string",val:ns}, tok, {typ:"boolean",val:true});
  				}
  				else if(op=="array"  ) {  var l=os.pop().val;  os.push({typ:"array" , val:new Array(l) });  }
  				else if(op=="aload"){
  					var o = os.pop(), arr = o.val;
  					for(var i=0; i<arr.length; i++) os.push(arr[i]);
  					os.push(o);
  				}
  				else if(op=="astore") {
  					var o=os.pop(), arr = o.val;  //console.log(arr.length);  throw "e";
  					for(var i=0; i<arr.length; i++) arr[arr.length-1-i]=os.pop();
  					os.push(o);
  				}
  				else if(op=="length" ) {
  					var o = os.pop(), typ=o.typ, l=0;
  					if     (typ=="array"    ) l = o.val.length;
  					else if(typ=="procedure") l = o.val.length;
  					else if(typ=="dict"     ) l = Object.keys(o.val).length;
  					else if(typ=="string"   ) l = o.val.length;
  					else {  console.log(o);  throw "e";  }
  					os.push({typ:"integer",val:l});
  				}
  				else if(op=="maxlength") {  var d=os.pop();  os.push({typ:"integer",val:d.maxl});  }
  				else if(op=="matrix" ) {  os.push({typ:"array", val:FromPS.makeArr([1,0,0,1,0,0],"real") });  }
  				else if(op=="mark"   ) {  os.push({typ:"mark"});  }
  				else if(op=="counttomark" || op=="cleartomark") {
  					var mi = 0;  while(mi<os.length && os[os.length-1-mi].typ!="mark")mi++;
  					if(op=="cleartomark") for(var i=0; i<mi+1; i++) os.pop();
  					else os.push({typ:"integer",val:mi});
  				}
  				else if(op=="dictstack") {
  					var arr = os.pop();
  					for(var i=0; i<ds.length; i++) arr.val[i] = {typ:"dict",val:ds[i]};
  					os.push(arr);
  				}
  				else if(op=="countdictstack") {
  					var n=0;  for(var i=0; i<os.length; i++) if(os[i].typ=="dict") n++;
  					os.push({typ:"integer",val:n});
  				}
  				else if(op=="begin") {  var o = os.pop(), dct=o.val;   if(dct==null || o.typ!="dict") {  console.log(o, ds);  throw "e";  }  ds.push(dct);  }
  				else if(op=="end"  ) {  ds.pop();  }
  				else if(op=="currentfile") {  var file;  for(var i=es.length-1; i>=0; i--) if(es[i].typ=="file")file=es[i];  os.push(file);  }
  				else if(op=="currentdict") {  var dct=ds[ds.length-1];  os.push({typ:"dict", val:dct});  }
  				else if(op=="known") {  var key=os.pop().val.slice(1), dct=os.pop().val;  os.push({typ:"boolean",val:dct[key]!=null});  }
  				else if(op=="version") {  os.push({typ:"string", val:[51]});  } // "3"
  				else if(["currentpacking","currentoverprint","currentglobal","currentsystemparams","currentuserparams","currentpagedevice"].indexOf(op)!=-1) {  var nv = env[op.slice(7)];
  					os.push({typ:(typeof nv=="boolean")?"boolean":"dict",val:nv});  
  				}
  				else if(["setpacking","setoverprint","setglobal","setsystemparams","setuserparams","setpagedevice"].indexOf(op)!=-1) {  env[op.slice(3)] = os.pop().val;  }
  				else if(op=="currentflat"   ) {  os.push({typ:"real",val:1});  }
  				else if(op=="currentlinewidth") {  os.push({typ:"real",val:gst.lwidth});  }
  				else if(op=="currentpoint"   ) {  var im=gst.ctm.slice(0);  UDOC.M.invert(im);  var p=UDOC.M.multPoint(im,gst.cpos);  
  								os.push({typ:"real",val:p[0]}, {typ:"real",val:p[1]});  }
  				else if(op=="currentscreen"  ) {  os.push({typ:"int",val:60}, {typ:"real",val:0},{typ:"real",val:0});  }
  				else if(op=="setscreen"      ) {  os.pop();  os.pop();  os.pop();  }
  				else if(op=="currenthalftone") {  os.push({typ:"dict",val:{}});  }
  				else if(op=="currentblackgeneration" || op=="currentundercolorremoval") {  os.push(env.funs[op.slice(7)]);  }
  				else if(op=="currentcolortransfer") {  for(var i=0; i<4; i++) os.push(env.funs["blackgeneration"]);  }
  				else if(op=="findresource")
  				{
  					var cat = os.pop().val.slice(1), key = os.pop().val.slice(1), rs;
  					if     (cat=="Font"    ) {  rs = {typ:"font",val:UDOC.getFont()};  rs.val.Tf=key;  }
  					else if(cat=="ProcSet" ) rs = {typ:"dict",val:{}};
  					else if(cat=="Category") rs = {typ:"dict",val:{}};
  					else throw("Unknown resource category: "+cat+","+ key);
  					os.push(rs);
  				}
  				else if(op=="defineresource") {
  					var cat = os.pop().val.slice(1), ins = os.pop().val, key = os.pop().val.slice(1);
  					if(env.res[cat]==null) env.res[cat]={};
  					env.res[cat][key]=ins;
  					os.push(ins);
  				}
  				else if(op=="undefineresource") {
  					var cat = os.pop().val.slice(1), key = os.pop().val.slice(1);
  					if(env.res[cat]!=null) delete env.res[cat];
  				}
  				else if(op=="image" || op=="colorimage") {
  					var ncomp = 1, multi = false;
  					if(op=="colorimage") {  ncomp = os.pop().val;  multi = os.pop().val;  }
  					var src0, src1, src2;  
  					if(multi) {  src2=os.pop();  src1=os.pop();  src0=os.pop();  }  else src0 = os.pop();
  					var mat = FromPS.readArr(os.pop().val), bpc = os.pop().val, h = os.pop().val, w = os.pop().val;
  					
  					if(ncomp!=3) throw "unsupported number of channels "+ncomp;
  					if(bpc!=8) throw "unsupported bits per channel: "+bpc;
  					
  					var img = new Uint8Array(w*h*4);  for(var i=0; i<img.length; i++) img[i]=255;
  					
  					es.push({typ:"name",val:op+"---",ctx:[w,h,bpc,mat, ncomp,multi,img,0, src0,src1,src2]});
  					FromPS.addProc(src0, es);  
  					if(multi) {  FromPS.addProc(src1, es);  FromPS.addProc(src2, es);  }
  					//console.log(w,h,bpc,mat, src0,src1,src2, multi, ncomp);  throw "e";
  				}
  				else if(op=="image---" || op=="colorimage---") {
  					var prm = tok.ctx, w=prm[0], h=prm[1], bpc=prm[2], mat=prm[3], ncomp=prm[4], multi=prm[5], img=prm[6], pind=prm[7];
  					var src0 = prm[8], src1 = prm[9], src2=prm[10], dlen = 0;
  					if(multi)
  						for(i=0; i<3; i++){  var row = os.pop().val;  dlen = row.length;
  							for(var j=0; j<dlen; j++) img[(pind+j)*4 + 2-i] = row[j];
  						}
  					else  {
  						var row = os.pop().val;  dlen = Math.floor(row.length/3);
  						if(row[0]==null) {  console.log(ds);  throw "e";  }
  						for(var j=0; j<dlen; j++) {  var tj=j*3, qj=(pind+j)*4;  img[qj+0]=row[tj+0];  img[qj+1]=row[tj+1];  img[qj+2]=row[tj+2];  }
  					}
  					pind += dlen;
  					FromPS.checkPageStarted(env,genv);
  					if(pind==w*h) genv.PutImage(gst, img, w, h);
  					else {  prm[7]=pind;  es.push(tok); 
  						FromPS.addProc(src0, es);  
  						if(multi) {  FromPS.addProc(src1, es);  FromPS.addProc(src2, es);  }
  					}
  				}
  				else if(op=="makefont") {
  					var mt = FromPS.readArr(os.pop().val), fnt = JSON.parse(JSON.stringify(os.pop()));
  					UDOC.M.concat(fnt.val.Tm, mt);  os.push(fnt);
  				}
  				else if(op=="scalefont") {
  					var sc = os.pop().val, fnt = os.pop();  //console.log(ds);
  					fnt.val.Tfs *= sc;  os.push(fnt);
  				}
  				else if(op=="stringwidth") {
  					var str=os.pop().val;
  					var sc = UDOC.M.getScale(gst.font.Tm) / UDOC.M.getScale(gst.ctm);
  					//console.log(FromPS.getString(str), gst.font, 0.6*sc*str.length);
  					os.push({typ:"real",val:0.6*sc*str.length}, {typ:"real",val:sc});
  				}
  				else if(op=="setfont"     ) gst.font = os.pop().val;
  				else if(op=="setlinewidth") gst.lwidth = os.pop().val;
  				else if(op=="setstrokeadjust") gst.SA = os.pop().val;
  				else if(op=="setlinecap") gst.lcap = os.pop().val;
  				else if(op=="setlinejoin") gst.ljoin = os.pop().val;
  				else if(op=="setmiterlimit") gst.mlimit = os.pop().val;
  				else if(op=="setflat") gst.dd.flat=os.pop();
  				else if(op=="setdash"     ) {  gst.doff=os.pop().val;  gst.dash = FromPS.readArr(os.pop().val);  }
  				else if(op=="show"||op=="ashow"||op=="xshow"||op=="yshow"||op=="xyshow"||op=="widthshow"||op=="awidthshow"||op=="charpath") {  
  					if(op=="charpath" || op=="xshow" || op=="xyshow" || op=="yshow") os.pop();
  					var sar = os.pop().val, str=FromPS.readStr(sar); 
  					if(op=="awidthshow") {  os.pop();  os.pop();  os.pop();  os.pop(); }
  					if(op=="widthshow" ) {  os.pop();  os.pop();  os.pop();  }
  					if(op=="ashow"     ) {  os.pop();  os.pop();  }
  					var om = gst.ctm;  gst.ctm = om.slice(0);  gst.ctm[4]=gst.cpos[0];  gst.ctm[5]=gst.cpos[1];//UDOC.M.translate(gst.ctm,gst.cpos[0],gst.cpos[1]);
  					FromPS.checkPageStarted(env,genv);
  					genv.PutText(gst, str, str.length*0.55);  gst.cpos[0] += str.length*UDOC.M.getScale(om)*gst.font.Tfs*0.55;  //console.log(str, gst.font.Tfs);
  					gst.ctm = om;
  				}
  				else if(op=="cshow") {  os.pop();  os.pop();  }
  				else if(op=="setgray"    ) {  var g=FromPS.nrm(os.pop().val);  gst.colr = gst.COLR = [g,g,g];  }
  				else if(op=="currentgray") {  os.push({typ:"real", val:(gst.colr[0]+gst.colr[1]+gst.colr[2])/3});  }
  				else if(op=="setrgbcolor") {  var b=os.pop().val,g=os.pop().val,r=os.pop().val;  gst.colr = gst.COLR = [FromPS.nrm(r),FromPS.nrm(g),FromPS.nrm(b)];  }
  				else if(op=="currentrgbcolor") {  for(var i=0; i<3; i++) os.push({typ:"real", val:gst.colr[i]});  }
  				else if(op=="sethsbcolor") {
  					var v=os.pop().val,s=os.pop().val,h=os.pop().val;
  					var r, g, b, i, f, p, q, t;
  					i = Math.floor(h * 6);
  					f = h * 6 - i;
  					p = v * (1 - s);
  					q = v * (1 - f * s);
  					t = v * (1 - (1 - f) * s);
  					switch (i % 6) {
  						case 0: r = v, g = t, b = p; break;
  						case 1: r = q, g = v, b = p; break;
  						case 2: r = p, g = v, b = t; break;
  						case 3: r = p, g = q, b = v; break;
  						case 4: r = t, g = p, b = v; break;
  						case 5: r = v, g = p, b = q; break;
  					}
  					gst.colr = gst.COLR = [FromPS.nrm(r),FromPS.nrm(g),FromPS.nrm(b)];
  				}
  				else if(op=="clip" || op=="eoclip") {  
  					var bbN = UDOC.G.getBB(gst.pth .crds);
  					var bbO = UDOC.G.getBB(gst.cpth.crds);
  					if     (UDOC.G.isBox(gst.pth, bbN) && UDOC.G.insideBox(bbO,bbN)) {  }  // clipping with a box, that contains a current clip path
  					else if(UDOC.G.isBox(gst.cpth,bbO) && UDOC.G.insideBox(bbN,bbO)) {  gst.cpth = JSON.parse(JSON.stringify(gst.pth));  }
  					else {
  						var p0 = UDOC.G.toPoly(gst.pth), p1 = UDOC.G.toPoly(gst.cpth);
  						if(p0 && p1) {
  							//console.log(gst.pth, gst.cpth);
  							var p = UDOC.G.polyClip(p0, p1);
  							//console.log(p0, p1, p);
  							if(p.length!=0) gst.cpth = UDOC.G.fromPoly(p);
  							else console.log("strange intersection of polygons");
  						}
  						else {
  							// do an advanced shape - shape intersection
  							//console.log("replacing clipping path");
  							//console.log(bbO, gst.cpth);
  							//console.log(bbN, gst.pth );
  							gst.cpth = JSON.parse(JSON.stringify(gst.pth ));  
  						}
  					}
  				}
  				else if(op=="clippath" ) {  gst.pth  = JSON.parse(JSON.stringify(gst.cpth));  }
  				else if(op=="pathbbox" ) {
  					var ps = gst.pth.crds;
  					var bb = UDOC.G.getBB(ps);
  					ps = [bb[0],bb[1], bb[2],bb[1],   bb[0],bb[3], bb[2],bb[3]];
  					var im = gst.ctm.slice(0);  UDOC.M.invert(im);  UDOC.M.multArray(im,ps);
  					bb = UDOC.G.getBB(ps);
  					f32[0]=bb[0];  bb[0]=f32[0];  f32[0]=bb[1];  bb[1]=f32[0];  f32[0]=bb[2];  bb[2]=f32[0];  f32[0]=bb[3];  bb[3]=f32[0];
  					bb = FromPS.makeArr(bb,"real");
  					os.push(bb[0],bb[1],bb[2],bb[3]);
  				}
  				else if(op=="newpath"  ) UDOC.G.newPath(gst);
  				else if(op=="stroke"              ) {  FromPS.checkPageStarted(env,genv);  genv.Stroke(gst);  UDOC.G.newPath(gst);  }
  				else if(op=="fill" || op=="eofill") {  FromPS.checkPageStarted(env,genv);  genv.Fill(gst, op=="eofill");    UDOC.G.newPath(gst);  }
  				else if(op=="closepath") UDOC.G.closePath(gst);
  				else if(op=="flattenpath") {}
  				else if(op=="showpage" ) {  FromPS.checkPageStarted(env,genv);  genv.ShowPage ();  var ofnt=gst.font;  gst = env.gst = UDOC.getState(env.bb);  gst.font=ofnt;  env.pgOpen = false;  }
  				else if(op=="print"    ) {  var sar = os.pop().val, str=FromPS.readStr(sar);  genv.Print(str);  }
  				else if(op=="moveto"  || op=="lineto" ) {
  					var y = os.pop().val, x = os.pop().val;
  					if(op=="moveto" ) UDOC.G.moveTo(gst,x,y);  else UDOC.G.lineTo(gst,x,y);
  				}
  				else if(op=="rmoveto" || op=="rlineto") {
  					var y = os.pop().val, x = os.pop().val;
  					var im=gst.ctm.slice(0);  UDOC.M.invert(im);  var p = UDOC.M.multPoint(im, gst.cpos);
  					y+=p[1];  x+=p[0];
  					if(op=="rmoveto") UDOC.G.moveTo(gst,x,y);  else UDOC.G.lineTo(gst,x,y);
  				}
  				else if(op=="curveto") {
  					var y3=os.pop().val, x3=os.pop().val, y2=os.pop().val, x2=os.pop().val, y1=os.pop().val, x1=os.pop().val;
  					UDOC.G.curveTo(gst,x1,y1,x2,y2,x3,y3);
  				}
  				else if(op=="arc" || op=="arcn") {
  					var a2 = os.pop().val, a1 = os.pop().val, r = os.pop().val, y = os.pop().val, x = os.pop().val;
  					//if(op=="arcn") a2=-a2;
  					UDOC.G.arc(gst,x,y,r,a1*Math.PI/180,a2*Math.PI/180, op=="arcn");
  				}
  				
  				else if(["translate","scale","rotate","concat"].indexOf(op)!=-1) {
  					var v = os.pop(), m, x, y;
  					if(v.typ=="array") {  m = FromPS.readArr(v.val);  y = os.pop().val;  }
  					else  {  m = [1,0,0,1,0,0];  y = v.val;  }
  					
  					if(op=="translate" || op=="scale") x = os.pop().val;
  					
  					if(op=="translate") UDOC.M.translate(m,x,y);
  					if(op=="scale"    ) UDOC.M.scale    (m,x,y);
  					if(op=="rotate"   ) UDOC.M.rotate   (m,-y*Math.PI/180);
  					if(op=="concat"   ) UDOC.M.concat   (m,y);
  					
  					if(v.typ=="array") os.push({typ:"array",val:FromPS.makeArr(m,"real")});
  					else {  UDOC.M.concat(m,gst.ctm);  gst.ctm = m;  }
  				}
  				else if(op=="concatmatrix") { var rA = FromPS.readArr;
  					var m3 = rA(os.pop().val), m2 = rA(os.pop().val), m1 = rA(os.pop().val);
  					var m = m1.slice(0);  UDOC.M.concat(m, m2);  m = FromPS.makeArr(m, "real");
  					os.push({typ:"array",val:m});
  				}
  				else if(op=="invertmatrix") { var rA = FromPS.readArr;
  					var m2 = rA(os.pop().val), m1 = rA(os.pop().val);
  					var m = m1.slice(0);  UDOC.M.invert(m);  m = FromPS.makeArr(m, "real");
  					os.push({typ:"array",val:m});
  				}
  				else if(op=="currentmatrix" || op=="defaultmatrix") {
  					var m = os.pop(), cm = FromPS.makeArr(op=="currentmatrix"?gst.ctm:[1,0,0,1,0,0],"real");
  					for(var i=0; i<6; i++) m.val[i]=cm[i];   os.push(m);
  				}
  				else if(op=="setmatrix") {
  					gst.ctm = FromPS.readArr(os.pop().val);
  				}
  				else if(op=="cvi") {
  					var o = os.pop(), v=o.val, out = 0;
  					if     (o.typ=="real"   ) out = Math.round(v);
  					else if(o.typ=="integer") out = v;
  					else throw "unknown type "+o.typ;
  					os.push({typ:"integer",val:out});
  				}
  				else if(op=="cvr") {
  					var o = os.pop(), v=o.val, out = 0;
  					if     (o.typ=="real"   ) out = v;
  					else if(o.typ=="integer") out = v;
  					else if(o.typ=="string" ) out = parseFloat(FromPS.readStr(v));
  					else throw "unknown type "+o.typ;
  					os.push({typ:"real",val:out});
  				}
  				else if(op=="cvs") {
  					var str = os.pop(), any = os.pop(), nv = "";  str.val=[];  os.push(str);
  					if(any.typ=="real" || any.typ=="integer") {
  						if(Math.abs(Math.round(any.val)-any.val)<1e-6) nv=Math.round(any.val)+".0";
  						else nv = (Math.round(any.val*1000000)/1000000).toString();
  					}
  					else throw "unknown var type: "+any.typ;
  					for(var i=0; i<nv.length; i++) str.val[i]=nv.charCodeAt(i);
  				}
  				else if(op=="cvx") {
  					var o = os.pop();
  					//if(o.typ=="array") o.typ="procedure";
  					//else if(o.typ=="name" && o.val.charAt(0)=="/") o = {typ:"name",val:o.val.slice(1)};
  					//else {  console.log(o);  throw "e";  }
  					os.push(o);
  				}
  				else if(op=="cvn") {
  					os.push({typ:"name",val:FromPS.readStr(os.pop().val)});
  				}
  				else if(op=="cvlit") {}
  				else if(["add","sub","mul","div","idiv","bitshift","mod","exp","atan"].indexOf(op)!=-1) {
  					var o1 = os.pop(), o0 = os.pop(), v0=o0.val, v1=o1.val, out = 0, otp = "";
  					if(op=="add" || op=="sub" || op=="mul") otp = (o0.typ=="real" || o1.typ=="real") ? "real" : "integer";
  					else if(op=="div" || op=="atan" || op=="exp") otp = "real";
  					else if(op=="mod" || op=="idiv" || op=="bitshift") otp = "integer";
  					
  					if(o0.typ=="real") {  f32[0]=v0;  v0=f32[0];  }
  					if(o1.typ=="real") {  f32[0]=v1;  v1=f32[0];  }
  					
  					if(op=="add") out = v0+v1;
  					if(op=="sub") out = v0-v1;
  					if(op=="mul") out = v0*v1;
  					if(op=="div") out = v0/v1;
  					if(op=="idiv")out = ~~(v0/v1);
  					if(op=="bitshift") out = v1>0 ? (v0<<v1) : (v0>>>(-v1));
  					if(op=="mod") out = v0%v1;
  					if(op=="exp") out = Math.pow(v0, v1);
  					if(op=="atan")out = Math.atan2(v0, v1)*180/Math.PI;
  					
  					if(otp=="real") {  f32[0]=out;  out=f32[0];  }
  					os.push({ typ:otp, val:out });
  				}
  				else if(["neg","abs","floor","round","truncate","sqrt","ln","sin","cos"].indexOf(op)!=-1) {
  					var o0 = os.pop(), v0=o0.val, out = 0, otp = "";
  					if(op=="neg" || op=="abs" || op=="truncate") otp=o0.typ;
  					else if(op=="round" || op=="floor") otp="integer";
  					else if(op=="sqrt" || op=="sin" || op=="cos" || op=="ln") otp="real";
  					
  					if(o0.typ=="real") {  f32[0]=v0;  v0=f32[0];  }
  					
  					if(op=="neg" ) out = -v0;
  					if(op=="abs" ) out = Math.abs(v0);
  					if(op=="floor")out = Math.floor(v0);
  					if(op=="round")out = Math.round(v0);
  					if(op=="truncate") out = Math.trunc(v0);
  					if(op=="sqrt") out = Math.sqrt(v0);
  					if(op=="ln"  ) out = Math.log(v0);
  					if(op=="sin" ) out = Math.sin(v0*Math.PI/180);
  					if(op=="cos" ) out = Math.cos(v0*Math.PI/180);
  					
  					if(op=="ln" && v0<=0)  throw "e";
  					
  					if(otp=="real") {  f32[0]=out;  out=f32[0];  }
  					
  					os.push({typ:otp, val:out});
  				}
  				else if(["eq","ge","gt","le","lt","ne"].indexOf(op)!=-1) {
  					var o1=os.pop(), o0=os.pop(), v0=o0.val, v1=o1.val, out=false;
  					if(op=="eq") out=v0==v1;
  					if(op=="ge") out=v0>=v1;
  					if(op=="gt") out=v0> v1;
  					if(op=="le") out=v0<=v1;
  					if(op=="lt") out=v0< v1;
  					if(op=="ne") out=v0!=v1;
  					os.push({typ:"boolean",val:out});
  				}
  				else if(["and","or"].indexOf(op)!=-1) {
  					var b2 = os.pop(), b1 = os.pop(), v1=b1.val, v2 = b2.val, ints=(b1.typ=="integer"), out;
  					if(op=="and") out = ints ? (v1&v2) : (v1&&v2);
  					if(op=="or" ) out = ints ? (v1|v2) : (v1||v2);
  					os.push({typ:ints?"integer":"boolean", val:out});
  				}
  				else if(op=="not") {
  					var b=os.pop(), v=b.val, ints=b.typ=="integer";
  					var out = ints ? (~v) : (!v);
  					os.push({typ:ints?"integer":"boolean", val:out});
  				}
  				else if(op=="if") {
  					var proc = os.pop(), cnd = os.pop().val;  //console.log(cnd);
  					if(cnd) FromPS.addProc(proc, es);//FromPS.callProcedure(proc, file, os, ds, es, gs, gst, genv);
  				}
  				else if(op=="ifelse") {
  					var proc2 = os.pop(), proc1 = os.pop(), cnd = os.pop().val;
  					FromPS.addProc(cnd?proc1:proc2, es);
  				}
  				else if(op=="exec" || op=="stopped") {  var obj = os.pop();  
  					if(op=="stopped") FromPS.addProc({typ:"procedure",val:[{typ:"boolean", val:false}]},es);  //os.push({typ:"boolean", val:false});
  					
  					if(obj.typ=="procedure") FromPS.addProc(obj, es);  
  					else if(obj.typ=="name" || obj.typ=="operator" || obj.typ=="real" || obj.typ=="array") FromPS.addProc({typ:"procedure",val:[obj]},es);
  					else {  console.log(obj);  throw "unknown executable type: "+obj.typ;  }
  				}
  				else if(op=="dup" ) {  var v=os.pop();  os.push(v,v);  }
  				else if(op=="exch") {  os.push(os.pop(), os.pop());  }
  				else if(op=="copy") {
  					var n = os.pop();  //console.log(n);
  					if(n.typ=="integer") {  var els=[];  for(var i=0; i<n.val; i++) els[n.val-1-i] = os.pop();  
  						for(var i=0; i<n.val; i++) os.push(els[i]);  
  						for(var i=0; i<n.val; i++) os.push(els[i]);    }
  					else if(n.typ=="array") {
  						var m = os.pop().val;
  						for(var i=0; i<m.length; i++) {  n.val[i]=m[i];  if(m[i].val==null) {  console.log(ds);  throw "e"; }  }
  						os.push(n);
  					}
  					else if(n.typ=="dict") {
  						var m = os.pop().val;
  						for(var prp in m) {  n.val[prp]=m[prp];  }
  						os.push(n);
  					}
  					else throw "e";
  				}
  				else if(op=="roll") {  var j=os.pop().val, n = os.pop().val;
  					var els = [];  for(var i=0; i<n; i++) els.push(os.pop());  els.reverse();
  					j = (n+j)%n;
  					for(var i=0; i<j; i++) els.unshift(els.pop());
  					for(var i=0; i<n; i++) os.push(els[i]);
  				}
  				else if(op=="index") {  var n=os.pop().val;  os.push(os[os.length-1-n]);  }
  				else if(op=="transform" || op=="itransform" || op=="dtransform" || op=="idtransform") {
  					var m = os.pop(), y=0, x=0;  //console.log(m);
  					if(m.typ=="array") { m = FromPS.readArr(m.val);  y = os.pop().val;  }
  					else               { y = m.val;  m = gst.ctm.slice(0);  }
  					if(op=="itransform"||op=="idtransform") {  UDOC.M.invert(m);  }
  					x = os.pop().val;
  					var np = UDOC.M.multPoint(m, [x,y]);
  					if(op=="dtransform"||op=="idtransform") {  np[0]-=m[4];  np[1]-=m[5];  }
  					//if(isNaN(np[0])) {  console.log(m,gst.ctm.slice(0),x,y);  throw "e";  }
  					os.push({typ:"real",val:np[0]},{typ:"real",val:np[1]});
  				}
  				else if(op=="pop" || op=="srand" || op=="==" ) {  os.pop();  }
  				else if(op=="rand") {  os.push({typ:"integer",val:Math.floor(Math.random()*0x7fffffff)});  }
  				else if(op=="put" ) {  
  					var val=os.pop(), o=os.pop(), obj=os.pop(), otp=obj.typ;  //console.log(obj,o,val);  //throw "e";
  					if(otp=="array") obj.val[o.val] = val;
  					else if(otp=="dict")  obj.val[o.val.slice(1)]=val;
  					else if(otp=="string") obj.val[o.val] = val.val;
  					else throw otp+" e";
  					//.val.slice(1), obj=os.pop();  obj.val[key]=obj.typ=="string" ? val.val : val;  
  				}
  				else if(op=="get" ) {  
  					var o=os.pop(), obj=os.pop(), otp=obj.typ; //  console.log(o, obj);
  					if     (otp=="string") os.push({typ:"integer",val:obj.val[o.val]}); 
  					else if(otp=="array" ) os.push(obj.val[o.val]);
  					else if(otp=="dict"  ) {var v =obj.val[o.val.slice(1)];  /*if(v==null) {  console.log(obj.val, o.val);  throw "e";  }*/  os.push(v);  }
  					else throw "getting from unknown type "+  obj.typ;  //os.push(obj.val[key]);  
  				}
  				else if(op=="load") {  var key=os.pop().val.slice(1), val = FromPS.getFromStacks(key, ds);  
  					if(val==null) {  console.log(key, ds);  throw "e";  }  os.push(val);  }
  				else if(op=="where"){  var key=os.pop().val.slice(1), dct=FromPS.where(key,ds);   //console.log(dct);
  					if(dct!=null) os.push({typ:"dict",val:dct});  os.push({typ:"boolean",val:dct!=null});  }
  				else if(op=="store"){
  					var val=os.pop(), key=os.pop().val.slice(1), dct=FromPS.where(key,ds);   //console.log(dct, key);  throw "e";
  					if(dct==null) dct=ds[ds.length-1];  dct[key]=val;  }
  				else if(op=="repeat" ) {
  					var proc=os.pop(), intg=os.pop().val;
  					es.push({typ:"name",val:op+"---", ctx:{ proc:proc, cur:0, cnt:intg }});
  				}
  				else if(op=="repeat---") {
  					var ctx = tok.ctx;
  					if(ctx.cur<ctx.cnt) {  es.push(tok);  FromPS.addProc(ctx.proc, es);  ctx.cur++;  }
  				}
  				else if(op=="for" ) {
  					var proc=os.pop(), liV=os.pop(), icV=os.pop(), itV=os.pop();
  					es.push({typ:"name",val:op+"---", ctx:{  proc:proc, isInt:(itV.typ=="integer" && icV.typ=="integer"), 
  								init:itV.val, inc:icV.val, limit:liV.val  }});
  				}
  				else if(op=="for---") {
  					var ctx = tok.ctx;
  					if(ctx.isInt) {
  						if((ctx.inc>0 && ctx.init<=ctx.limit) || (ctx.inc<0 && ctx.init>=ctx.limit)) {
  							es.push(tok);  FromPS.addProc(ctx.proc, es);  
  							os.push({typ:"integer",val:ctx.init});  ctx.init+=ctx.inc;
  						}
  					}
  					else {
  						var lf = new Float32Array(1);
  						lf[0]=ctx.limit;  ctx.limit=lf[0];
  						lf[0]=ctx.inc  ;  ctx.inc  =lf[0];
  						lf[0]=ctx.init;
  						if((ctx.inc>0 && lf[0]<=ctx.limit)  ||  (ctx.inc<0 && lf[0]>=ctx.limit)) { 
  							es.push(tok);  FromPS.addProc(ctx.proc, es);  
  							os.push({typ:"real",val:lf[0]});  lf[0]+=ctx.inc;  ctx.init=lf[0];
  						}
  					}
  				}
  				else if(op=="loop" ) {
  					var proc=os.pop();
  					es.push({typ:"name",val:op+"---", ctx:{ proc:proc }});
  				}
  				else if(op=="loop---") {
  					var ctx = tok.ctx;
  					FromPS.addProc(ctx.proc, es);
  				}
  				else if(op=="forall") {
  					var proc = os.pop(), obj = os.pop();
  					es.push({typ:"name",val:op+"---",ctx:[proc,obj,0]});
  				}
  				else if(op=="forall---") {
  					var ctx=tok.ctx, proc=ctx[0],obj=ctx[1],i=ctx[2];
  					if(obj.typ=="dict") {
  						var keys = Object.keys(obj.val);  //console.log(keys, obj.val, proc);
  						if(i<keys.length) {
  							es.push(tok);  FromPS.addProc(proc, es);  
  							os.push({typ:"name",val:"/"+keys[i]});
  							os.push(obj.val[keys[i]]);  ctx[2]++;
  							//console.log(keys[i], obj.val[keys[i]]);
  							//for(var p in obj.val) {  FromPS.addProcedure(proc.val, file);  FromPS.addProcedure([obj.val[p]], file);  }
  						}
  					}
  					else if(obj.typ=="procedure" || obj.typ=="array") {
  						if(i<obj.val.length) {
  							es.push(tok);  FromPS.addProc(proc, es);  
  							os.push(obj.val[i]);  ctx[2]++;
  						}
  						//for(var i=obj.val.length-1; i>=0; i--) {  FromPS.addProcedure(proc.val, file);  FromPS.addProcedure([obj.val[i]], file);  }
  					}
  					else {  console.log(proc, obj);  throw "forall: unknown type: "+obj.typ;  }
  				}
  				else if(op=="exit") {
  					var i = es.length-1;
  					while(i!=0 && (es[i].typ!="name" || !es[i].val.endsWith("---"))) i--;
  					if(i!=0) while(es.length>i) es.pop();
  					//console.log(es,i);  throw "e";
  				}
  				else if(op=="bind") {  
  				
  					/* var v=os.pop(), prc=v.val;  os.push(v); 
  					for(var i=0; i<prc.length; i++){
  						var nop = FromPS.getOperator(prc[i].val, ds);	 
  						//if(nop!=null) prc[i]=nop;  // missing !!!
  					}*/
  				}
  				else if(op=="xcheck") {
  					var obj = os.pop(), typ=obj.typ;
  					os.push({typ:"boolean",val:(typ=="procedure")});
  					//console.log(obj);  throw "e";
  				}
  				else if(op=="status"  ) {  var str = os.pop();  os.push({typ:"boolean",val:false});  }
  				else if(op=="cachestatus") {  for(var i=0; i<7; i++) os.push({typ:"integer",val:5000});  }
  				else if(op=="setcachelimit") {  os.pop();  }
  				else if(op=="type"    ) {
  					var o = os.pop();
  					var tps = {"name":"nametype","dict":"dicttype","boolean":"booleantype","procedure":"operatortype","string":"stringtype","null":"nulltype",
  								"integer":"integertype","array":"arraytype","operator":"operatortype","real":"realtype"};  
  					if(tps[o.typ]==null) {  console.log(o);  throw o.typ;  }
  					os.push({typ:"name",val:"/"+tps[o.typ]})
  				}
  				else if(op=="save"    ) {  os.push({typ:"state",val:JSON.parse(JSON.stringify(gst))});   }
  				else if(op=="restore" ) {  gst = env.gst = os.pop().val;  }
  				else if(op=="gsave"   ) {  gs.push(JSON.parse(JSON.stringify(gst)));  }
  				else if(op=="grestore") {  gst = env.gst = gs.pop();  }
  				else if(op=="grestoreall") {  while(gs.length!=0) gst = env.gst = gs.pop();  }
  				else if(op=="usertime" || op=="realtime") os.push({typ:"integer",val:(op=="usertime"?(Date.now()-otime):Date.now())});
  				else if(op=="flush" || op=="readonly" || op=="executeonly") {}
  				else if(op=="flushfile") os.pop();
  				else if(op=="filter") {
  					var fname = os.pop().val.slice(1), sarr;
  					if(fname=="ASCII85Decode") {
  						var sfile = os.pop().val;   sarr = FromPS.F.ASCII85Decode(sfile);
  					}
  					else if(fname=="RunLengthDecode") {
  						var sfile = os.pop().val;   sarr = FromPS.F.RunLengthDecode(sfile);
  					}
  					else if(fname=="SubFileDecode") {
  						var sfile = os.pop().val;   sarr = new Uint8Array(sfile);
  					}
  					else {  console.log(os);  throw fname;  }
  					os.push({typ:"file", val:{buff:sarr, off:0, stk:[], env:{pckn:false}}});
  				}
  				else if(op=="begincmap" || op=="endcmap") {}
  				else if(op=="begincodespacerange"||op=="beginbfrange"||op=="beginbfchar") {  env.cmnum = os.pop().val;  }
  				else if(op=="endcodespacerange"  ||op=="endbfrange"  ||op=="endbfchar"  ) {
  					var cl = (op=="endbfrange"?3:2);
  					var pn = op.slice(3), dct = ds[ds.length-1], bpc=0;
  					if(dct[pn]==null) dct[pn]=[];
  					for(var i=0; i<env.cmnum; i++) {
  						var vs=[];  
  						for(var j=cl-1; j>=0; j--) {  
  							var ar=os.pop(), av=ar.val, nv;
  							if(ar.typ=="string") {  nv = FromPS.strToInt(av);  if(j==0) bpc=av.length;  }
  							else {  nv = [];  for(var k=0; k<av.length; k++) nv.push(FromPS.strToInt(av[k].val));  }
  							vs[j]=nv;
  						}
  						dct[pn] = dct[pn].concat(vs);
  					}
  					if(op!="endcodespacerange") dct["bpc"] = bpc; // bytes per input character
  				}
  				else if(Oprs) Oprs(op, os, ds, es, gs, env, genv);
  				else {  console.log(val, op);  console.log(ds, os);  throw "e";  }
  			}
  		}
  		return true;
  	}
  	FromPS.strToInt = function(str)  {  var v=0;  for(var i=0; i<str.length; i++) v = (v<<8)|str[i];  return v;  }
  
  	FromPS.checkPageStarted = function(env,genv) {  if(!env.pgOpen) {  genv.StartPage(env.bb[0], env.bb[1], env.bb[2], env.bb[3]);  env.pgOpen = true;   }  }
  	
  	FromPS.F = {
  		HexDecode : function(file) {
  			function num(cc) {
  				if(48<=cc && cc<= 57) return cc-48;
  				if(65<=cc && cc<= 70) return 10+cc-65;
  				if(97<=cc && cc<=102) return 10+cc-97;
  				throw "e";
  			}
  			//console.log(FromPS.B.readASCII(file.buff, 0, file.buff.length));  throw "e";
  			var arr = [], i=file.off;
  			while(true) {
  				if(file.buff[i]==0x3e) break;
  				while(FromPS.isWhite(file.buff[i])) i++;
  				var c0 = num(file.buff[i]);  i++;
  				
  				if(file.buff[i]==0x3e) {  arr.push(c0<<4);  break;  }
  				while(FromPS.isWhite(file.buff[i])) i++;
  				var c1 = num(file.buff[i]);  i++;
  				arr.push((c0<<4) + c1); 
  			}
  			//console.log(file.buff.length, arr);
  			return new Uint8Array(arr);;
  		},
  		ASCII85Decode : function(file) {
  			var pws = [85*85*85*85, 85*85*85, 85*85, 85, 1];
  			var arr = [], i=0, tc=0, off=file.off;
  			while(true) {
  				if(off>=file.buff.length)  throw "e";
  				var cc = file.buff[off];  off++;
  				if(FromPS.isWhite(cc))  continue;
  				if(cc==126) {
  					if(i!=0) {
  						if(i==3) {  arr.push(((tc>>>24)&255));                   }
  						if(i==4) {  arr.push(((tc>>>24)&255), ((tc>>>16)&255));    }
  						var lb = (5-i)<<3;  // i=2: 24, i=3: 16 ...
  						var nn=((tc>>>lb)&255);  tc=(tc&((1<<lb)-1));  if(tc!=0)nn++;  arr.push(nn);
  					}
  					file.off=off+1;  //console.log(arr.join(","));  
  					return new Uint8Array(arr);  
  				}
  				if(cc==122) {  arr.push(0,0,0,0);  continue;  }
  				if(cc<33 || cc-33>84) throw "e";
  				tc += (cc-33)*pws[i];  i++;
  				if(i==5) {
  					arr.push((tc>>>24)&255);  arr.push((tc>>>16)&255);
  					arr.push((tc>>> 8)&255);  arr.push((tc>>> 0)&255);
  					i=0;  tc=0;
  				}
  			}
  		},
  		RunLengthDecode : function(file) {
  			var arr = [], off=file.off;
  			while(true) {
  				if(off>=file.buff.length)  {  console.log(arr);  throw "e";  }
  				var cc = file.buff[off];  off++;
  				if(cc==128) {  file.off=off;  return new Uint8Array(arr);  }
  				if(cc< 128) {  for(var i=0; i<cc+1  ; i++) arr.push(file.buff[off+i]);  off+=cc+1;  }
  				else        {  for(var i=0; i<257-cc; i++) arr.push(file.buff[off]  );  off++;      }
  			}
  		},
  		FlateDecode : function(file) {
  			//console.log("FlateDecode", file);
  			//if(file.buff.length==26770)  {  console.log(FromPS.B.readASCII(file.buff, 0, file.buff.length));  throw "e";  }
  			var b = file.buff, ub = new Uint8Array(b.buffer,file.off,b.length);  //console.log(ub);
  			var bytes = pako["inflate"](ub);
  			return bytes;
  		}
  	}
  	
  	FromPS.B = {
  		readUshort : function(buff,p)  {  return (buff[p]<< 8) | buff[p+1];  },
  		readUint   : function(buff,p)  {  return (buff[p]*(256*256*256)) + ((buff[p+1]<<16) | (buff[p+2]<< 8) | buff[p+3]);  },
  		readASCII  : function(buff,p,l){  var s = "";  for(var i=0; i<l; i++) s += String.fromCharCode(buff[p+i]);  return s;    }
  	}
  	
  	FromPS.nrm = function(v) {  return Math.max(0,Math.min(1,v));  }
  	FromPS.makeArr = function(a,typ) {  var na=[];  for(var i=0; i<a.length; i++) na.push({typ:typ,val:a[i]});  return na;  }
  	FromPS.readArr = function(a    ) {  var na=[];  for(var i=0; i<a.length; i++) na.push(a[i].val          );  return na;  }
  	FromPS.readStr = function(a    ) {  var s ="";  for(var i=0; i<a.length; i++) s+=String.fromCharCode(a[i]); return s;   }
  
  	FromPS.getFromStacks = function(name, ds)
  	{
  		//console.log(ds);
  		var di = ds.length-1;
  		while(di>=0) {  if(ds[di][name]!=null) return ds[di][name];  di--;  }
  		return null;
  	}
  	FromPS.where = function(name, ds)
  	{
  		var di = ds.length-1;
  		while(di>=0) {  if(ds[di][name]!=null) return ds[di]      ;  di--;  }
  		return null;
  	}
  	
  	
  	
  	
  	
  	
  	
  	FromPS.skipWhite = function(file) {
  		var i = file.off, buff=file.buff, isWhite = FromPS.isWhite;
  		
  		while(isWhite(buff[i]) || buff[i]==37) {
  			while(isWhite(buff[i])) i++;	// not the first whitespace
  			if(buff[i]==37) {  while(i<buff.length && !FromPS.isEOL(buff[i])) i++;  i++;  }	// comments
  		}
  		file.off = i;
  	}
  	
  	
  	
  	FromPS.getToken = function(es, ds) {
  		if(es.length==0) return null;
  		var src = es[es.length-1];
  		if(src.typ=="procedure") {
  			var tok = src.val[src.off];  src.off++;
  			if(src.off==src.val.length) es.pop();
  			return tok;
  		}
  		if(src.typ=="name") {  es.pop();  return src;  }
  		
  		var ftok = FromPS.getFToken(src.val, ds);
  		if(ftok==null) {  es.pop();  if(es.length!=0) ftok = FromPS.getFToken(es[es.length-1].val, ds);  }
  		return ftok;
  	}
  	
  	FromPS.getFToken = function(file, ds) {
  		FromPS.skipWhite(file);
  		
  		var isWhite = FromPS.isWhite, isSpecl = FromPS.isSpecl;
  		var i = file.off, buff=file.buff, tok = null;
  		if(i>=buff.length) return null;
  		
  		var cc = buff[i], ch = String.fromCharCode(cc);  i++;
  			
  		if(ch=="(") {  
  			var dpth=0, end=i;
  			while(!(buff[end]==41 && dpth==0)) {  if(buff[end]==40) dpth++;  if(buff[end]==41) dpth--;  if(buff[end]==92) end++;   end++;  }
  			var str = []; 
  			for(var j=0; j<end-i; j++) str.push(buff[i+j]);
  			i = end+1;
  			str = FromPS.getString(str);
  			tok = {typ:"string", val:str};
  		}
  		else if(ch=="{" || ch=="}" || ch=="[" || ch=="]") {  tok = {typ:"name", val:ch};  }
  		else if((ch=="<" && buff[i]==60) || (ch==">" && buff[i]==62)) {  tok = {typ:"name", val:ch=="<" ? "<<" : ">>"};  i++;  }
  		else if(ch=="<") {
  			var end = i;  while(buff[end]!=62) end++;  
  			var str = FromPS.readHex({buff:buff,off:i},(end-i)>>>1);
  			tok = {typ:"string",val:str};  i = end+1;
  		}
  		else {
  			var end = i;
  			while(end<buff.length && !isWhite(buff[end]) && (!isSpecl(buff[end]) || (buff[end]==47&&buff[end-1]==47&&end==i) )) end++;  // read two slashes
  			var name = FromPS.B.readASCII(buff, i-1, end-i+1);
  			i = end;
  			var num = parseFloat(name);
  			if(false) {}
  			else if(name=="true" || name=="false") tok = {typ:"boolean", val:name=="true"};
  			else if(!isNaN(num)) {
  				var f32 = new Float32Array(1);  f32[0]=num;  num=f32[0];
  				tok = {typ:name.indexOf(".")==-1?"integer":"real", val:num};
  			}
  			else {  
  				if(name.slice(0,2)=="//") {
  					var nn = name.slice(2);
  					var sbs = FromPS.getFromStacks(nn, ds);
  					if(sbs==null) throw "e";
  					tok = sbs;
  				}
  				else tok = {typ:"name", val:name};    
  			}
  		}
  		file.off = i;
  		
  		return tok;
  	}
  	// ( ) < >     [ ] { }  %  /
  	FromPS.isSpecl = function(cc) {  return [ 40,41, 60,62,   91,93, 123,125,  37,  47   ].indexOf(cc)!=-1;  }
  	FromPS.isWhite = function(cc) {  return cc==9 || cc==10 || cc==12 || cc==13 || cc==32;  }
  	FromPS.isEOL   = function(cc) {  return cc==10 || cc==13;  }
  	
  	FromPS.getString = function(str) {  
  		var s=[];  
  		var m0 = ["n" , "r" , "t" , "b" , "f" , "\\", "(", ")", " ", "/"];
  		var m1 = ["\n", "\r", "\t", "", "", "\\", "(", ")", " ", "/"];
  		
  		for(var i=0; i<str.length; i++) {
  			var cc = str[i], ch = String.fromCharCode(cc);
  			if(ch=="\\") {
  				var nx = String.fromCharCode(str[i+1]);  i++;
  				if(nx=="\r" || nx=="\n") continue;
  				var idx = m0.indexOf(nx);
  				if(idx!=-1) s.push(m1[idx].charCodeAt(0));
  				else {
  					var cod = nx + String.fromCharCode(str[i+1]) + String.fromCharCode(str[i+2]);  i+=2;
  					s.push(parseInt(cod,8));
  				}
  			}
  			else s.push(cc);  
  		}
  		return s;  
  	}
  	FromPS.makeString = function(arr) {
  		var m0 = ["n" , "r" , "t" , "b" , "f" , "\\", "(", ")"];
  		var m1 = ["\n", "\r", "\t", "", "", "\\", "(", ")"];
  		var out = [];
  		for(var i=0; i<arr.length; i++) {
  			var b = arr[i];
  			var mi = m1.indexOf(String.fromCharCode(b));
  			if(mi==-1) out.push(b);
  			else out.push(92, m0[mi].charCodeAt(0));
  		}
  		return out;
  	}
  	FromPS.readHex = function(fl, l)
  	{
  		var i=0, j=-1, val=[];
  		while(true) {
  			var cc = fl.buff[fl.off];  fl.off++;
  			var ci=0;
  			if(47<cc && cc<58) ci=cc-48;
  			else if(96<cc && cc<103) ci=10+cc-97;
  			else if(64<cc && cc<71 ) ci=10+cc-65;
  			else continue;
  			if(j==-1) j=ci;
  			else {  val[i]=(j<<4)|ci;  j=-1;  i++;  if(i==l) break;  }
  		}
  		return val;
  	}
  	
  module.exports={
  	FromPS
  };
  