index.org (51539B)
1 #+title: WPS: PostScript for the Web 2 #+description: PostScript and PDF interpreter for HTML 5 canvas 3 #+keywords: PostScript, PDF, interpreter, HTML 5, canvas, JavaScript 4 #+options: num:nil toc:t 5 #+macro: ps [[http://www.capcode.de/help/$1][$1]] 6 7 #+begin_html 8 <p class="h0">WPS: PostScript for the Web</p> 9 #+end_html 10 11 Welcome to WPS, a PostScript and PDF interpreter for HTML 5 canvas. 12 13 Note that to see and run the examples, JavaScript must be enabled and 14 your browser must support HTML 5 canvas (latest Firefox, Opera and 15 Chrome should work). 16 17 This document allows you to try simple PostScript programs in the WPS 18 sandbox. A few examples are presented here accompanied by a brief 19 description of the interpreter and listing some implementation notes 20 for my future reference. 21 22 #+begin_html 23 <style> 24 tt {background-color:#fdf} 25 canvas {width:12em;height:12em;border:1px dashed black} 26 </style> 27 #+end_html 28 29 #+html: <div id="wps" style="display:none"> 30 #+include "wps.wps" src text 31 #+html: </div> 32 33 #+begin_html 34 <script type="text/javascript" src="wps.js"></script> 35 <script> 36 function $(Id) {return document.getElementById(Id);} 37 function $$(Id) {return $(Id).textContent;} 38 wps = new Wps; 39 wps.parse($$("wps")); 40 </script> 41 #+end_html 42 43 * WPS sandbox 44 45 #+begin_html 46 <canvas id="xsandbox"></canvas> 47 <p>Sandbox:</p> 48 <p> 49 <textarea id="sandbox" style="width:100%" rows="18"> 50 /n 10 def 51 /w 25 def 52 53 0 0 n w mul dup .gbox 54 55 4 dict begin 56 0 1 n 1 sub { 57 /i exch def 58 /ii 1 1 n div i mul sub def 59 0 1 n 1 sub { 60 /j exch def 61 /jj 1 1 n div j mul sub def 62 ii jj 0 setrgbcolor 63 w j mul w i mul w w rectfill 64 } for 65 } for 66 end 67 </textarea> 68 </p> 69 <script> 70 function sandbox() {(new Wps).parse($$("wps"), "(xsandbox) .setGc", $("sandbox").value);} 71 </script> 72 <button onclick="javascript:sandbox();">Run</button> code from sandbox. 73 #+end_html 74 75 * PostScript interpreter 76 77 A few initial ideas and questions: 78 79 - Learn and implement a Forth like language. PostScript seems like a 80 good choice: 81 - It has the right syntax and stack based evaluation. 82 - It is practical and widely used. 83 - It has long sucessful history in print and publishing (and more). 84 - It is a predecessor of PDF. 85 - Almost everything (e.g. editors, pictures, documentation) can be 86 reused to a great extent. 87 - It is ideal for HTML 5 canvas experiments because from the 88 PostScript point of view, canvas is just another low level device. 89 - Flexibility and simplicity first. 90 - Optimize for fast code change, not for raw running speed. Keep 91 the code small and regular if possible. 92 - Can JavaScript be used as a portable assembler for the Web? Is 93 building scripting languages on top of JavaScript feasible and 94 efficient enough for real-world use? If not, why? Find the 95 limits. 96 - Keep the language/environment specific core as small as possible. 97 - Allow to port the interpreter to other languages on both 98 client and server side. 99 - Be open for the possibility of running "the same code" on both the 100 client and server side. 101 - Can PDF documents be displayed in web browsers without server-side 102 image rendering? 103 - Implement a canvas based version of PDF page contents in [[../ondoc/index.org][OnDoc]]. 104 - It might be possible to implement different backend devices to be 105 used instead of HTML 5 canvas, for example an SVG device. 106 - Investigate the possibility of implementing a Lisp interpreter 107 suitable for production use in web applications. 108 109 There are several things WPS is about: 110 111 - stack(s) 112 - function (operator) dictionary 113 - reader 114 - interpreter/evaluator 115 - data types 116 - native bindings (JavaScript FFI) 117 - PostScript and PDF API 118 119 [[http://en.wikipedia.org/wiki/PostScript][PostScript]] can be seen as a crossover between [[http://en.wikipedia.org/wiki/Forth_(programming_language)][Forth]] and [[http://en.wikipedia.org/wiki/LISP][Lisp]] 120 programming languages. It is (roughly) a programming language with 121 [[http://en.wikipedia.org/wiki/Reverse_Polish_notation][RPN]], complex data types, garbage collection and specialized 122 drawing operators. 123 124 ** Trivial example 125 126 The core essence of a RPN calculator is captured in the JavaScript 127 code bellow. 128 129 #+html: <div id="example1"> 130 #+begin_src js2 131 function example1() { 132 // define stack and operators 133 var Os = []; 134 var Sd = {}; 135 Sd["+"] = function() {Os.push(Os.pop() + Os.pop());}; 136 Sd["="] = function() {alert(Os.pop());}; 137 // compute 1 2 = 3 + = 138 Os.push(1); 139 Os.push(2); 140 Sd["="](); 141 Os.push(3); 142 Sd["+"](); 143 Sd["="](); 144 } 145 #+end_src 146 #+html: </div> 147 #+begin_html 148 <script> 149 function ex1() { 150 eval($$("example1")); 151 example1(); 152 } 153 </script> 154 <button onclick="javascript:ex1()">Eval</button> 155 "<tt>1 2 = 3 + =</tt>" 156 #+end_html 157 158 =Os= stands for Operand Stack, which holds arguments for operators. 159 =Sd= is a System Dictionary which contains definitions of operators 160 (JavaScript functions in this case). 161 162 ** Example with PostScript reader 163 164 PostScript has simple but non-trivial syntax so a reader which reads 165 text and creates internal PostScript objects is necessary. The reader 166 and evaluator is called =Ps0= (an empty PostScript interpreter) in the 167 JavaScript code bellow. 168 169 #+html: <div id="example2"> 170 #+begin_src js2 171 function example2(T) { 172 var Os = []; 173 var Sd = {}; 174 var Ds = [Sd]; 175 var Es = []; 176 Sd[new Symbol("+")] = function() {Os.push(Os.pop() + Os.pop());}; 177 Sd[new Symbol("dup")] = function() {var X = Os.pop(); Os.push(X); Os.push(X);}; 178 Sd[new Symbol("=")] = function() {alert(Os.pop());}; 179 (new Ps0(Os, Ds, Es)).parse(T); // read and interpret code T 180 } 181 #+end_src 182 #+html: </div> 183 #+begin_html 184 <script> 185 function ex2() { 186 eval($$("example2")); 187 example2($$("ex2")); 188 } 189 </script> 190 <button onclick="javascript:ex2()">Eval</button> 191 "<tt id="ex2">12 34 + dup = 56 + =</tt>" 192 #+end_html 193 194 =Ds= is a Dictionary Stack allowing users to redefine existing 195 operators and revert back to the original ones. =Es= is an Execution 196 Stack which is used to implement a tail recursive evaluator. 197 198 ** Example with recursion 199 200 It is possible to write recursive code in PostScript. The following 201 PostScript code is from the [[http://www.math.ubc.ca/~cass/graphics/manual/pdf/ch9.pdf][Recursion in PostScript PDF document]]. 202 203 #+html: <div id="example3"> 204 #+begin_src ps 205 /factorial1 { 206 1 dict begin 207 /n exch def 208 n 0 eq {1}{n n 1 sub factorial1 mul} ifelse 209 end 210 } def 211 212 5 factorial1 = 213 214 /factorial2 { 215 dup 0 eq {pop 1}{dup 1 sub factorial2 mul} ifelse 216 } def 217 218 5 factorial2 = 219 220 % based on the PostScript example from 221 % http://partners.adobe.com/public/developer/en/ps/sdk/sample/BlueBook.zip 222 223 /factorial3 { 224 dup 1 gt {dup 1 sub factorial3 mul} if 225 } def 226 227 5 factorial3 = 228 #+end_src 229 #+html: </div> 230 #+begin_html 231 <script> 232 function ex3() {(new Wps).parse($$("wps"), $$("example3"));} 233 </script> 234 <button onclick="javascript:ex3();">Run</button> the example. 235 #+end_html 236 237 ** Execution stack 238 239 The interpreter manages its Execution Stack explicitly. 240 241 Most operators simply: 242 243 1. get their arguments from the Operand Stack 244 2. perform some computation and/or side effects 245 3. push results to the Operand Stack 246 247 Some operators are more complex and involve some kind of control flow, 248 e.g. {{{ps(if)}}}, {{{ps(repeat)}}}, {{{ps(for)}}}, {{{ps(loop)}}} 249 operators. Such operators: 250 251 1. get their arguments from the Operand Stack 252 2. perform single step of some computation and/or side effects 253 3. push the continuation (code and arguments to be executed next) to 254 the Execution Stack 255 256 [[http://en.wikipedia.org/wiki/Tail_call][Tail Call Optimisation]] is implemented using [[http://logand.com/picoWiki/trampoline][trampoline]]. The evaluator 257 runs in a loop getting the next [[http://en.wikipedia.org/wiki/Continuation][continuation]] from the Execution Stack. 258 Operators that want to "continue" their execution (i.e. use the 259 interpreter to run other operators, including themselves) must perform 260 only one step at a time and save the remaining steps (continuation) on 261 the Execution Stack. 262 263 For example, the {{{ps(if)}}} operator saves the "then" or "else" code 264 branch to the Execution Stack depending on the value of the "test" 265 argument. It does not "evaluate" the chosen branch directly 266 (recursively) but leaves the control to the evaluator loop. 267 268 The whole process of interpreting is fed from JavaScript strings which 269 are obtained from the content of HTML elements (sometimes hidden from 270 this document). 271 272 ** PostScript data types 273 274 PostScript has quite rich set of data types. 275 See [[http://www.adobe.com/devnet/postscript/pdfs/PLRM.pdf][PostScript Language Reference PDF document]] for more details. 276 277 | category | type | executable | example | spec | 278 |-----------+-------------+------------+------------------------+--------------------| 279 | simple | boolean | | true false | | 280 | | fontID | | | | 281 | | integer | | 42 -123 0 | | 282 | | mark | | | | 283 | | name | Y | draw /draw | | 284 | | null | | null | | 285 | | operator | Y | | | 286 | | real | | 3.14 1e-10 | | 287 | | save | | | | 288 | composite | array | Y | [1 /hi 3.14] {1 2 add} | | 289 | | condition | | | Display PostScript | 290 | | dictionary | | <</a 1/b 2>> | | 291 | | file | | | | 292 | | gstate | | | Level 2 | 293 | | lock | | | Display PostScript | 294 | | packedarray | | | Level 2 | 295 | | string | Y | (hi) <a33f> | | 296 297 The following data types are implemented in WPS: 298 299 | category | type | direct | literal | executable | 300 |-----------+------------+--------+---------+------------| 301 | simple | boolean | Y | Y | - | 302 | | number | Y | Y | - | 303 | | mark | - | Y | - | 304 | | name | - | Y | Y | 305 | | null | Y | Y | - | 306 | | operator | Y | - | Y | 307 | composite | array | Y | Y | - | 308 | | proc | - | - | Y | 309 | | dictionary | Y | Y | - | 310 | | string | Y | Y | - | 311 312 All the above types are represented directly in JavaScript except: 313 314 | type | representation | 315 |-----------------+-----------------| 316 | mark | unique object | 317 | literal name | quoted symbol | 318 | executable name | unquoted symbol | 319 | operator | function | 320 | proc | quoted array | 321 322 The interpreter needs to understand when to evaluate an argument. The 323 distinction between a "literal" and "executable" is the key. For the 324 "proc" type, its origin from the Execution Stack is also important. 325 326 ** Quoting and execution 327 328 There are two important operators to control evaluation at the 329 PostScript language level. 330 331 The {{{ps(exec)}}} operator usually leaves the argument as is except: 332 333 | type | result | 334 |-----------------+-------------------| 335 | executable name | exec value | 336 | operator | apply operator | 337 | proc | exec each element | 338 339 The {{{ps(cvx)}}} operator makes the argument "executable". Usually 340 leaves the argument as is except: 341 342 | from | to | how | 343 |--------------+-----------------+---------| 344 | literal name | executable name | unquote | 345 | array | proc | quote | 346 | string | proc | ~ parse | 347 348 The ~ (tilde) character in the above table means that the 349 functionality has not been implemented yet. 350 351 * Drawing with PostScript 352 353 As a convention, operators beginning with dot are non-standard, low 354 level operators which are subject to change. 355 356 There is a difference in how HTML 5 canvas, PostScript and PDF measure 357 angles: 358 359 | language/device | unit | 360 |-----------------+------| 361 | canvas | rad | 362 | PostScript | deg | 363 | PDF | rad | 364 365 Many of the examples below set up their bounding box using the 366 =.gbox= operator, e.g. 367 368 #+begin_src ps 369 0 0 180 180 .gbox 370 #+end_src 371 372 Only the width and height of the canvas clipping rectangle are taken 373 into account so far. The width and height is related to the drawing 374 units rather than to the size of the canvas element. 375 376 Both PostScript and PDF documents have the origin of the coordinate 377 system in the bottom left corner while HTML 5 canvas in the top left 378 corner. Thus, some of the following pictures are displayed upside 379 down unless an explicit coordinate transformation was added. This 380 discrepancy between the origin of the coordinate system is a problem 381 when drawing text because a simple coordinate transformation on its 382 own would draw the text upside-down. 383 384 ** Bowtie example 385 386 See the [[https://developer.mozilla.org/en/drawing_graphics_with_canvas#section_6][original example]] in JavaScript. 387 388 #+html: <canvas id="xbowtie"></canvas> 389 #+html: <div id="bowtie"> 390 #+include "bowtie.wps" src ps 391 #+html: </div> 392 #+html: <script>wps.parse("save (xbowtie) .setGc", $$("bowtie"), "restore");</script> 393 394 ** Analog clock example 395 396 See the [[http://oreilly.com/openbook/cgi/ch06_02.html][original example]]. 397 398 Click on the clock to start/stop it. (If using Chrome, you might need 399 to reload the page for this to work. Not sure why?) 400 401 #+html: <canvas id="xclock2"></canvas> 402 403 #+html: <div id="clock2"> 404 #+include "clock2.wps" src ps 405 #+html: </div> 406 #+html: <script>(new Wps).parse($$("wps"), "(xclock2) .setGc", $$("clock2"));</script> 407 408 Running the clock keeps the CPU noticeably busy. Chrome is best with 409 very little overhead. Firefox and Opera perform significantly worse. 410 WPS seems to be fast enough for one-off drawings, but its usability 411 depends on the efficiency of the host JavaScript interpreter when 412 running the interpreter in a tight loop. 413 414 ** Fill example 415 416 See the [[https://developer.mozilla.org/samples/canvas-tutorial/4_1_canvas_fillstyle.html][original example]] in JavaScript. 417 418 #+html: <canvas id="xfill"></canvas> 419 #+html: <div id="fill"> 420 #+include "fill.wps" src ps 421 #+html: </div> 422 #+html: <script>wps.parse("save (xfill) .setGc", $$("fill"), "restore");</script> 423 424 ** Tiger example 425 426 The [[http://svn.ghostscript.com/viewvc/trunk/gs/examples/tiger.eps?view=co][original example]] is included with [[http://ghostscript.com/][Ghostscript]]. 427 428 #+begin_html 429 <canvas id="xtiger" style="width:283pt;height:369pt"> 430 </canvas> 431 <p>Drawing took <span id="msg">--</span> seconds.</p> 432 #+end_html 433 434 #+html: <div id="tiger" style="display:none"> 435 #+include "tiger.eps" src text 436 #+html: </div> 437 438 #+begin_html 439 <div id="tiger1" style="display:none"> 440 (xtiger) .setGc 441 0 0 567 739 .gbox 442 1 0 0 -1 0 739 .transform 443 /time1 .date (getTime) 0 .call def 444 </div> 445 446 <div id="tiger2" style="display:none"> 447 /time2 .date (getTime) 0 .call def 448 (msg) .getElementById (textContent) time2 time1 sub 1000 div put 449 </div> 450 451 <script> 452 function tiger() {(new Wps).parse($$("wps"), $$("tiger1"), $$("tiger"), $$("tiger2"));} 453 </script> 454 <button onclick="javascript:tiger();">Draw</button> the tiger (be patient). 455 #+end_html 456 457 Is this an interesting JavaScript and canvas benchmark? 458 459 #+plot: title:"tiger.eps drawing times" ind:1 deps:(2 3 4) type:2d with:histograms set:"yrange [0:]" set:"xlabel 'browser'" set:"ylabel 'time [s]'" set:"style histogram gap 3" file:"tiger.png" set:"term png size 600, 300" 460 | browser | WPS time [s] | WPS time (no bind) [s] | PostCanvas time [s] | 461 |-------------+--------------+------------------------+---------------------| 462 | Chrome | 2.7 | 4.1 | 1.6 | 463 | Opera | 17.9 | 12.3 | 0 | 464 | Firefox 3.0 | 21.0 | 19.0 | 7.0 | 465 | Firefox 3.5 | 13.0 | 9.5 | 3.3 | 466 | Safari | 2.9 | 0 | 0 | 467 468 The above times were on Vaio T7200 Core 2 2GHz 2GB running Ubuntu. 469 470 [[http://www.feiri.de/pcan/][PostCanvas]] runs this [[http://www.feiri.de/pcan/example1.html][example]] about 1.5 times (Chrome) to 3 times 471 (Firefox) faster. I am actually surprised that WPS runs only about 472 1.5 times slower in Chrome even though it interprets almost everything 473 with minimal number of operators coded directly in JavaScript 474 (compared to PostCanvas which implements all operators directly in 475 JavaScript). Time for Safari was reported by Will King and even 476 though it was not run on the same machine as the other tests, it shows 477 that the speed is comparable to Chrome. 478 479 Another surprise to me is that I expected more significant speed up 480 after implementing the {{{ps(bind)}}} operator. Why does Opera and 481 Firefox get slower in this case? 482 483 It should be fairly easy to speed WPS up by coding more operators 484 directly in JavaScript. The speed of PostCanvas could probably be 485 taken as the best case that could be achieved by optimizing WPS. 486 487 file:tiger.png 488 489 Note by Dave Chapman: 490 491 #+begin_quote 492 493 I've found that reducing the number of function calls in complex 494 scripts has by far the biggest gains in speed - but I guess you 495 already know this. For instance, when I run the Tiger demo it takes 496 about 19sec on my machine (FF3.0, dual core, 4gb ram) but according to 497 the firebug profiler it's making nearly 4 million function calls (as a 498 comparison PostCanvas is *only* making about 220,000 calls). 499 500 #+end_quote 501 502 Note by Ray Johnson: 503 504 #+begin_quote 505 506 Tested Safari 4.0.4 (Win) and Firefox 3.5.5 (Win): 507 508 - Safari 4.0.4 Tiger drawing time = 1.76 509 - Firefox 3.5.5 Tiger drawing time = 6.945 510 511 I’m on a Dell T7400 Xeon Quad Core 3.0 GHz with 4GB Ram and Vista SP2 32 Bit-and 512 513 #+end_quote 514 515 Firefox throws error about linecap and linejoin not being supported so 516 these were not used here. Opera throws an error when running the 517 PostCanvas example. The tiger does not look the same as rendered by 518 [[http://projects.gnome.org/evince/][Evince]] ([[http://poppler.freedesktop.org/][poppler]]/[[http://cairographics.org/][cairo]]) so maybe the linecap and linejoin are 519 really needed to get proper image as intended. 520 521 It is also interesting to observe that PDF operators and their names 522 probably came up from shortening/compressing common "user-space" 523 PostScript operators in PostScript files. The tiger.eps file was 524 created in 1990 and contains some "shortcuts" that match PDF operators 525 standardised later. 526 527 * Drawing with PDF 528 529 PDF is rather complex format. WPS aims to implement only drawing 530 operators that can be present in PDF content streams. The number of 531 these operators is fixed and limited. Even though the full PostScript 532 language is not required, it can be convenient to implement them in 533 PostScript. 534 535 However, some aspects (e.g. colors) are handled differently in PDF 536 compared to PostScript and these differences are not addressed by WPS. 537 I imagine that a supporting server-side solution like [[../ondoc/index.org][OnDoc]] would 538 provide necessary data (e.g. decomposing PDF into pages and objects, 539 providing HTML 5 web fonts and font metrics) and WPS would only draw 540 preprocessed page content. 541 542 Quoting from [[http://www.adobe.com/print/features/psvspdf/index.html][Adobe]]: 543 544 #+begin_quote 545 A PDF file is actually a PostScript file which has already been 546 interpreted by a RIP and made into clearly defined objects. 547 #+end_quote 548 549 ** Heart example 550 551 See also the [[https://developer.mozilla.org/samples/canvas-tutorial/2_6_canvas_beziercurveto.html][original example]] in JavaScript. 552 553 #+html: <canvas id="xheart"></canvas> 554 #+html: <div id="heart"> 555 #+include "heart.wps" src ps 556 #+html: </div> 557 #+html: <script>wps.parse("save (xheart) .setGc", $$("heart"), "restore");</script> 558 559 ** Rectangle example 560 561 TODO find the original example 562 563 #+html: <canvas id="xrect"></canvas> 564 #+html: <div id="rect"> 565 #+include "rect.wps" src ps 566 #+html: </div> 567 #+html: <script>wps.parse("save (xrect) .setGc", $$("rect"), "restore");</script> 568 569 ** Triangles example 570 571 See also the [[https://developer.mozilla.org/samples/canvas-tutorial/2_3_canvas_lineto.html][original example]] in JavaScript. 572 573 #+html: <canvas id="xtriangles"></canvas> 574 #+html: <div id="triangles"> 575 #+include "triangles.wps" src ps 576 #+html: </div> 577 #+html: <script>wps.parse("save (xtriangles) .setGc", $$("triangles"), "restore");</script> 578 579 ** Smile example 580 581 See also the [[http://developer.mozilla.org/samples/canvas-tutorial/2_2_canvas_moveto.html][original example]] in JavaScript. 582 583 #+html: <canvas id="xsmile"></canvas> 584 #+html: <div id="smile"> 585 #+include "smile.wps" src ps 586 #+html: </div> 587 #+html: <script>wps.parse("save (xsmile) .setGc", $$("smile"), "restore");</script> 588 589 ** Star example 590 591 See also the [[http://www.adobe.com/technology/pdfs/presentations/KingPDFTutorial.pdf][original PDF document]] where this example is presented. 592 593 #+html: <canvas id="xstar"></canvas> 594 #+html: <div id="star"> 595 #+include "star.wps" src ps 596 #+html: </div> 597 #+html: <script>wps.parse("save (xstar) .setGc", $$("star"), "restore");</script> 598 599 ** Squares example 600 601 See also the [[https://developer.mozilla.org/samples/canvas-tutorial/5_1_canvas_savestate.html][original example]] in JavaScript. 602 603 #+html: <canvas id="xsquares"></canvas> 604 #+html: <div id="squares"> 605 #+include "squares.wps" src ps 606 #+html: </div> 607 #+html: <script>wps.parse("save (xsquares) .setGc", $$("squares"), "restore");</script> 608 609 ** Two squares example 610 611 See also the [[https://developer.mozilla.org/en/drawing_graphics_with_canvas][original example]] in JavaScript. 612 613 #+html: <canvas id="xsquares2"></canvas> 614 #+html: <div id="squares2"> 615 #+include "squares2.wps" src ps 616 #+html: </div> 617 #+html: <script>wps.parse("save (xsquares2) .setGc", $$("squares2"), "restore");</script> 618 619 * Operators and JavaScript bindings 620 621 WPS implements a minimum core in JavaScript and the rest is 622 implemented in PostScript itself. 623 624 Many JavaScript data types map quite easily to PostScript data types 625 so native bindings can be implemented mostly in PostScript via 626 PostScript dictionaries (JavaScript objects). [[http://www.whatwg.org/specs/web-apps/current-work/#the-canvas-element][HTML 5 canvas API]] 627 bindings are quite straightforward. 628 629 ** Native operators 630 631 | category | in | operator | out | 632 |----------------+------------------------+-----------------------+-----------------------------------------------------| 633 | Trivial | | {{{ps(true)}}} | true | 634 | | | {{{ps(false)}}} | false | 635 | | | {{{ps(null)}}} | null | 636 | Math | x y | {{{ps(sub)}}} | x-y | 637 | | x y | {{{ps(mul)}}} | x*y | 638 | | x y | {{{ps(div)}}} | x/y | 639 | | x y | {{{ps(mod)}}} | x%y | 640 | Stack | | {{{ps(mark)}}} | mark | 641 | | | {{{ps(counttomark)}}} | n | 642 | | x y | {{{ps(exch)}}} | y x | 643 | | ... | {{{ps(clear)}}} | | 644 | | x | {{{ps(pop)}}} | | 645 | | x_n ... x_0 n | {{{ps(index)}}} | x_n ... x_0 x_n | 646 | | x_(n-1) ... x_0 n j | {{{ps(roll)}}} | x_((j-1) mod n) ... x_0 ... x_(n-1) ... x_(j mod n) | 647 | | x_1 ... x_n n | {{{ps(copy)}}} | x_1 ... x_n x_1 ... x_n | 648 | Array | array | {{{ps(length)}}} | n | 649 | | x_n ... x_0 array | {{{ps(astore)}}} | array | 650 | | n | {{{ps(array)}}} | array | 651 | Conditionals | x y | {{{ps(eq)}}} | bool | 652 | | x y | {{{ps(lt)}}} | bool | 653 | Control | bool then else | {{{ps(ifelse)}}} | | 654 | | n proc | {{{ps(repeat)}}} | | 655 | | i j k proc | {{{ps(for)}}} | | 656 | | array/dict/string proc | {{{ps(forall)}}} | | 657 | | any | {{{ps(exec)}}} | | 658 | | any | {{{ps(cvx)}}} | any | 659 | | any | {{{ps(cvlit)}}} | any | 660 | Dictionary | n | {{{ps(dict)}}} | dict | 661 | | dict key | {{{ps(get)}}} | any | 662 | | dict key any | {{{ps(put)}}} | | 663 | | dict | {{{ps(begin)}}} | | 664 | | | {{{ps(end)}}} | | 665 | | | {{{ps(currentdict)}}} | dict | 666 | | sym | {{{ps(where)}}} | false / dict true | 667 | Miscellaneous | | {{{ps(save)}}} | dstack | 668 | | dstack | {{{ps(restore)}}} | | 669 | | any | {{{ps(type)}}} | name | 670 | | bool | .strictBind | | 671 | | any | {{{ps(bind)}}} | any | 672 | Debugging | x | {{{ps(=)}}} | | 673 | | x | {{{ps(==)}}} | | 674 | | | {{{ps(stack)}}} | | 675 | | | {{{ps(pstack)}}} | | 676 | JavaScript FFI | x_1 ... x_n dict key n | .call | any | 677 | | | .math | Math | 678 | | | .date | (new Date) | 679 | | | .window | window | 680 | | proc | .callback | callback | 681 | HTML | m | .minv | m^-1 | 682 | | m_1 m_2 | .mmul | (m_1 x m_2) | 683 | | x y m | .xy | x' y' | 684 | | r g b | .rgb | text | 685 | | r g b a | .rgba | text | 686 687 Some of the above operators could still be implemented in PostScript 688 instead of directly in JavaScript. 689 690 ** Core operators 691 692 TODO update 693 694 | category | in | operator | out | | 695 |--------------+-------------+--------------------+--------+---| 696 | Math | | {{{ps(abs)}}} | | | 697 | | | .acos | | | 698 | | | .asin | | | 699 | | | {{{ps(atan)}}} | | | 700 | | | .atan2 | | | 701 | | | {{{ps(ceiling)}}} | | | 702 | | | {{{ps(cos)}}} | | | 703 | | | .exp | | | 704 | | | {{{ps(floor)}}} | | | 705 | | | {{{ps(log)}}} | | | 706 | | | .max | | | 707 | | | .min | | | 708 | | | .pow | | | 709 | | | .random | | | 710 | | | {{{ps(rand)}}} | | | 711 | | | {{{ps(round)}}} | | | 712 | | | {{{ps(sin)}}} | | | 713 | | | {{{ps(sqrt)}}} | | | 714 | | | .tan | | | 715 | | | {{{ps(truncate)}}} | | | 716 | | | .e | | | 717 | | | .ln2 | | | 718 | | | .ln10 | | | 719 | | | .log2e | | | 720 | | | .log10e | | | 721 | | | .pi | | | 722 | | | .sqrt1_2 | | | 723 | | | .sqrt2 | | | 724 | | | {{{ps(sub)}}} | | | 725 | | | {{{ps(idiv)}}} | | | 726 | | num/string | {{{ps(cvr)}}} | real | | 727 | | num/string | {{{ps(cvi)}}} | int | | 728 | Stack | x | {{{ps(dup)}}} | x x | | 729 | Conditionals | x y | {{{ps(ne)}}} | bool | | 730 | | x y | {{{ps(ge)}}} | bool | | 731 | | x y | {{{ps(le)}}} | bool | | 732 | | x y | {{{ps(gt)}}} | bool | | 733 | | bool proc | {{{ps(if)}}} | | | 734 | HTML 5 | key | .gget | | | 735 | | any key | .gput | | | 736 | | key nargs | .gcall0 | | | 737 | | key nargs | .gcall1 | | | 738 | | | .gcanvas | canvas | | 739 | | w h | .gdim | | | 740 | | x0 y0 x1 y1 | .gbox | | | 741 742 ** HTML 5 canvas methods and attributes 743 744 *** Canvas methods 745 746 | | in | canvas | out | ps | pdf | 747 |---+----------------------------------------------+-----------------------+----------------+--------------------------------+-------------| 748 | / | | | | < | < | 749 | | | .save | | {{{ps(gsave)}}} | q | 750 | | | .restore | | {{{ps(grestore)}}} | Q | 751 | | x y | .scale | | {{{ps(scale)}}} | - | 752 | | angle | .rotate | | {{{ps(rotate)}}} | - | 753 | | x y | .translate | | {{{ps(translate)}}} | - | 754 | | m11 m12 m21 m22 dx dy | .transform | | - | cm | 755 | | m11 m12 m21 m22 dx dy | .setTransform | | - | - | 756 | | x0 y0 x1 y1 | .createLinearGradient | canvasGradient | | | 757 | | x0 y0 r0 x1 y1 r1 | .createRadialGradient | canvasGradient | | | 758 | | image repetition | .createPattern | canvasPattern | | | 759 | | x y w h | .clearRect | | {{{ps(rectclip)}}} | | 760 | | x y w h | .fillRect | | {{{ps(rectfill)}}} | | 761 | | x y w h | .strokeRect | | {{{ps(rectstroke)}}} | | 762 | | | .beginPath | | {{{ps(newpath)}}} | m ? | 763 | | | .closePath | | {{{ps(closepath)}}} | ~ h ? ~ n ? | 764 | | x y | .moveTo | | {{{ps(moveto)}}} | m ? | 765 | | x y | .lineTo | | {{{ps(lineto)}}} | l | 766 | | cpx cpy x y | .quadraticCurveTo | | | | 767 | | cp1x cp1y cp2x cp2y x y | .bezierCurveTo | | | c | 768 | | x1 y1 x2 y2 radius | .arcTo | | {{{ps(arcto)}}} | | 769 | | x y w h | .rect | | - | ~ re | 770 | | x y radius startAngle endAngle anticlockwise | .arc | | ~ {{{ps(arc)}}} {{{ps(arcn)}}} | | 771 | | | .fill | | {{{ps(fill)}}} | ~ f ? | 772 | | | .stroke | | {{{ps(stroke)}}} | S | 773 | | | .clip | | {{{ps(clip)}}} | ~ W ? | 774 | | x y | .isPointInPath | boolean | | | 775 | | text x y | .fillText1 | | | | 776 | | text x y maxWidth | .fillText2 | | | | 777 | | text x y | .strokeText1 | | | | 778 | | text x y maxWidth | .strokeText2 | | | | 779 | | text | .measureText | textMetrics | | | 780 | | image dx dy | .drawImage1 | | | | 781 | | image dx dy dw dh | .drawImage2 | | | | 782 | | image sx sy sw sh dx dy dw dh | .drawImage3 | | | | 783 | | imagedata | .createImageData1 | imageData | | | 784 | | sw sh | .createImageData1 | imageData | | | 785 | | sx sy sw sh | .getImageData | imageData | | | 786 | | imagedata dx dy | .putImageData1 | | | | 787 | | imagedata dx dy dirtyX dirtyY dirtyW dirtyH | .putImageData2 | | | | 788 789 *** Canvas attributes 790 791 | | type | attribute | values | ps | pdf | 792 |---+------+---------------------------+----------------------------------------------------+-------------------------+-------| 793 | / | | < | | < | < | 794 | | num | .globalAlpha | (1.0) | | | 795 | | str | .globalCompositeOperation | (source-over) | | | 796 | | any | .strokeStyle | (black) | ~ {{{ps(setdash)}}} ? | ~ d ? | 797 | | any | .fillStyle | (black) | | | 798 | | num | .lineWidth | (1) | {{{ps(setlinewidth)}}} | w | 799 | | str | .lineCap | (butt) round square | ~ {{{ps(setlinecap)}}} | J | 800 | | str | .lineJoin | round bevel (miter) | ~ {{{ps(setlinejoin)}}} | j | 801 | | num | .miterLimit | (10) | {{{ps(setmiterlimit)}}} | M | 802 | | num | .shadowOffsetX | (0) | | | 803 | | num | .shadowOffsetY | (0) | | | 804 | | num | .shadowBlur | (0) | | | 805 | | str | .shadowColor | (transparent black) | | | 806 | | str | .font | (10px sans-serif) | | | 807 | | str | .textAlign | (start) end left right center | | | 808 | | str | .textBaseline | top hanging middle (alphabetic) ideographic bottom | | | 809 810 *** Other operators 811 812 | | in | canvas | out | ps | pdf | 813 |---+-----------------------------+---------------+-----+----+-----| 814 | / | | < | | | | 815 | | canvasGradient offset color | .addColorStop | | | | 816 817 *** Other attributes 818 819 | | dict | type | attribute | values | ps | pdf | 820 |---+------------------+------------------+-----------+--------+----+-----| 821 | / | | | < | | < | < | 822 | | textMetrics | num | width | | | | 823 | | imageData | cnt | width | | | | 824 | | imageData | cnt | heigth | | | | 825 | | imageData | canvasPixelArray | data | | | | 826 | | canvasPixelArray | cnt | length | | | | 827 828 TODO [IndexGetter, IndexSetter] CanvasPixelArray 829 830 ** PostScript operators 831 832 TODO update 833 834 | | category | in | operator | out | 835 |---+----------+---------+-----------------------+-----| 836 | / | | < | < | < | 837 | | | x y [m] | {{{ps(transform)}}} | x y | 838 | | | x y [m] | {{{ps(itransform)}}} | x y | 839 | | | gray | {{{ps(setgray)}}} | | 840 | | | r g b | {{{ps(setrgbcolor)}}} | | 841 | | | ??? | {{{ps(setfont)}}} ? | | 842 | | | | {{{ps(clippath)}}} ? | | 843 | | | text | {{{ps(show)}}} ? | | 844 | | | x y | {{{ps(rlineto)}}} | | 845 846 ** PDF operators 847 848 | | category | operator | meaning | 849 |---+------------------------+----------+---------------------------------------------| 850 | / | | < | | 851 | | General graphics state | w | setlinewidth | 852 | | | J | ~ setlinecap | 853 | | | j | ~ setlinejoin | 854 | | | M | setmiterlimit | 855 | | | d | ~ setdash ? | 856 | | | ri | | 857 | | | i | 1 .min setflat | 858 | | | gs | | 859 | | Special graphics state | q | gsave | 860 | | | Q | grestore | 861 | | | cm | .transform | 862 | | Path construction | m | moveto | 863 | | | l | lineto | 864 | | | c | .bezierCurveTo (~ curveto) | 865 | | | v | currentpoint 6 2 roll c | 866 | | | y | 2 copy c | 867 | | | h | closepath | 868 | | | re | ! x y m , x+w y l , x+w y+h l , x y+h l , h | 869 | | Path painting | S | stroke | 870 | | | s | h S | 871 | | | f | ~ fill | 872 | | | F | f | 873 | | | f* | ~ eofill | 874 | | | B | f S ! q f Q S | 875 | | | B* | f* S ! q f* Q S | 876 | | | b | h B | 877 | | | b* | h B* | 878 | | | n | ~ newpath | 879 | | Clipping paths | W | clip | 880 | | | W* | eoclip | 881 | | Text objects | BT | ~ q | 882 | | | ET | ~ Q | 883 | | Text state | Tc | | 884 | | | Tw | | 885 | | | Tz | | 886 | | | TL | | 887 | | | Tf | | 888 | | | Tr | | 889 | | | Ts | | 890 | | Text positioning | Td | | 891 | | | TD | | 892 | | | Tm | | 893 | | | T* | | 894 | | Text showing | Tj | ~ show | 895 | | | TJ | | 896 | | | ' | | 897 | | | " | | 898 | | Type 3 fonts | d0 | setcharwidth | 899 | | | d1 | setcachedevice | 900 | | Color | CS | | 901 | | | cs | | 902 | | | SC | | 903 | | | SCN | | 904 | | | sc | | 905 | | | scn | | 906 | | | G | g | 907 | | | g | setgray | 908 | | | RG | rg | 909 | | | rg | setrgbcolor | 910 | | | K | k | 911 | | | k | setcmykcolor | 912 | | Shading patterns | sh | | 913 | | Inline images | BI | | 914 | | | ID | | 915 | | | EI | | 916 | | XObjects | Do | | 917 | | Marked content | MP | | 918 | | | DP | | 919 | | | BMC | | 920 | | | BDC | | 921 | | | EMC | | 922 | | Compatibility | BX | | 923 | | | EX | | 924 925 * Supported Browsers 926 927 I have tried the following browsers so far: 928 929 | | Browser | Version | Note | 930 |---+---------+---------------------------------+------------------------------------| 931 | / | | < | | 932 | | Firefox | 3.0.11 | no text drawing, linecap, linejoin | 933 | | | 3.5b4pre | ~ same as Firefox 3.0.11? | 934 | | | 3.5.5 Win | reported by Ray Johnson | 935 | | Opera | 10.00 Beta | no text drawing, ugly aliasing | 936 | | Chrome | 3.0.189.0 | lines not joined properly | 937 | | Safari | for Mac Version 4.0.2 (5530.19) | reported by Will King | 938 | | | 4.0.4 Win | reported by Ray Johnson | 939 940 If you are using a different browser, please [[http://logand.com/contact.html][let me know]] if it works 941 for you. 942 943 * Limitations and Known Issues 944 945 - many PostScript operators are still to be implemented 946 - only small fraction of PDF operators has been implemented 947 - text drawing and font related functionality has not been implemented 948 949 * Changes 950 951 2009-07-15 v0.2 952 953 - Capable of drawing tiger.eps 954 - JavaScript callbacks and timer added 955 - bind operator implemented 956 - Refactored JavaScript code: parser, evaluator and PostScript 957 interpreter separated 958 - Improved documentation 959 960 2009-06-30 v0.1 961 962 - Initial version 963 964 * Links 965 966 Discussions about WPS on [[http://www.reddit.com/r/programming/comments/95xll/wps_postscript_and_pdf_interpreter_for_html_5/][reddit]] and [[http://ajaxian.com/archives/wps-postscript-and-pdf-interpreter-for-html-5-canvas][ajaxian]]. 967 968 [[http://www.feiri.de/pcan/][PostCanvas]] is a RPN interpreter with many PostScript operators 969 implemented directly in JavaScript. It is faster than WPS but not a 970 "real" PostScript. 971 972 [[http://svgkit.sourceforge.net/][SVGKit]] has a PostScript interpreter on the wish list. 973 974 PostScript is a registered trademark of [[http://www.adobe.com][Adobe Systems Incorporated]].