Excellent advice! Your setmatrix advice is unintuitive, but I believe you. The JavaScript/C barrier is expensive, while JavaScript calling JavaScript is optimized up the yin-yang.

I love canvas because I cut my teeth on PostScript, and it's basically the PostScript imaging model grown up.

What you said about minimizing graphics state changes is an important point, that also applies to PostScript. And a lot of PostScript optimization advice also applies to canvas/JavaScript too, because they're similar in many ways.

Glenn Reid (who worked for Adobe, and was the author of the "Green Book" on PostScript language program design) wrote "The Distillery", which I've written about before on HN (link and excerpt below), that was a PostScript program that loaded in and partially evaluated another PostScript drawing program, and output another program that drew the exact same thing, only (usually) much faster and usually smaller. Unless you had any loops, which it would unroll.

https://www.donhopkins.com/home/archive/news-tape/utilities/...

One of the most important optimizations it did was to transform all the graphics into the same default coordinate system, and optimize out not only graphics state changes but also importantly calls to gsave/grestore, which could be quite common.

https://news.ycombinator.com/item?id=21988195

Glenn Reid described his classic "Grandfather Clock" metaphor to comp.lang.postscript that really opened my eyes about writing code in interpreted languages like PostScript (and JavaScript, while JIT'ed at runtime, still makes calling native code expensive), and balancing optimization with readability. With modern JavaScript/canvas, JavaScript code that calls other JavaScript code runs really fast, but calling between JavaScript and built-in code is still slow, so it's good to have the built-in code do as much as possible when you do (like rendering a long string of text at once, instead of rendering it word by word like many PostScript drivers would):

Glenn's post in a comp.lang.postscript discussion about PostScript programming style and optimization:

https://groups.google.com/forum/#!search/%22glenn$20reid%22$...

From: Glenn Reid (Abode Systems)
Newsgroup: comp.lang.postscript
Subject: Re: An Idea to Help Make Postscript Easier to Read (and Write)
Date: 10 Sep 88 17:26:24 GMT

You people tend to forget that the PostScript language is interpreted.
It is well and good to use tools to convert to and from PostScript,
but it is not quite as "transparent" as we all might think.
I like to think of a big grandfather clock, with the pendulum swinging.
Each time pendulum swings, the PostScript interpreter gets to do one
operation. The "granularity" of the clock is nowhere near the speed
of a microprocessor instruction set, and any comparison with assembly
languages doesn't make sense.

The difference between:

0 0 moveto

and

0 0 /arg2 exch def /arg1 exch def arg1 arg2 moveto

can sort of be measured in "ticks" of the interpreter's clock. It's
not quite this simple, since simply pushing a literal is faster than
executing a real PostScript operator, but it is a rough rule of thumb.
It will take about three times as long to execute the second of these
in a tight loop, and about five times as long if it is transmitted and
scanned each time. My rule of thumb is that if you have roughly the
same number of tokens in your stack approach as you do with your 'exch
def' approach, the 'exch def' is likely to be much more readable and
better. Otherwise, I usually go with the stack approach.

One other thing of note is that if you have too much stack manipulation
going on, it may well be symptomatic of a problem in the original program
design.

Also, most procedures don't do any stack manipulation at all, they
simply use their arguments directly from the stack. In this situation,
it is especially wasteful (and confusing, I think) to declare
intermediate variables.

Compare:

% sample procedure call:
(Text) 100 100 12 /Times-Roman SETTEXT

% approach 1:
/SETTEXT { %def
findfont exch scalefont setfont moveto show
} def

% approach 2:
/SETTEXT { %def
/arg5 exch def
/arg4 exch def
/arg3 exch def
/arg2 exch def
/arg1 exch def
arg5 findfont arg4 scalefont setfont
arg2 arg3 moveto arg1 show
} def

Which of these is easier for you to understand?

Anyway, I think the discussion is a good one, but let's not forget
that PostScript it is an interpreted language. And I don't think
it is terribly hard to use and understand, if it is written well.

Glenn Reid
Adobe Systems

Here's Glenn's "Green Book", which was like a bible to me, and still is quite relevant to canvas 2d context programming -- see especially page 9, section 1.5, Program Design Guidelines, page 72, section 4.6, Optimizing Translator Output, and page 99, chapter 7, The Mechanics of Setting Text:

https://www-cdf.fnal.gov/offline/PostScript/GREENBK.PDF

>page 9: 1.5 Program Design Guidelines

>There are a few items that may be kept in mind while implementing a driver for a PostScript device. As with most software development, the most difficult part of writing programs in the PostScript language is the design of the program. If the design is good, implementing it is easy. If the design is poor, it may not even be possible to correctly implement it. Below are some helpful items to keep in mind when writing your software. All of them are explained more fully within the text of this book; this is only an attempt to prime the pump before you start reading:

>• Use the operand stack efficiently. Pay particular attention to the order of the elements on the stack and how they are used.

(Using the stack efficiently by designing fluent words that chain and dovetail together elegantly in pipelines (and systematically writing line-by-line stack comments) instead of using named variables in dictionaries is good idiomatic PostScript and Forth, aka tacit programming or point-free style, the stack-based equivalent of fluent interfaces.)

https://en.wikipedia.org/wiki/Tacit_programming

https://en.wikipedia.org/wiki/Fluent_interface

>• Avoid unnecessary redundancy. When a program is produced, check for many repetitive steps that perhaps could be condensed. Keep identifiers short if they are to be transmitted many times.

(Only create paths once!)

>• Use the PostScript imaging model effectively. When printing, a document must be translated into the language of the printer. This includes a philosophical adjustment to the nature of the PostScript imaging model.

(Use the graphics state stack!)

>• It is better to translate into the PostScript imaging model than to maintain another set of graphics primitives using the PostScript language for emulation.

The hardest problem I ever tried (and failed) to solve properly with PostScript was making a printer driver for rendering user interfaces drawn with X11 using a combination of bitmaps and lines, that looked perfect on the screen at 1:1 scale, but didn't look terrible when you zoomed into them or printed them at high resolution on paper. Because of X11 "half open" pixel rounding rules versus PostScript's "stencil paint" model, they just don't line up right when you zoom into them, and there's no fudge or compromise that works in all cases. Here is my commented-out failed attempt:)

https://github.com/mmontone/garnet/blob/1652af38f76b1c4efb19...

line-color line-cap line-join dash-pattern
thickness
% dup -1 ne { .5 add } if % fudge outline width thicker
StrokeShape

>page 100: Note: There is one principle to keep in mind when deciding upon an algorithm for setting text. The longer the string presented to one of the show operators, the more efficient the system is likely to be. This is because the PostScript language built-in operators, such as show, widthshow, and ashow, operate essentially at compiled speed once they have been invoked. Each moveto or div operation performed must first be interpreted, which is significantly slower.

Here's a description of Glenn's PostScript Distillery, which foreshadowed Acrobat Distiller.

https://news.ycombinator.com/item?id=28115946

>Glenn Reid wrote a PostScript partial evaluator in PostScript that optimized other PostScript drawing programs, called "The Distillery". You would send still.ps to your PostScript printer, and then send another PostScript file that drew something to the printer. The first PostScript Distillery program would then partially evaluate the second PostScript drawing program, and send back a third PostScript program, an optimized drawing program, with all the loops and conditionals unrolled, calculations and transformations pre-computed, all in the same coordinate system.

>It was originally John Warnock's idea, that Glenn implemented. And it led to Adobe Acrobat's "Distiller". Acrobat is basically PostScript without the programming language.

>No, you could not make it optimize itself by sending it to a PostScript printer two times in a row. It was not magic: all it did was intercept and capture the side-effects of the PostScript drawing commands (fill, stroke, show), read out the path, and optimize it in a uniform coordinate system. Since it didn't do any drawing, so it would just output an empty program if run on itself. (Take that, halting problem!)

https://donhopkins.com/home/archive/postscript/newerstill.ps...

>From: greid@adobe.com (Glenn Reid) Newsgroups: comp.lang.postscript Subject: release 10 of the Distillery Date: 10 Mar 89 10:21:52 GMT

>Here is another release of the PostScript Language Distillery. I know it's not terribly long after the last release, but there are some significant enhancements, and I thought it would be worthwhile.

>I finally took a closer look at user-defined fonts, which now seem to be working fairly well. In particular, it seems to handle the Macintosh screen bitmap fonts that get used if the native font is unavailable when the print file is genreated. The entire user-defined font is reverse-engineered to the output file as it stands, and is used exactly like the original file used it. I also fixed some rotate text bugs, rotated charpath, and a few other things.

>I want to emphasize that probably the two best uses of this program, currently, are to do speed comparisons of various PostScript language drivers and to convert "non-conforming" files into "conforming" files. It is not particularly well suited to carefully hand-written programs, especially not those which use looping constructs. It works (usually), but it unrolls the loops and makes the files much bigger.