2 * PostScript backend for Halibut
9 static void ps_comment(FILE *fp
, char const *leader
, word
*words
);
10 static void ps_string(FILE *fp
, char const *str
);
12 paragraph
*ps_config_filename(char *filename
)
14 return cmdline_cfg_simple("ps-filename", filename
, NULL
);
17 void ps_backend(paragraph
*sourceform
, keywordlist
*keywords
,
18 indexdata
*idx
, void *vdoc
) {
19 document
*doc
= (document
*)vdoc
;
31 filename
= dupstr("output.ps");
32 for (p
= sourceform
; p
; p
= p
->next
) {
33 if (p
->type
== para_Config
) {
34 if (!ustricmp(p
->keyword
, L
"ps-filename")) {
36 filename
= dupstr(adv(p
->origkeyword
));
41 fp
= fopen(filename
, "w");
43 error(err_cantopenw
, filename
);
47 fprintf(fp
, "%%!PS-Adobe-3.0\n");
48 fprintf(fp
, "%%%%Creator: Halibut, %s\n", version
);
49 fprintf(fp
, "%%%%DocumentData: Clean7Bit\n");
50 fprintf(fp
, "%%%%LanguageLevel: 1\n");
51 for (pageno
= 0, page
= doc
->pages
; page
; page
= page
->next
)
53 fprintf(fp
, "%%%%Pages: %d\n", pageno
);
54 for (p
= sourceform
; p
; p
= p
->next
)
55 if (p
->type
== para_Title
)
56 ps_comment(fp
, "%%Title: ", p
->words
);
57 fprintf(fp
, "%%%%DocumentNeededResources:\n");
58 for (fe
= doc
->fonts
->head
; fe
; fe
= fe
->next
)
59 /* XXX This may request the same font multiple times. */
60 if (!fe
->font
->info
->fp
)
61 fprintf(fp
, "%%%%+ font %s\n", fe
->font
->info
->name
);
62 fprintf(fp
, "%%%%DocumentSuppliedResources: procset Halibut 0 2\n");
63 for (fe
= doc
->fonts
->head
; fe
; fe
= fe
->next
)
64 /* XXX This may request the same font multiple times. */
65 if (fe
->font
->info
->fp
)
66 fprintf(fp
, "%%%%+ font %s\n", fe
->font
->info
->name
);
67 fprintf(fp
, "%%%%EndComments\n");
69 fprintf(fp
, "%%%%BeginProlog\n");
70 fprintf(fp
, "%%%%BeginResource: procset Halibut 0 2\n");
72 * Supply a prologue function which allows a reasonably
73 * compressed representation of the text on the pages.
75 * "t" expects two arguments: a y-coordinate, and then an array.
76 * Elements of the array are processed sequentially as follows:
78 * - a number is treated as an x-coordinate
79 * - an array is treated as a (font, size) pair
82 * "r" takes four arguments, and behaves like "rectfill".
85 "/tdict 4 dict dup begin\n"
86 " /arraytype {aload pop scalefont setfont} bind def\n"
87 " /realtype {1 index moveto} bind def\n"
88 " /integertype /realtype load def\n"
89 " /stringtype {show} bind def\n"
91 "/t { tdict begin {dup type exec} forall end pop } bind def\n"
92 "/r { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto\n"
93 " neg 0 rlineto closepath fill } bind def\n");
97 * "p" generates a named destination referencing this page.
98 * "x" generates a link to a named destination.
99 * "u" generates a link to a URI.
101 * They all do nothing if pdfmark is undefined.
104 "/pdfmark where { pop\n"
105 " /p { [ /Dest 3 -1 roll /View [ /XYZ null null null ]\n"
106 " /DEST pdfmark } bind def\n"
107 " /x { [ /Dest 3 -1 roll /Rect 5 -1 roll /Border [0 0 0 0]\n"
108 " /Subtype /Link /ANN pdfmark } bind def\n"
109 " /u { 2 dict dup /Subtype /URI put dup /URI 4 -1 roll put\n"
110 " [ /Action 3 -1 roll /Rect 5 -1 roll /Border [0 0 0 0]\n"
111 " /Subtype /Link /ANN pdfmark } bind def\n"
113 " [/p /x /u] { null cvx def } forall\n"
116 fprintf(fp
, "%%%%EndResource\n");
117 fprintf(fp
, "%%%%EndProlog\n");
119 fprintf(fp
, "%%%%BeginSetup\n");
122 * This is as good a place as any to put version IDs.
124 for (p
= sourceform
; p
; p
= p
->next
)
125 if (p
->type
== para_VersionID
)
126 ps_comment(fp
, "% ", p
->words
);
129 * Request the correct page size. We might want to bracket this
130 * with "%%BeginFeature: *PageSize A4" or similar, and "%%EndFeature",
131 * but that would require us to have a way of getting the name of
132 * the page size given its dimensions.
134 fprintf(fp
, "/setpagedevice where {\n");
135 fprintf(fp
, " pop 2 dict dup /PageSize [%g %g] put setpagedevice\n",
136 doc
->paper_width
/ FUNITS_PER_PT
,
137 doc
->paper_height
/ FUNITS_PER_PT
);
138 fprintf(fp
, "} if\n");
140 /* Request outline view if the document is converted to PDF. */
143 " pop [ /PageMode /UseOutlines /DOCVIEW pdfmark\n"
146 for (fe
= doc
->fonts
->head
; fe
; fe
= fe
->next
) {
147 /* XXX This may request the same font multiple times. */
148 if (fe
->font
->info
->fp
) {
151 fprintf(fp
, "%%%%BeginResource: font %s\n", fe
->font
->info
->name
);
152 rewind(fe
->font
->info
->fp
);
154 len
= fread(buf
, 1, sizeof(buf
), fe
->font
->info
->fp
);
155 fwrite(buf
, 1, len
, fp
);
156 } while (len
== sizeof(buf
));
157 fprintf(fp
, "%%%%EndResource\n");
159 fprintf(fp
, "%%%%IncludeResource: font %s\n",
160 fe
->font
->info
->name
);
165 * Re-encode the fonts.
168 for (fe
= doc
->fonts
->head
; fe
; fe
= fe
->next
) {
172 sprintf(fname
, "f%d", font_index
++);
173 fe
->name
= dupstr(fname
);
175 fprintf(fp
, "/%s findfont dup length dict begin\n",
176 fe
->font
->info
->name
);
177 fprintf(fp
, "{1 index /FID ne {def} {pop pop} ifelse} forall\n");
178 fprintf(fp
, "/Encoding [\n");
179 for (i
= 0; i
< 256; i
++)
180 fprintf(fp
, "/%s%c", fe
->vector
[i
] ? fe
->vector
[i
] : ".notdef",
181 i
% 4 == 3 ?
'\n' : ' ');
182 fprintf(fp
, "] def\n");
183 fprintf(fp
, "currentdict end\n");
184 fprintf(fp
, "/fontname-%s exch definefont /%s exch def\n\n",
187 fprintf(fp
, "%%%%EndSetup\n");
190 * Assign a destination name to each page for pdfmark purposes.
193 for (page
= doc
->pages
; page
; page
= page
->next
) {
196 buf
= snewn(12, char);
197 sprintf(buf
, "/p%d", pageno
);
202 * Output the text and graphics.
205 for (page
= doc
->pages
; page
; page
= page
->next
) {
206 text_fragment
*frag
, *frag_end
;
213 fprintf(fp
, "%%%%Page: %d %d\n", pageno
, pageno
);
214 fprintf(fp
, "save %s p\n", (char *)page
->spare
);
216 for (xr
= page
->first_xref
; xr
; xr
= xr
->next
) {
217 fprintf(fp
, "[%g %g %g %g]",
218 xr
->lx
/FUNITS_PER_PT
, xr
->by
/FUNITS_PER_PT
,
219 xr
->rx
/FUNITS_PER_PT
, xr
->ty
/FUNITS_PER_PT
);
220 if (xr
->dest
.type
== PAGE
) {
221 fprintf(fp
, "%s x\n", (char *)xr
->dest
.page
->spare
);
223 ps_string(fp
, xr
->dest
.url
);
228 for (r
= page
->first_rect
; r
; r
= r
->next
) {
229 fprintf(fp
, "%g %g %g %g r\n",
230 r
->x
/ FUNITS_PER_PT
, r
->y
/ FUNITS_PER_PT
,
231 r
->w
/ FUNITS_PER_PT
, r
->h
/ FUNITS_PER_PT
);
234 frag
= page
->first_text
;
239 * Collect all the adjacent text fragments with the
242 for (frag_end
= frag
;
243 frag_end
&& frag_end
->y
== frag
->y
;
244 frag_end
= frag_end
->next
);
246 fprintf(fp
, "%g[", frag
->y
/ FUNITS_PER_PT
);
248 while (frag
&& frag
!= frag_end
) {
250 if (frag
->fe
!= fe
|| frag
->fontsize
!= fs
)
251 fprintf(fp
, "[%s %d]", frag
->fe
->name
, frag
->fontsize
);
255 fprintf(fp
, "%g", frag
->x
/FUNITS_PER_PT
);
256 ps_string(fp
, frag
->text
);
264 fprintf(fp
, "restore showpage\n");
267 fprintf(fp
, "%%%%EOF\n");
274 static void ps_comment(FILE *fp
, char const *leader
, word
*words
)
276 fprintf(fp
, "%s", leader
);
278 for (; words
; words
= words
->next
) {
282 switch (words
->type
) {
292 type
= removeattr(words
->type
);
296 text
= utoa_dup(words
->text
, CS_ASCII
);
298 case word_WhiteSpace
:
313 static void ps_string(FILE *fp
, char const *str
) {
317 for (c
= str
; *c
; c
++) {
318 if (*c
< ' ' || *c
> '~') {
319 fprintf(fp
, "\\%03o", 0xFF & (int)*c
);
321 if (*c
== '(' || *c
== ')' || *c
== '\\')