http://code.google.com/p/xpost/downloads/list
The rest is the README.
The new XPost mark 2
First it was flips (draft, rewrite, failed upgrade),
then podvig (draft with reference-counting),
then xpost (draft with separate pages for each save level),
and now xpost[2 (awaiting a better name).
It draws the fern!
try:
PS> (testg.ps)run fern
It'll show you the start and finish times, too.
my results:
PS>(testg.ps)run fern
13128
54450
and ghostscript's results (after commenting the initcanvas line)
1010(1)02:49 AM:mark2 0> gs testg.ps -c fern
GPL Ghostscript 8.62 (2008-02-29)
Copyright (C) 2008 Artifex Software, Inc. All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
0
8040
So it's a little more than 5 times slower.
Caveats:
It's not really packaged as an application, yet.
There's no 'make install'.
init.ps and err.ps must be in the current directory when run.
And it doesn't process command-line arguments of any kind.
You can pipe or redirect a file into it, and it should work,
but you'll get a screenful of bogus prompts. Or you can
run a file from the prompt with '(filename) run'.
Access restrictions aren't fully implemented.
Since the postscript-level error handler doesn't display the stacks,
I've left active the internal stack dump performed by the c-
level
error handler. This gives a spurious stack dump when you
terminate
a session with EOF, because it is at the postscript level
where undefinedfilename is treated as a request to quit the
executive.
The overall design is as follows
core.h - basic structures, macros, functions exported from the core
core.c - state structure, stacks, virtual memory, strings, arrays,
interpreter loop
- dic.h - dictionary functions
- dic.c
- file.h - file functions
- file.c
- tok.h - the scanner
- tok.c
- timer.h - functions for setting and checking a millisecond-
grain timer
- timer.c
op.h - operator functions
op.c
- grops.c - graphics operators
- grops.h
init.ps - procedures and variables in systemdict
- err.ps - the postscript part of the error handler
Schematically, it's kind of like this:
_____
/ \
timer \
/ dic \
| / \ \
| / \ |
main -- core -------- op ---- grops
| \ / |
| \ / |
\ tok /
/ \ /
| -- file ---
\
-- init.ps --- err.ps
core and op are both 'hubs', but op is subsidiary to core.
Everything's interconnected except tok, file, and dic are
isolated
from one another.
core.h defines the basic structures and enums and exports functions
and macros
from the core.
core.c implements the basic pieces, one on top of another.
Names are implemented as ternary search trees storing an index
into a dynamic (malloc) array of malloc'ed strings. This is
outside
of VM.
VM is implemented as an anonymous mmap. The allocator,
vmalloc,
adds an object sized header and trailer and pads the requested
size to an even multiple of objects. It returns an "address"
which is the byte offset from the start of the mmap page.
Copy-on-write semantics is implemented with a few macros
to check the header of the allocation and create a new clone
before modifying. Read operations have to loop to newest
clone.
Arrays and Strings are allocated in VM.
Objects are constructed with cons*() functions. For simple
objects,
the argument to the constructor is just the value (int,
double, bool),
and the constructor simply wraps that in an object with the
appropriate
tag. For composite objects, the constructor also receives a
pointer
to the interpreter state, through which it can access virtual
memory.
Any function that needs to access a value from a composite
objects
needs to go through the state to get to VM.
dic.c implements the hash table.
Space is allocated in VM for (n+1)*2 objects, and one pair
kept empty so the search loop can terminate on null.
tok.c implements the scanner.
The scanner uses a generic interface using a pointer to
a source object and pointers to getch and ungetch functions.
Internally, the scanner uses a handful of finite automatons
to check for valid number formats and falls back to a
character
switch to look for array or string or literal name delimiters.
Failing these, the string is converted to a name object.
timer.c implements the usertime operator.
mt_set() is called by TODO to set the base for measurements of
user program elapsed time. mt_elapse() is called by usertime()
and the difference between current and base times is returned
in milliseconds as an integer on the stack.
file.c implements the file system interface and special files.
statementedit and lineedit create a temporary file to copy
characters until their respective terminations and return
the file rewound and ready to read.
The file constructor uses an if/else chain of strcmps
to check for special file names, and then calls fopen().
It places the FILE * in VM, and returns an object with
the address of the FILE * as its payload.
op.c implements the operator constructor, handler, and most
of the operator functions themselves (all but graphics).
Operator functions are exported to other modules via
"shortcuts" containing the integer opcodes. And there
are only a few of these.
The constructor can be called with or without a function
pointer. Without a function, it returns an operator object
referring to a previously installed function with matching
name. With a function, installs a new prototype (or a new
operator if need be). consoper uses malloc to store the
type pattern strings.
The operator handler checks the stack against the prototype
(signature) and calls the matching function or issues
a typecheck or stackunderflow depending on the last failed
check.
Before calling the function, the handler pops the arguments
from the stack and passes them as arguments to the function
the function pushes its results.
By factoring-out the type check from the operator functions,
this version is shorter than the previous by about 1000 lines.
I've changed most of the strndup's to memcpy into a VLA.
grops.c implements the graphics operators.
Many of the graphics operators merely crack their argument
objects and pass
the values straight to the corresponding cairo function. This
is only
possible because of the close correspondance between cairo's
image
model and that of postscript (upon which cairo was based).
This presents some difficulty for the postscript operators
settransfer
currenttransfer
setscreen
currentscreen
for which there appears to be no corresponding cairo
functionality.
BUGS
Since the 'graphics state' is inside Cairo, it doesn't really interact
with
the Virtual Memory; hence the proper interaction between save/restore
and
gsave/grestore is difficult to imagine clearly.
Also there's no 'nulldevice' so without a canvas, graphics functions
won't behave quite right (ie. "10 10 moveto currentpoint" won't give
you 10 10).
But functions that are independent of the graphics state should work
find
(ie. "10 10 50 matrix rotate transform" will return the point 10 10
rotated
50 degrees).
What other programs do people use to test their implementations?
Most often the Quality Logic test suite. Unfortunately this is a
commercial set of tests, not free.
Artifex (and other suppliers I'm sure) also uses a collection of real
jobs supplied by customers and known to exercise 'interesting' areas of
the specification.
For PDf there is also the Ghent test suite and the Altona test suite.
Ken
Cool. I poked into the ghostscript examples and tried the snowflake.
!undefined in clippath. This looks like an ugly one. Cairo has a
function
to get a list of rectangles. I guess I'll have to use that to build a
path.
Seems a shame to do all that just to get a bounding box; but, oh well.
Step one is knowing what needs to be done.
I've uploaded the revised code, now with command-line arguments.
For the new mid-level function between main and run, I decided on
the name 'mcp' for master control program, in honor of tron.
http://code.google.com/p/xpost/downloads/list
there's also a screenshot of the fern drawn by both xpost and
ghostscript
at 300000 points.
I've added a screenshot showing the ghostscript rendering of the
tiger, and xpost's "rendition". It's almost like half of the layers
are drawn with the color "inverted" somehow. I'm not sure about the
orange vs. brown thing; but a lot of white patches are black, and
around the nose where it should fade to white, it fades to black.
That's some sort of clue, I hope.
The tiger.eps file defines its own color operator, setcmybcolor
which uses setrgbcolor. But that part's got to be right, right?
Adobe can be trusted to write postscript code to transform
color spaces; and my setrgbcolor simply cracks the realtype objects
and passes the doubles straight to Cairo; and Cairo's default space
is certainly a flat rgb (ignoring alpha).
And then there's the fact that parts that should be black, are black.
Not white, as one might expect with this "inversion" hypothesis.
But the tiger also uses setgray. Crap. That's almost certainly
where the problem is.
I feel so embarrassed.
The problem was an off-by-one in 'index'!
I'll upload again after I give all the operators a careful read-
through.
http://code.google.com/p/xpost/downloads/list
Now I need to do lots of reading about FreeType and X11 fonts.