Paste: pslib

Author: nlogax
Mode: postscript
Date: Mon, 18 Jun 2012 17:23:43
Plain Text |
%!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-<tag name> /Font-Name 11 mfont
% <tag name> 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

New Annotation

Summary:
Author:
Mode:
Body: