wps

PostScript for the Web
git clone https://logand.com/git/wps.git/
Log | Files | Refs | LICENSE

wps.js (14833B)


      1 /// WPS: PostScript and PDF interpreter for HTML 5 canvas
      2 /// http://logand.com/sw/wps/index.html
      3 /// (c) 2009, 2010, 2011 Tomas Hlavaty
      4 /// Licensed under the GPLv3+ license.
      5 /// http://www.fsf.org/licensing/licenses/gpl.html
      6 
      7 // random markers for quoting and toString generated using picolisp
      8 // (hex (in "/dev/random" (rd 4)))
      9 
     10 function isQuoted(V) {return V.D16322F5;}
     11 function quote(V) {V.D16322F5 = true; return V;}
     12 function unquote(V) {delete V.D16322F5; return V;}
     13 
     14 function Symbol(N) {this.nm = N; return this;}
     15 function isSymbol(V) {return V &&  V.constructor === Symbol;}
     16 function symbolName(V) {return V.nm;}
     17 Symbol.prototype.toString = function() {return "05E2710C" + symbolName(this);};
     18 
     19 function isArray(V) {
     20   return V &&  V.constructor === Array;
     21 }
     22 
     23 function isObject(V) {return "object" == typeof V;}
     24 
     25 function inDs(Ds, K) {
     26   for(var I = Ds.length - 1; 0 <= I; --I) {
     27 	if("undefined" != typeof Ds[I][K])
     28 	  return Ds[I];
     29   }
     30   return false;
     31 }
     32 
     33 function member(C, L) {
     34   return 0 <= L.indexOf(C);
     35 }
     36 
     37 function PsParser(Ds) {
     38   var Self = this;
     39   function init(L) {
     40     Self.L = L;
     41     Self.N = L.length;
     42     Self.I = 0;
     43     Self.D = 0;
     44   }
     45   function peek() {return Self.I < Self.N && Self.L[Self.I];}
     46   function xchar() {return Self.I < Self.N && Self.L[Self.I++];}
     47   function skip() { // TODO white space ffeed + null???
     48     while(Self.I < Self.N && member(Self.L[Self.I], " \t\n"))
     49       Self.I++;
     50   }
     51   function comment() {
     52     while("%" == peek()) {
     53       while(peek() && "\n" != peek())
     54         xchar();
     55       skip();
     56     }
     57   }
     58   function text() {
     59     // TODO hex text in <>
     60     // TODO ASCII base-85 <~ and ~>
     61     xchar();
     62     var L = [];
     63     var N = 1;
     64     while(0 < N && peek()) {
     65       var C = xchar();
     66       switch(C) {
     67         case "(":
     68           N++;
     69           break;
     70         case ")":
     71           N--;
     72           if(N <= 0) C = false;
     73           break;
     74         case "\\":
     75           C = xchar();
     76           switch(C) {
     77             case "(": break;
     78             case ")": break;
     79             case "\\": break;
     80             case "n": C = "\n"; break;
     81             case "r": C = "\r"; break;
     82             case "t": C = "\t"; break;
     83             // TODO \n (ignore \n) \b \f \ddd octal
     84             default:
     85               C = false;
     86           }
     87           break;
     88       }
     89       if(C !== false) L.push(C);
     90     }
     91     return L.join("");
     92   }
     93   function symbol() {
     94     // TODO 1e10 1E-5 real numbers
     95     // TODO radix numbers 8#1777 16#FFFE 2#1000
     96     // TODO if preceeded with / then cannot be number
     97     var C = xchar();
     98     if(member(C, "()<>/% \t\n")) throw "Symbol expected, got " + C;
     99     var N = member(C, "+-0123456789.");
    100     var F = "." == C;
    101     var L = [C];
    102     while(peek() && !member(peek(), "()<>[]{}/% \t\n")) {
    103       C = xchar();
    104       L.push(C);
    105       if(N && !member(C, "0123456789")) {
    106         if(!F && "." == C) F = true;
    107         else N = false;
    108       }
    109     }
    110     L = L.join("");
    111     if(1 == L.length && member(L, "+-.")) N = false;
    112     return N ? (F ? parseFloat(L) : parseInt(L, 10)) : new Symbol(L);
    113   }
    114   function token() {
    115     skip();
    116     switch(peek()) { // TODO read dict in <> <~~>
    117       case false: return undefined;
    118       case "%": return comment();
    119       case "[": return new Symbol(xchar());
    120       case "]": return new Symbol(xchar());
    121       case "{": Self.D++; return new Symbol(xchar());
    122       case "}": Self.D--; return new Symbol(xchar());
    123       case "/":
    124         xchar();
    125         if("/" == peek()) {
    126             xchar();
    127             var X = symbol();
    128             return inDs(Ds, X);
    129         } else {
    130             var X = symbol();
    131             return quote(X);
    132         }
    133       case "(": return text();
    134       case "<":
    135         xchar();
    136         if("<" != peek()) throw "Encoded strings not implemented yet";
    137         xchar();
    138         return new Symbol("<<");
    139       case ">":
    140         xchar();
    141         if(">" != peek()) throw "Unexpected >";
    142         xchar();
    143         return new Symbol(">>");
    144       default: return symbol();
    145     }
    146   }
    147   PsParser.prototype.init = init;
    148   PsParser.prototype.peek = peek;
    149   PsParser.prototype.token = token;
    150   return this;
    151 }
    152 
    153 function Ps0(Os, Ds, Es) {
    154   function run(X, Z) {
    155     if(isSymbol(X) && !isQuoted(X)) { // executable name
    156       var D = inDs(Ds, X);
    157       if(!D)
    158         throw "bind error '" + X + "'";
    159       Es.push([false, D[X]]);
    160     } else if(Z && isArray(X) && isQuoted(X)) { // proc from Es
    161       if(0 < X.length) {
    162         var F = X[0];
    163         var R = quote(X.slice(1));
    164         if(0 < R.length) Es.push([false, R]);
    165         run(F, false);
    166       }
    167     } else if("function" == typeof X) X(); // operator
    168     else Os.push(X);
    169   }
    170   function exec() {
    171     var X = Os.pop();
    172     run(X, false);
    173   }
    174   function step() {
    175     var C = Es.pop();
    176     var L = C.shift(); // TODO use for 'exit'
    177     var X = C.pop();
    178     for(var I = 0; I < C.length; I++)
    179       Os.push(C[I]);
    180     run(X, true);
    181   }
    182   var PsP = new PsParser(Ds);
    183   function parse(L) {
    184     PsP.init(L);
    185     while(PsP.peek()) {
    186       var T = PsP.token();
    187       if(T || T === 0) {
    188         Os.push(T);
    189         if(PsP.D <= 0 || isSymbol(T) &&
    190            (member(symbolName(T), "[]{}") ||
    191             "<<" == symbolName(T) || ">>" == symbolName(T))) {
    192           exec();
    193           while(0 < Es.length)
    194             step();
    195         }
    196       }
    197     }
    198     return Os;
    199   }
    200   Ps0.prototype.run = run;
    201   Ps0.prototype.exec = exec;
    202   Ps0.prototype.step = step;
    203   Ps0.prototype.parse = parse;
    204   return this;
    205 }
    206 
    207 function Wps() {
    208   var Os = [];
    209   var Sd = {};
    210   var Ds = [Sd];
    211   var Es = [];
    212   var Ps = new Ps0(Os, Ds, Es);
    213 
    214   function def(Nm, Fn) {Sd[new Symbol(Nm)] = Fn;}
    215 
    216   // trivial
    217   def("true", function() {Os.push(true);});
    218   def("false", function() {Os.push(false);});
    219   def("null", function() {Os.push(null);});
    220   // math
    221   def("sub", function() {var X = Os.pop(); Os.push(Os.pop() - X);});
    222   def("mul", function() {Os.push(Os.pop() * Os.pop());});
    223   def("div", function() {var X = Os.pop(); Os.push(Os.pop() / X);});
    224   def("mod", function() {var X = Os.pop(); Os.push(Os.pop() % X);});
    225   // stack
    226   var M = {};
    227   def("mark", function() {Os.push(M);});
    228   def("counttomark", function() {
    229     var N = 0;
    230     for(var I = Os.length - 1; 0 <= I; I--)
    231       if(M === Os[I]) return Os.push(N);
    232       else N++;
    233     throw "Mark not found";
    234   });
    235   def("<<", Sd[new Symbol("mark")]); // TODO doc
    236   def(">>", function() { // TODO doc
    237     var D = {};
    238     while(0 < Os.length) {
    239       var V = Os.pop();
    240       if(M === V) return Os.push(D);
    241       D[Os.pop()] = V;
    242     }
    243     throw "Mark not found";
    244   });
    245   def("exch", function() {
    246     var Y = Os.pop();
    247     var X = Os.pop();
    248     Os.push(Y);
    249     Os.push(X);
    250   });
    251   def("clear", function() {Os.length = 0;});
    252   def("pop", function() {Os.pop();});
    253   def("index", function() {
    254     Os.push(Os[Os.length - 2 - Os.pop()]);
    255   });
    256   def("roll", function() { // TODO in ps
    257     var J = Os.pop();
    258     var N = Os.pop();
    259     var X = [];
    260     var Y = [];
    261     for(var I = 0; I < N; I++)
    262       if(I < J) X.unshift(Os.pop());
    263       else Y.unshift(Os.pop());
    264     for(I = 0; I < J; I++) Os.push(X.shift());
    265     for(I = 0; I < N - J; I++) Os.push(Y.shift());
    266   });
    267   def("copy", function() {
    268 	var N = Os.pop();
    269 	if(isObject(N)) {
    270 	  var X = Os.pop();
    271 	  for(var I in X)
    272         N[I] = X[I];
    273       Os.push(N);
    274     } else {
    275       var X = Os.length - N;
    276       for(var I = 0; I < N; I++)
    277         Os.push(Os[X + I]);
    278     }
    279   });
    280   // array
    281   def("length", function() {Os.push(Os.pop().length);});
    282   def("astore", function() {
    283     var A = Os.pop();
    284     var N = A.length;
    285     for(var I = N - 1; 0 <= I; I--)
    286       A[I] = Os.pop();
    287     Os.push(A);
    288   });
    289   def("array", function() {Os.push(new Array(Os.pop()));});
    290   // conditionals
    291   def("eq", function() {var Y = Os.pop(); var X = Os.pop(); Os.push(X == Y);});
    292   def("lt", function() {var Y = Os.pop(); var X = Os.pop(); Os.push(X < Y);});
    293   // control
    294   def("ifelse", function() {
    295     var N = Os.pop();
    296     var P = Os.pop();
    297     var C = Os.pop();
    298     Es.push([false, C === true ? P : N]);
    299   });
    300   def("repeat", function Xrepeat() { // TODO in ps
    301     var B = Os.pop();
    302     var N = Os.pop();
    303     if(1 < N) Es.push([true, N - 1, B, Xrepeat]);
    304     if(0 < N) Es.push([false, B]);
    305   });
    306   def("for", function Xfor() { // TODO in ps
    307     var B = Os.pop();
    308     var L = Os.pop();
    309     var K = Os.pop();
    310     var J = Os.pop();
    311     if(K < 0) {
    312       if(L <= J + K) Es.push([true, J + K, K, L, B, Xfor]);
    313       if(L <= J) Es.push([false, J, B]);
    314     } else {
    315       if(J + K <= L) Es.push([true, J + K, K, L, B, Xfor]);
    316       if(J <= L) Es.push([false, J, B]);
    317     }
    318   });
    319   function XforallA() {
    320     var B = Os.pop();
    321     var A = Os.pop();
    322     var I = Os.pop();
    323     var N = A.length;
    324     if(1 < N - I) Es.push([true, I + 1, A, B, XforallA]);
    325     if(0 < N - I) Es.push([false, A[I], B]);
    326   }
    327   function XforallO() {
    328     var B = Os.pop();
    329     var O = Os.pop();
    330     var L = Os.pop();
    331     var N = L.length;
    332     var K;
    333     if(0 < N) K = L.pop();
    334     if(1 < N) Es.push([true, L, O, B, XforallO]);
    335     if(0 < N) Es.push([false, K, O[K], B]);
    336   }
    337   def("forall", function() { // TODO in ps
    338     var B = Os.pop();
    339     var O = Os.pop();
    340     if(isArray(O)) {
    341       Os.push(0);
    342       Os.push(O);
    343       Os.push(B);
    344       XforallA();
    345     } else if(isObject(O)) {
    346       var L = [];
    347       for(var K in O) {L.push(K);}
    348       Os.push(L);
    349       Os.push(O);
    350       Os.push(B);
    351       XforallO();
    352     } else if("string" == typeof O) {
    353       Os.push(0);
    354       Os.push(O.split(""));
    355       Os.push(B);
    356       XforallA();
    357     } else throw "Cannot apply forall to " + O;
    358   });
    359   def("exec", function() {Es.push([false, Os.pop()]);});
    360   def("cvx", function() {
    361     var X = Os.pop();
    362     if(isSymbol(X) && isQuoted(X)) Os.push(unquote(X)); // executable name
    363     else if(isArray(X) && !isQuoted(X)) Os.push(quote(X)); // proc
    364     // TODO string -> parse
    365     else Os.push(X);
    366   });
    367   def("cvlit", function() {
    368     var X = Os.pop();
    369     if(isSymbol(X) && !isQuoted(X)) Os.push(quote(X)); // un-executable name
    370     else if(isArray(X) && isQuoted(X)) Os.push(unquote(X)); // un-proc
    371     // TODO reverse? string -> parse
    372     else Os.push(X);
    373   });
    374   // dictionary
    375   def("dict", function() {Os.pop(); Os.push({});});
    376   def("get", function() {
    377     var K = Os.pop();
    378     var D = Os.pop();
    379     // TODO other datatypes
    380     if(isSymbol(K)) Os.push(D[K]);
    381     else Os.push(D[K]);
    382   });
    383   def("put", function() {
    384     var V = Os.pop();
    385     var K = Os.pop();
    386     var D = Os.pop();
    387     // TODO other datatypes
    388     if(isSymbol(K)) D[K] = V;
    389     else D[K] = V;
    390   });
    391   def("begin", function() {Ds.push(Os.pop());});
    392   def("end", function() {Ds.pop();});
    393   def("currentdict", function() {Os.push(Ds[Ds.length - 1]);});
    394   def("where", function() {
    395     var K = Os.pop();
    396     var D = inDs(Ds, K);
    397 	if(D) {
    398 	  Os.push(D);
    399 	  Os.push(true);
    400 	} else Os.push(false);
    401   });
    402   // miscellaneous
    403   def("save", function() {
    404     var X = Ds.slice();
    405     for(var I = 0; I < X.length; I++) {
    406       var A = X[I];
    407       var B = {};
    408       for(var J in A)
    409         B[J] = A[J];
    410       X[I] = B;
    411     }
    412     Os.push(X);
    413   });
    414   def("restore", function() {
    415     var X = Os.pop();
    416     while(0 < Ds.length)
    417       Ds.pop();
    418     while(0 < X.length)
    419       Ds.unshift(X.pop());
    420   });
    421   def("type", function() {
    422     var A = Os.pop();
    423     var X;
    424     if(null === A) X = "nulltype";
    425     else if(true === A || false === A) X = "booleantype";
    426     else if(M === A) X = "marktype";
    427     else if("string" == typeof A) X = "stringtype";
    428     else if(isSymbol(A)) X = isQuoted(A) ? "nametype" : "operatortype";
    429     else if("function" == typeof A) X = "operatortype";
    430     else if(isArray(A)) X = "arraytype";
    431     else if(isObject(A)) X = "dicttype";
    432     else if(1 * A == A) X = A % 1 == 0 ? "integertype" : "realtype";
    433     else throw "Undefined type '" + A + "'";
    434     Os.push(X);
    435     // filetype
    436     // packedarraytype (LanguageLevel 2)
    437     // fonttype
    438     // gstatetype (LanguageLevel 2)
    439     // savetype
    440   });
    441   var Sb = true;
    442   def(".strictBind", function() {Sb = true === Os.pop();});
    443   def("bind", function() {Os.push(bind(Os.pop()));});
    444   function bind(X) {
    445     if(isSymbol(X) && !isQuoted(X)) {
    446       var D = inDs(Ds, X);
    447       if(Sb) {
    448         if(!D)
    449           throw "bind error '" + X + "'";
    450         return bind(D[X]);
    451       } else return !D ? X : bind(D[X]);
    452     } else if(isArray(X) && isQuoted(X)) {
    453       var N = X.length;
    454       var A = [];
    455       for(var I = 0; I < N; I++) {
    456         var Xi = X[I];
    457         var Xb = bind(Xi);
    458         if(isArray(Xi))
    459           A = A.concat(isQuoted(Xi) ? quote([Xb]) : [Xb]);
    460         else
    461           A = A.concat(Xb);
    462       }
    463       return quote(A);
    464     }
    465     return X;
    466   }
    467   // debugging
    468   def("=", function() {var X = Os.pop(); alert(X);}); // TODO
    469   def("==", function() {alert(Os.pop());}); // TODO
    470   def("stack", function() {alert(Os);}); // TODO
    471   def("pstack", function() {alert(Os);}); // TODO
    472   // js ffi
    473   def(".call", function() {
    474     var N = Os.pop();
    475     var K = Os.pop();
    476     var D = Os.pop();
    477     var X = [];
    478     for(var I = 0; I < N; I++) X.unshift(Os.pop());
    479     Os.push(D[K].apply(D, X));
    480   });
    481   def(".math", function() {Os.push(Math);});
    482   def(".date", function() {Os.push(new Date());}); // TODO split new and Date
    483   def(".window", function() {Os.push(window);});
    484   def(".callback", function() { // TODO event arg?
    485     var X = Os.pop();
    486     Os.push(function() {
    487               Ps.run(X, true);
    488               while(0 < Es.length)
    489                 Ps.step();
    490             });
    491   });
    492   // html5
    493   def(".minv", function() { // TODO in ps
    494     var M = Os.pop();
    495     var a = M[0]; var b = M[1];
    496     var d = M[2]; var e = M[3];
    497     var g = M[4]; var h = M[5];
    498     Os.push([e, b, d, a, d*h-e*g, b*g-a*h]);
    499   });
    500   def(".mmul", function() { // TODO in ps
    501     var B = Os.pop();
    502     var A = Os.pop();
    503     var a = A[0]; var b = A[1];
    504     var d = A[2]; var e = A[3];
    505     var g = A[4]; var h = A[5];
    506     var r = B[0]; var s = B[1];
    507     var u = B[2]; var v = B[3];
    508     var x = B[4]; var y = B[5];
    509     Os.push([a*r+b*u, a*s+b*v, d*r+e*u, d*s+e*v, g*r+h*u+x, g*s+h*v+y]);
    510   });
    511   def(".xy", function() { // TODO in ps
    512     var M = Os.pop();
    513     var Y = Os.pop();
    514     var X = Os.pop();
    515     Os.push(M[0] * X + M[2] * Y + M[4]);
    516     Os.push(M[1] * X + M[3] * Y + M[5]);
    517   });
    518   // TODO js ffi to manipulate strings so the following can be in ps
    519   def(".rgb", function() { // TODO in ps
    520     var B = Os.pop();
    521     var G = Os.pop();
    522     var R = Os.pop();
    523     Os.push("rgb(" + R + "," + G + "," + B + ")");
    524   });
    525   def(".rgba", function() { // TODO in ps
    526     var A = Os.pop();
    527     var B = Os.pop();
    528     var G = Os.pop();
    529     var R = Os.pop();
    530     Os.push("rgba(" + R + "," + G + "," + B + "," + A + ")");
    531   });
    532 
    533   function parse() {
    534     var T = arguments;
    535     if(T.length)
    536       for(var I = 0; I < T.length; I++)
    537         Ps.parse(T[I]);
    538     else Ps.parse(T);
    539     return Os;
    540   }
    541   Wps.prototype.parse = parse;
    542   return this;
    543 }