%!PS % Usage % Run it using a PostScript interpreter, either on your computer or a printer. % If you need an interpreter, check out GhostScript[1]. % If you use GhostScript, you can create a PDF by saying: % $ gs -q -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=some.pdf some.ps % If you are on OS X, you can just open the .ps file and Preview will make a PDF on the fly. % [1] http://www.ghostscript.com % % Features % Not many! But enough to get you started. I am a PostScript novice, trying to learn by implementing the basics. % There's an algorithm for full justification, automatic page breaks, and some procedures for paragraphs, headings etc. % There's a callback when new pages are created, `on:newpage', which you can use e.g. to create headers and footers. % Inside it, `PAGENUM' (integer) refers to the current page number. % All the automated typesetting stuff happens on a grid. If you don't like in-register setting, you will sad. % Patches, suggestions, or any other form of feedback is appreciated. % % Design rationale % I have tried to write it in a clear way, instead of the usual "shortest possible" style. % If there is a standard PS procedure for something, I try not to re-implement or alias it. % If you too are new to PostScript, I hope you find it helpful. % % Future plans % Automatic sectioning and table of contents generation. % More structured control over grid/page setup. % Fancier layouts: a box set through which text and content can flow. % Procedures for typesetting mathematical formulae. % Procedures for drawing pretty graphs. % Reading from `currentfile' and allowing embedded codes. % Hanging punctuation. % Font/encoding tools (needed to make OpenType math more usable). % Figure out how to make proper resources. % % Conventions used in this library % The stack effect of an operator is denoted as follows: % (str) 42 opname (str42) % This would take a string and an int from the stack, % % convert the int to a string, append it to the string, % % and leave the resulting string on the stack. % The arguments use standard PS syntax to denote types; (string), /name etc. % If an operator has multiple possible outcomes, they are separated by a `|': % (some words) dostuff (words) (some) true | (words) dostuff (words) false % A name used/modified by other operators is in UPPERCASE. % Destructive (e.g. re-binding a name to a new value) operators end with '!', % and boolean operators end with '?', Scheme-style. Callbacks are named `on:something', % where `something' is normally the name of the procedure doing the interesting thing. % Things that mimic LaTeX environments are bracketed with `begin:foo' and `end:foo'. % Workaround for some Preview.app issue I don't understand. TODO: Understand, remove. /Courier findfont setfont % Print n items, popping them from the stack. % (lol) (omg) (wtf) 2 dprint (lol) /dprint { { = } repeat } bind def % Decrement and redefine a name. /dec! { % /num 42 dec! - exch dup load 3 2 roll sub def } bind def /inc! { % /num 42 inc! - exch dup load 3 2 roll add def } bind def /map { % [1 2 3] { dup mul } map -> [1 4 9] mark 3 1 roll forall ] } bind def % 1 2 max 2 /max { dup 2 index gt { exch } if pop } bind def % 1 2 min 1 /min { dup 2 index lt { exch } if pop } bind def % Show and move back to previous point. /show- { currentpoint 3 2 roll show moveto } bind def % Grid stuff. /gridx 15 def /gridy 15 def % Print the grid, for debugging layout. /gridlinethin { 0.1 setlinewidth 0.1 0.075 0 0 setcmykcolor } bind def /gridlinethick { 0.5 setlinewidth 0.2 0.15 0 0 setcmykcolor } bind def /gridshow { gsave % Draw page boundary 1 setlinejoin gridlinethick newpath 0 0 moveto 0 pageheight rlineto pagewidth 0 rlineto 0 pageheight neg rlineto closepath stroke 0 setlinejoin % Draw grid 0 gridx pagewidth { dup dup gridx 4 mul mod 0 eq { gridlinethick } { gridlinethin } ifelse 0 moveto pageheight lineto stroke } for 0 gridy pageheight { dup dup gridy 4 mul mod 0 eq { gridlinethick } { gridlinethin } ifelse 0 exch moveto pagewidth exch lineto stroke } for grestore } bind def % Page setup. /pageheight gridx 52 mul def /pagewidth gridx 40 mul def % Concat into new string. (a) (b) -> (ab) /concat { exch dup length 2 index length add string dup dup 4 2 roll copy length 4 -1 roll putinterval } bind def % Concat and redefine. /name ( -- str) concat! (stuff in name -- str) /concat! { exch dup load 3 -1 roll concat def } bind def % Exec if exists. /nom excif - /excif { dup currentdict exch known { load exec } { pop } ifelse } bind def /PAGENUM 0 def /newpage { /on:newpage excif /PAGENUM 1 inc! PAGENUM 1 gt { grestore showpage } if gsave 6 6 6 pop translate /lineheight gridx def /textwidth gridy 26 mul def /textheight gridx 38 mul def % Automagic text position. /pagebottom gridy 7 mul def /posx gridx 7 mul def /posy textheight gridy 6 mul add def posx posy moveto resetline } bind def /lastpage { /on:newpage excif grestore showpage } bind def % Default callbacks. /on:newpage { %gridshow pageheader pagefooter } bind def /pageheader { gsave grestore } bind def /pagefooter { gsave (BOOBS) = 20 gridx mul 3 gridy mul PAGENUM 10 string cvs dup stringwidth pop 2 div 4 -1 roll exch sub 3 2 roll moveto show grestore } bind def /begin:doc { (begin:doc!) 1 dprint newpage } bind def /end:doc { (end:doc!) 1 dprint lastpage } bind def /autopage { posy pagebottom lt { (autopage!) 1 dprint newpage } if } bind def /munchword { /LINE exch spacechar concat concat! } bind def /resetbuf { /LINE () def } bind def /resetspace { /SPACELEFT textwidth def } bind def /resetline { resetbuf resetspace } bind def /eatspace { /SPACELEFT exch dec! } bind def /spaceleft? { SPACELEFT 0 gt } bind def /newline { resetline /posy lineheight dec! autopage posx posy moveto } bind def /putlinefj { LINE % (Buncha fine words. ) putline - dup dup spacecount 1 sub % 2x(Buncha fine words. ) 2 exch stringwidth pop % (Buncha fine words. ) 2 123 % Example width spacewidth sub % (Buncha fine words. ) 2 120 textwidth exch sub % (Buncha fine words. ) 2 234 % String width subtracted from line width. exch div 0 spaceint % (Buncha fine words. ) 117 0 ( ) % Leftover space divided between words. 4 -1 roll widthshow newline % 117 0 ( ) (Buncha fine words. % BOOM! } bind def % Last line can get special treatment. /putlast { LINE stringwidth pop textwidth lineheight sub ge { putline } % Do full justify if text fills almost entire line. { 0 0 spaceint LINE widthshow newline } ifelse } bind def /spacechar ( ) def /spaceint 32 def /spacewidth { spacechar stringwidth pop } bind def /spacecount { 0 exch % (a b c d) spacecount 3 { dup spaceint eq { exch 1 add exch } if pop } forall } bind def % (A string that is 42 points wide) measure 42 /measure { dup stringwidth pop spacewidth add } bind def % (Two words) nextword (words) ( ) (Two) true | (Single) nextword (Single) false /nextword { spacechar search } bind def /justify { { nextword exch measure dup eatspace spaceleft? not { putline eatspace } { pop } ifelse munchword { pop } { putlast exit } ifelse } loop } bind def /putline /putlinefj load def % /name /Font-Name 12 mfont - /mfont { [ 3 1 roll /exch cvx /findfont cvx /exch cvx /scalefont cvx /setfont cvx ] cvx bind def } bind def % Some helpers named after HTML elements. /H1 { font-H1 justify newline } bind def /P { font-P justify newline } bind def /P- { font-P justify } bind def /BR { newline } bind def % Maths, not usable. TODO: Make usable. /glyphwidth { gsave nulldevice 0 0 moveto glyphshow currentpoint grestore } bind def % Math block. /begin:math { } bind def /end:math { newline } bind def /mvar { font-math-i show font-math } bind def /mvar- { font-math-i show- font-math } bind def /mcentr { 2 div exch stringwidth pop 2 div sub 0 rmoveto } bind def /mfrac { % (numerator) (denominator) frac - gsave font-math 0 gridy neg 2 div rmoveto dup stringwidth pop exch mvar- exch 0.8315 setlinewidth 0 gridy rmoveto dup stringwidth pop exch mvar- 0 gridy neg 3 div rmoveto max 0 rlineto stroke grestore } bind def % Utilities for embedding external files. /begin:eps { /pre-eps-state save def /dictc countdictstack def /op_count count 1 sub def userdict begin /showpage { } bind def 0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin 10 setmiterlimit [ ] 0 setdash newpath /languagelevel where { pop languagelevel 1 ne { false setstrokeadjust false setoverprint } if } if } bind def /end:eps { count op_count sub { pop } repeat % Clean staxxx countdictstack dictc sub { end } repeat pre-eps-state restore } bind def /newlines { /newline load repeat } bind def % Remember to pick some fonts (no defaults yet). % Usage: /font- /Font-Name 11 mfont % is an operator that corresponds roughly to the HTML tag of that name. % /Font-Name is the PostScript name of the font. In OS X, see Font Book: http://i.imgur.com/T70Z3.png /font-H1 /Helvetica-Bold 18 mfont /font-P /Georgia 12 mfont % Your document starts here! begin:doc gridshow % Show the layout grid. Put it in `on:newpage' if you want in on every page! (Look, a heading!) H1 (A paragraph with some lorem ipsum dolor sit amet, consectetur adipisicing elit, \ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad \ minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.) P (It occurs to me that this example document should be a tutorial that prints itself.) P (Not much else to show, I guess. I'll draw a grid and add some more stuff so that another page is created.) P BR BR BR BR BR % Brrrrr. Can has HR too? (Weeeeee.) P BR BR BR BR BR (Stuff.) P (Stuff.) P (Stuff.) P (Stuff.) P (Stuff.) P (Stuff.) P (Stuff.) P % Around here, a new page is automatically created. (Is there another page now?) H1 (It would appear so! The automagic should have added a page number now, as well.) P end:doc