2 * PostScript backend for Halibut
10 /* Ideal number of characters per line, for use in PostScript code */
12 /* Absolute maxiumum characters per line, for use in DSC comments */
13 #define PS_MAXWIDTH 255
15 static void ps_comment(FILE *fp
, char const *leader
, word
*words
);
16 static void ps_token(FILE *fp
, int *cc
, char const *fmt
, ...);
17 static void ps_string_len(FILE *fp
, int *cc
, char const *str
, int len
);
18 static void ps_string(FILE *fp
, int *cc
, char const *str
);
20 paragraph
*ps_config_filename(char *filename
)
22 return cmdline_cfg_simple("ps-filename", filename
, NULL
);
25 void ps_backend(paragraph
*sourceform
, keywordlist
*keywords
,
26 indexdata
*idx
, void *vdoc
) {
27 document
*doc
= (document
*)vdoc
;
37 int cc
; /* Character count on current line */
42 filename
= dupstr("output.ps");
43 for (p
= sourceform
; p
; p
= p
->next
) {
44 if (p
->type
== para_Config
) {
45 if (!ustricmp(p
->keyword
, L
"ps-filename")) {
47 filename
= dupstr(adv(p
->origkeyword
));
52 fp
= fopen(filename
, "w");
54 error(err_cantopenw
, filename
);
58 fprintf(fp
, "%%!PS-Adobe-3.0\n");
59 fprintf(fp
, "%%%%Creator: Halibut, %s\n", version
);
60 fprintf(fp
, "%%%%DocumentData: Clean7Bit\n");
61 fprintf(fp
, "%%%%LanguageLevel: 1\n");
62 for (pageno
= 0, page
= doc
->pages
; page
; page
= page
->next
)
64 fprintf(fp
, "%%%%Pages: %d\n", pageno
);
65 for (p
= sourceform
; p
; p
= p
->next
)
66 if (p
->type
== para_Title
)
67 ps_comment(fp
, "%%Title: ", p
->words
);
68 fprintf(fp
, "%%%%DocumentNeededResources:\n");
69 for (fe
= doc
->fonts
->head
; fe
; fe
= fe
->next
)
70 /* XXX This may request the same font multiple times. */
71 if (!fe
->font
->info
->fontfile
)
72 fprintf(fp
, "%%%%+ font %s\n", fe
->font
->info
->name
);
73 fprintf(fp
, "%%%%DocumentSuppliedResources: procset Halibut 0 3\n");
74 for (fe
= doc
->fonts
->head
; fe
; fe
= fe
->next
)
75 /* XXX This may request the same font multiple times. */
76 if (fe
->font
->info
->fontfile
)
77 fprintf(fp
, "%%%%+ font %s\n", fe
->font
->info
->name
);
78 fprintf(fp
, "%%%%EndComments\n");
80 fprintf(fp
, "%%%%BeginProlog\n");
81 fprintf(fp
, "%%%%BeginResource: procset Halibut 0 3\n");
83 * Supply a prologue function which allows a reasonably
84 * compressed representation of the text on the pages.
86 * "t" expects two arguments: a y-coordinate, and then an array.
87 * Elements of the array are processed sequentially as follows:
89 * - a number is treated as an x-coordinate
90 * - an array is treated as a (font, size) pair
93 * "r" takes four arguments, and behaves like "rectfill".
96 "/tdict 4 dict dup begin\n"
97 " /arraytype {aload pop scalefont setfont} bind def\n"
98 " /realtype {1 index moveto} bind def\n"
99 " /integertype /realtype load def\n"
100 " /stringtype {show} bind def\n"
102 "/t { tdict begin {dup type exec} forall end pop } bind def\n"
103 "/r { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto\n"
104 " neg 0 rlineto closepath fill } bind def\n");
108 * "p" generates a named destination referencing this page.
109 * "x" generates a link to a named destination.
110 * "u" generates a link to a URI.
111 * "o" generates an outline entry.
112 * "m" generates a general pdfmark.
114 * They all do nothing if pdfmark is undefined.
117 "/pdfmark where { pop\n"
118 " /p { [ /Dest 3 -1 roll /View [ /XYZ null null null ]\n"
119 " /DEST pdfmark } bind def\n"
120 " /x { [ /Dest 3 -1 roll /Rect 5 -1 roll /Border [0 0 0]\n"
121 " /Subtype /Link /ANN pdfmark } bind def\n"
122 " /u { 2 dict dup /Subtype /URI put dup /URI 4 -1 roll put\n"
123 " [ /Action 3 -1 roll /Rect 5 -1 roll /Border [0 0 0]\n"
124 " /Subtype /Link /ANN pdfmark } bind def\n"
125 " /o { [ /Count 3 -1 roll /Dest 5 -1 roll /Title 7 -1 roll\n"
126 " /OUT pdfmark } bind def\n"
127 " /m /pdfmark load def\n"
130 " /p { pop } bind def\n"
131 " /x { pop pop } bind def\n"
133 " /o { pop pop pop } bind def\n"
134 " /m /cleartomark load def\n"
137 fprintf(fp
, "%%%%EndResource\n");
138 fprintf(fp
, "%%%%EndProlog\n");
140 fprintf(fp
, "%%%%BeginSetup\n");
143 * Assign a destination name to each page for pdfmark purposes.
146 for (page
= doc
->pages
; page
; page
= page
->next
) {
149 buf
= snewn(12, char);
150 sprintf(buf
, "/p%d", pageno
);
155 * This is as good a place as any to put version IDs.
157 for (p
= sourceform
; p
; p
= p
->next
)
158 if (p
->type
== para_VersionID
)
159 ps_comment(fp
, "% ", p
->words
);
163 * Request the correct page size. We might want to bracket this
164 * with "%%BeginFeature: *PageSize A4" or similar, and "%%EndFeature",
165 * but that would require us to have a way of getting the name of
166 * the page size given its dimensions.
168 ps_token(fp
, &cc
, "/setpagedevice where {\n");
169 ps_token(fp
, &cc
, " pop 2 dict dup /PageSize [%g %g] put setpagedevice\n",
170 doc
->paper_width
/ FUNITS_PER_PT
,
171 doc
->paper_height
/ FUNITS_PER_PT
);
172 ps_token(fp
, &cc
, "} if\n");
174 ps_token(fp
, &cc
, "[/PageMode/UseOutlines/DOCVIEW m\n");
175 noe
= doc
->n_outline_elements
;
176 for (oe
= doc
->outline_elements
; noe
; oe
++, noe
--) {
178 int titlelen
, count
, i
;
180 title
= pdf_outline_convert(oe
->pdata
->outline_title
, &titlelen
);
181 if (oe
->level
== 0) {
182 ps_token(fp
, &cc
, "[/Title");
183 ps_string_len(fp
, &cc
, title
, titlelen
);
184 ps_token(fp
, &cc
, "/DOCINFO m\n");
188 for (i
= 1; i
< noe
&& oe
[i
].level
> oe
->level
; i
++)
189 if (oe
[i
].level
== oe
->level
+ 1)
191 if (oe
->level
> 0) count
= -count
;
193 ps_string_len(fp
, &cc
, title
, titlelen
);
195 ps_token(fp
, &cc
, "%s %d o\n",
196 (char *)oe
->pdata
->first
->page
->spare
, count
);
199 for (fe
= doc
->fonts
->head
; fe
; fe
= fe
->next
) {
200 /* XXX This may request the same font multiple times. */
201 if (fe
->font
->info
->fontfile
) {
202 fprintf(fp
, "%%%%BeginResource: font %s\n", fe
->font
->info
->name
);
203 if (fe
->font
->info
->filetype
== TYPE1
)
204 pf_writeps(fe
->font
->info
, fp
);
206 sfnt_writeps(fe
->font
->info
, fp
);
207 fprintf(fp
, "%%%%EndResource\n");
209 fprintf(fp
, "%%%%IncludeResource: font %s\n",
210 fe
->font
->info
->name
);
215 * Re-encode the fonts.
218 for (fe
= doc
->fonts
->head
; fe
; fe
= fe
->next
) {
222 sprintf(fname
, "f%d", font_index
++);
223 fe
->name
= dupstr(fname
);
225 ps_token(fp
, &cc
, "/%s findfont dup length dict begin\n",
226 fe
->font
->info
->name
);
227 ps_token(fp
, &cc
, "{1 index /FID ne {def} {pop pop} ifelse} forall\n");
228 ps_token(fp
, &cc
, "/Encoding [\n");
229 for (i
= 0; i
< 256; i
++)
230 ps_token(fp
, &cc
, "/%s", glyph_extern(fe
->vector
[i
]));
231 ps_token(fp
, &cc
, "] def\n");
232 ps_token(fp
, &cc
, "currentdict end\n");
233 ps_token(fp
, &cc
, "/fontname-%s exch definefont /%s exch def\n",
236 fprintf(fp
, "%%%%EndSetup\n");
239 * Output the text and graphics.
242 for (page
= doc
->pages
; page
; page
= page
->next
) {
243 text_fragment
*frag
, *frag_end
;
250 fprintf(fp
, "%%%%Page: %d %d\n", pageno
, pageno
);
252 ps_token(fp
, &cc
, "save %s p\n", (char *)page
->spare
);
254 for (xr
= page
->first_xref
; xr
; xr
= xr
->next
) {
255 ps_token(fp
, &cc
, "[%g %g %g %g]",
256 xr
->lx
/FUNITS_PER_PT
, xr
->by
/FUNITS_PER_PT
,
257 xr
->rx
/FUNITS_PER_PT
, xr
->ty
/FUNITS_PER_PT
);
258 if (xr
->dest
.type
== PAGE
) {
259 ps_token(fp
, &cc
, "%s x\n", (char *)xr
->dest
.page
->spare
);
261 ps_string(fp
, &cc
, xr
->dest
.url
);
262 ps_token(fp
, &cc
, "u\n");
266 for (r
= page
->first_rect
; r
; r
= r
->next
) {
267 ps_token(fp
, &cc
, "%g %g %g %g r\n",
268 r
->x
/ FUNITS_PER_PT
, r
->y
/ FUNITS_PER_PT
,
269 r
->w
/ FUNITS_PER_PT
, r
->h
/ FUNITS_PER_PT
);
272 frag
= page
->first_text
;
277 * Collect all the adjacent text fragments with the
280 for (frag_end
= frag
;
281 frag_end
&& frag_end
->y
== frag
->y
;
282 frag_end
= frag_end
->next
);
284 ps_token(fp
, &cc
, "%g[", frag
->y
/ FUNITS_PER_PT
);
286 while (frag
&& frag
!= frag_end
) {
288 if (frag
->fe
!= fe
|| frag
->fontsize
!= fs
)
289 ps_token(fp
, &cc
, "[%s %d]",
290 frag
->fe
->name
, frag
->fontsize
);
294 ps_token(fp
, &cc
, "%g", frag
->x
/FUNITS_PER_PT
);
295 ps_string(fp
, &cc
, frag
->text
);
300 ps_token(fp
, &cc
, "]t\n");
303 ps_token(fp
, &cc
, "restore showpage\n");
306 fprintf(fp
, "%%%%EOF\n");
313 static void ps_comment(FILE *fp
, char const *leader
, word
*words
) {
316 cc
+= fprintf(fp
, "%s", leader
);
318 for (; words
; words
= words
->next
) {
322 switch (words
->type
) {
332 type
= removeattr(words
->type
);
336 text
= utoa_dup(words
->text
, CS_ASCII
);
338 case word_WhiteSpace
:
346 if (cc
+ strlen(text
) > PS_MAXWIDTH
)
347 text
[PS_MAXWIDTH
- cc
] = 0;
348 cc
+= fprintf(fp
, "%s", text
);
355 static void ps_token(FILE *fp
, int *cc
, char const *fmt
, ...) {
359 if (*cc
>= PS_WIDTH
- 10) {
363 *cc
+= vfprintf(fp
, fmt
, ap
);
364 /* Assume that \n only occurs at the end of a string */
365 if (fmt
[strlen(fmt
) - 1] == '\n')
369 static void ps_string_len(FILE *fp
, int *cc
, char const *str
, int len
) {
373 for (c
= str
; c
< str
+len
; c
++) {
374 if (*c
< ' ' || *c
> '~')
376 else if (*c
== '(' || *c
== ')' || *c
== '\\')
382 ps_token(fp
, cc
, "<");
383 for (c
= str
; c
< str
+len
; c
++) {
384 ps_token(fp
, cc
, "%02X", 0xFF & (int)*c
);
386 ps_token(fp
, cc
, ">");
388 *cc
+= fprintf(fp
, "(");
389 for (c
= str
; c
< str
+len
; c
++) {
390 if (*cc
>= PS_WIDTH
- 4) {
394 if (*c
< ' ' || *c
> '~') {
395 *cc
+= fprintf(fp
, "\\%03o", 0xFF & (int)*c
);
397 if (*c
== '(' || *c
== ')' || *c
== '\\') {
405 *cc
+= fprintf(fp
, ")");
409 static void ps_string(FILE *fp
, int *cc
, char const *str
) {
410 ps_string_len(fp
, cc
, str
, strlen(str
));