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 }