Hamish wrote:
I added PostScript support to d.out.file, and also did a bit of cleanup
in the script along the way. It seems to work very nicely.
For an example see the d.out.file help page.
Also new in d.out.file is a size=width,height option for setting
GRASS_WIDTH and GRASS_HEIGHT.
One thing that I wanted to add was EPS support. To do that I'd just
apply a little patch to the start of the output file:
-%!PS
+%!PS-Adobe-2.0 EPSF-2.0
+%%Creator: $USER
+%%CreationDate: `date`
+%%Title: $outname
+%%BoundingBox: 0 0 $GRASS_WIDTH $GRASS_HEIGHT
According to this page, EPS doesn't like the "erasepage" and "initclip"
operators, which the PS driver uses.
http://www.postscript.org/FAQs/language/node82.html
Can this be solved with awk magic or must the file be rejigged?
Ultimately, the display architecture must be re-jigged; the existing
API wasn't designed for encapsulation.
Both of these issues correspond to features in the graphics API which
are fundamentally incompatible with encapsulation.
To eliminate the specific features in question, it would be necessary
to make a couple of changes.
1. R_erase() would need to erase the current frame, rather than the
entire screen.
XDRIVER already behaves this way (the default erase implementation
fills a screen-sized rectangle with the current colour, which will
honour the current clip rectangle), but the PNG driver doesn't (it
provides its own implementation which erases the entire screen to
either transparent or the background colour).
[The XDRIVER behaviour is an artifact of having made the clip
rectangle apply to all rendering operations; I've only just noticed
that it also affects the erase operation. This doesn't actually matter
at present, as the only code which uses R_erase() also removes any
frames first, so the clip rectangle will always be the entire screen.]
But, the existing erase implementation is intentional. It is meant to
be a "reset" operation. The main reason why I implemented a specific
erase operation in the PS driver (rather than using the default "fill
a screen-sized rectangle) is so that it could be replaced with e.g.
"showpage" for generating multi-page PostScript output.
If you want EPS, you probably shouldn't be using the erase operation
anyhow.
R_erase() is currently used by "d.frame -e", d.profile, and
D_full_screen(). D_full_screen() is used by "d.erase -f", "d.frame -e"
and the "usual suspects" of i.class, i.ortho.photo, i.points and
i.vpoints.
If you think about it, these are all cases which violate the principle
of encapsulation. Rather than their output being encapsulated within
the existing context (i.e. the current frame), they override it and
assert global control.
That only leaves the PS_Erase() at initialisation, which is
unnecessary (essentially, that part of the PNG driver was carried over
without noticing that it's unnecessary; PostScript always starts with
a clean page).
2. R_set_window() would need a matching "unset window" operation.
In practice, this means that calls to the display functions which
select specific frames would need matching calls to unset those
frames.
The reason is that, once you set a clip path in PostScript, any
subsequent clip path is cumulative, i.e. the new clip path is itself
clipped to the current clip path, so the resulting clip path is the
intersection of the two.
The only way to remove an existing clip path is either initclip (which
is incompatible with encapsulation, because it will also remove any
clip path set outside the encapsulated object) or to use grestore to
revert the graphics state to the point prior to the clip path having
been set.
But gsave/grestore have to be matched. This means that you need an API
which, rather than setting global state "from now on", sets it until a
matching "end" operation occurs. And the begin/end operations have to
nest correctly.
IOW, you can't just call D_set_cur_wind() etc and forget about it; you
would have to explicitly revert to the previous window when you're
done with it.
For now, the only practical solution is to provide a way to avoid the
initclip in the WINDOW operation. That will have the side-effect that
programs which create their own frames will be largely incompatible
with EPS output. Once a frame has been created, it will cease to be
possible to draw anything outside of that frame.
It would sure be nice if the PS driver could act like the PNG driver
does for ppm files if it detects an .eps extension to GRASS_PSFILE.
I don't mind a little awk in d.out.file, but native eps support would
be much nicer for general use. I'd prefer not to have to rely on a 3rd
party dependancy (ps2epsi) for this simple task-- using GRASS_WIDTH and
GRASS_HEIGHT with GRASS_PAPER undefined gets us 95% of the way to .eps
already.
Changing details of the output format depending upon the $GRASS_PSFILE
extension is simple enough. What I can't do in the PS driver is to
change the way that d.* commands use the R_* API. That can only be
done by changing the d.* commands themselves.
d.vect.chart updated to use D_polygon(). I notice D_polygon() leads to
round(), which rounds away from zero (ie ceil() for positive pixel
values). Would it help with exact map placement to use floor(0.5+float x),
or round(float x -0.5)? I didn't bother to update other calls to
G_plot_polygon() as they weren't relevant to the PS driver & don't seem
to have much of a future anyway.
round() rounds to the nearest integer:
#define round(x) ((int) floor(0.5 + (x)))
Once 7.x is started, the first change to the R_* API will be to
replace integer coordinates with floats, so this will no longer be an
issue.
--
Glynn Clements <glynn@gclements.plus.com>