1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stddef.h>
4 #include <string.h>
5 #include <errno.h>
6 #include <inttypes.h>
7
8 #include <X11/Xlib.h>
9 #include <X11/Intrinsic.h>
10 #include <Xm/Xm.h>
11 #include <Xm/Text.h>
12 #include <Xm/SelectioB.h>
13 #include <Xm/DrawingA.h>
14 #include <Xm/RowColumn.h>
15 #include <Xm/PushB.h>
16 #include <Xm/Scale.h>
17 #include <Xm/Label.h>
18 #include "RegEdit.h"
19
20 #include "ida.h"
21 #include "readers.h"
22 #include "writers.h"
23 #include "viewer.h"
24
25 struct PAPER {
26 char *name;
27 int width,height;
28 };
29
30 static struct PAPER formats[] = {
31 {
32 name: "A4",
33 width: 595,
34 height: 842,
35 },{
36 name: "Letter",
37 width: 612,
38 height: 792,
39 },{
40 /* EOF */
41 }
42 };
43
44 static const char *header =
45 "%%!PS-Adobe-2.0 EPSF-2.0\n"
46 "%%%%Creator: ida " VERSION " (https://www.kraxel.org/blog/linux/fbida/)\n"
47 "%%%%Pages: 1\n"
48 "%%%%BoundingBox: %d %d %d %d\n"
49 "%%%%DocumentFonts: \n"
50 "%%%%EndComments\n"
51 "%%%%EndProlog\n"
52 "\n"
53 "%%%%Page: 1 1"
54 "\n"
55 "/origstate save def\n"
56 "20 dict begin\n";
57
58 static const char *footer =
59 "\n"
60 "showpage\n"
61 "end\n"
62 "origstate restore\n"
63 "%%Trailer\n";
64
65 /* taken from xwd2ps, ftp://ftp.x.org/R5contrib/xwd2ps.tar.Z */
66 static const char *ColorImage =
67 "% define 'colorimage' if it isn't defined\n"
68 "% ('colortogray' and 'mergeprocs' come from xwd2ps\n"
69 "% via xgrab)\n"
70 "/colorimage where % do we know about 'colorimage'?\n"
71 " { pop } % yes: pop off the 'dict' returned\n"
72 " { % no: define one\n"
73 " /colortogray { % define an RGB->I function\n"
74 " /rgbdata exch store % call input 'rgbdata'\n"
75 " rgbdata length 3 idiv\n"
76 " /npixls exch store\n"
77 " /rgbindx 0 store\n"
78 " 0 1 npixls 1 sub {\n"
79 " grays exch\n"
80 " rgbdata rgbindx get 20 mul % Red\n"
81 " rgbdata rgbindx 1 add get 32 mul % Green\n"
82 " rgbdata rgbindx 2 add get 12 mul % Blue\n"
83 " add add 64 idiv % I = .5G + .31R + .18B\n"
84 " put\n"
85 " /rgbindx rgbindx 3 add store\n"
86 " } for\n"
87 " grays 0 npixls getinterval\n"
88 " } bind def\n"
89 "\n"
90 " % Utility procedure for colorimage operator.\n"
91 " % This procedure takes two procedures off the\n"
92 " % stack and merges them into a single procedure.\n"
93 "\n"
94 " /mergeprocs { % def\n"
95 " dup length\n"
96 " 3 -1 roll\n"
97 " dup\n"
98 " length\n"
99 " dup\n"
100 " 5 1 roll\n"
101 " 3 -1 roll\n"
102 " add\n"
103 " array cvx\n"
104 " dup\n"
105 " 3 -1 roll\n"
106 " 0 exch\n"
107 " putinterval\n"
108 " dup\n"
109 " 4 2 roll\n"
110 " putinterval\n"
111 " } bind def\n"
112 "\n"
113 " /colorimage { % def\n"
114 " pop pop % remove 'false 3' operands\n"
115 " {colortogray} mergeprocs\n"
116 " image\n"
117 " } bind def\n"
118 " } ifelse % end of 'false' case\n"
119 "\n";
120
121
122 /* ---------------------------------------------------------------------- */
123 /* save */
124
125 #define PORTRAIT 0
126 #define LANDSCAPE 1
127
128 #define DRAW_SIZE 200
129 #define DRAW_SCALE 6
130 #define DSCALED(x) (((x)+DRAW_SCALE/2)/DRAW_SCALE)
131
132 static struct ps_options {
133 Widget shell,draw,scale,geo;
134 int xscale,yscale;
135 GC gc;
136 int lastx,lasty;
137
138 int format;
139 int ori;
140 int scaling;
141
142 int iwidth,iheight,ires;
143 int pwidth,pheight;
144 int mwidth,mheight;
145 int width,height,xcenter,ycenter;
146 } ps;
147
148 static void
ps_draw(Widget widget,XtPointer client_data,XtPointer calldata)149 ps_draw(Widget widget, XtPointer client_data, XtPointer calldata)
150 {
151 XmDrawingAreaCallbackStruct *cb = calldata;
152 XExposeEvent *e;
153 XmString str;
154 char buf[128];
155 int x,y,w,h;
156
157 if (!(cb->reason == XmCR_EXPOSE))
158 return;
159 e = (XExposeEvent*)cb->event;
160 if (e->count)
161 return;
162
163 if (!ps.gc)
164 ps.gc = XCreateGC(dpy,XtWindow(app_shell),0,NULL);
165
166 w = DSCALED(ps.pwidth);
167 h = DSCALED(ps.pheight);
168 x = (DRAW_SIZE-w) / 2;
169 y = (DRAW_SIZE-h) / 2;
170 XDrawRectangle(dpy,XtWindow(widget),ps.gc, x,y,w,h);
171
172 w = DSCALED(ps.width);
173 h = DSCALED(ps.height);
174 x += DSCALED(ps.xcenter - ps.width/2);
175 y += DSCALED(ps.ycenter - ps.height/2);
176 XFillRectangle(dpy,XtWindow(widget),ps.gc, x,y,w,h);
177
178 sprintf(buf,"%d dpi",ps.iwidth * 72 / ps.width);
179 str = XmStringGenerate(buf, NULL, XmMULTIBYTE_TEXT, NULL);
180 XtVaSetValues(ps.geo,XmNlabelString,str,NULL);
181 XmStringFree(str);
182 }
183
184 static void
ps_defaults(void)185 ps_defaults(void)
186 {
187 /* max size, keep aspect ratio */
188 if (ps.ori == PORTRAIT) {
189 ps.pwidth = formats[ps.format].width;
190 ps.pheight = formats[ps.format].height;
191 } else {
192 ps.pheight = formats[ps.format].width;
193 ps.pwidth = formats[ps.format].height;
194 }
195
196 if (ps.iwidth * ps.pheight > ps.iheight * ps.pwidth) {
197 ps.mwidth = ps.pwidth;
198 ps.mheight = ps.iheight * ps.mwidth / ps.iwidth;
199 } else {
200 ps.mheight = ps.pheight;
201 ps.mwidth = ps.iwidth * ps.mheight / ps.iheight;
202 }
203 ps.scaling = 0;
204 if (ps.ires) {
205 /* Use image resolution to calculate default scaling factor.
206 * The image will be printed in original size if it fits into
207 * one page */
208 ps.scaling = ps.iwidth * 72 * 1000 / ps.mwidth / ps.ires;
209 }
210 if (ps.scaling > 1000 || ps.scaling < 1) {
211 /* default: maxpect with some border */
212 ps.scaling = 1000;
213 while (ps.mwidth * ps.scaling / 1000 + 50 > ps.mwidth ||
214 ps.mheight * ps.scaling / 1000 + 50 > ps.mheight)
215 ps.scaling--;
216 }
217 XmScaleSetValue(ps.scale,ps.scaling);
218 ps.width = ps.mwidth * ps.scaling / 1000;
219 ps.height = ps.mheight * ps.scaling / 1000;
220 ps.xcenter = ps.pwidth/2;
221 ps.ycenter = ps.pheight/2;
222
223 if (XtWindow(ps.draw))
224 XClearArea(XtDisplay(ps.draw), XtWindow(ps.draw),
225 0,0,0,0, True);
226 }
227
228 static void
ps_ranges(void)229 ps_ranges(void)
230 {
231 if (ps.width == 0)
232 ps.width = 1;
233 if (ps.height == 0)
234 ps.height = 1;
235 if (ps.xcenter - ps.width/2 < 0)
236 ps.xcenter = ps.width/2;
237 if (ps.xcenter + ps.width/2 > ps.pwidth)
238 ps.xcenter = ps.pwidth - ps.width/2;
239 if (ps.ycenter - ps.height/2 < 0)
240 ps.ycenter = ps.height/2;
241 if (ps.ycenter + ps.height/2 > ps.pheight)
242 ps.ycenter = ps.pheight - ps.height/2;
243 }
244
245 static void
ps_mouse(Widget widget,XtPointer client_data,XEvent * ev,Boolean * cont)246 ps_mouse(Widget widget, XtPointer client_data,
247 XEvent *ev, Boolean *cont)
248 {
249 switch (ev->type) {
250 case ButtonPress:
251 {
252 XButtonEvent *e = (XButtonEvent*)ev;
253
254 ps.lastx = e->x;
255 ps.lasty = e->y;
256 break;
257 }
258 case MotionNotify:
259 {
260 XMotionEvent *e = (XMotionEvent*)ev;
261
262 if (e->state & Button1Mask) {
263 ps.xcenter += (e->x - ps.lastx) * DRAW_SCALE;
264 ps.ycenter += (e->y - ps.lasty) * DRAW_SCALE;
265 ps.lastx = e->x;
266 ps.lasty = e->y;
267 }
268 break;
269 default:
270 return;
271 }
272 }
273 ps_ranges();
274 if (XtWindow(ps.draw))
275 XClearArea(XtDisplay(ps.draw), XtWindow(ps.draw),
276 0,0,0,0, True);
277 }
278
279 static void
ps_paper_cb(Widget widget,XtPointer clientdata,XtPointer call_data)280 ps_paper_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
281 {
282 ps.format = (intptr_t)clientdata;
283 ps_defaults();
284 }
285
286 static void
ps_ori_cb(Widget widget,XtPointer clientdata,XtPointer call_data)287 ps_ori_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
288 {
289 ps.ori = (intptr_t)clientdata;
290 ps_defaults();
291 }
292
293 static void
ps_scaling_cb(Widget widget,XtPointer clientdata,XtPointer call_data)294 ps_scaling_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
295 {
296 XmScaleCallbackStruct *cd = call_data;
297
298 ps.scaling = cd->value;
299 ps.width = ps.mwidth * ps.scaling / 1000;
300 ps.height = ps.mheight * ps.scaling / 1000;
301 ps_ranges();
302 if (XtWindow(ps.draw))
303 XClearArea(XtDisplay(ps.draw), XtWindow(ps.draw),
304 0,0,0,0, True);
305 }
306
307 static void
ps_button_cb(Widget widget,XtPointer clientdata,XtPointer call_data)308 ps_button_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
309 {
310 XmSelectionBoxCallbackStruct *cb = call_data;
311
312 if (XmCR_OK == cb->reason) {
313 do_save_print();
314 }
315 XtUnmanageChild(ps.shell);
316 }
317
318 static int
ps_conf(Widget parent,struct ida_image * img)319 ps_conf(Widget parent, struct ida_image *img)
320 {
321 Widget rc,menu,push,opt;
322 Arg args[2];
323 intptr_t i;
324
325 if (!ps.shell) {
326 /* build dialog */
327 ps.shell = XmCreatePromptDialog(parent,"ps",NULL,0);
328 XmdRegisterEditres(XtParent(ps.shell));
329 XtUnmanageChild(XmSelectionBoxGetChild(ps.shell,XmDIALOG_HELP_BUTTON));
330 XtUnmanageChild(XmSelectionBoxGetChild(ps.shell,XmDIALOG_SELECTION_LABEL));
331 XtUnmanageChild(XmSelectionBoxGetChild(ps.shell,XmDIALOG_TEXT));
332 XtAddCallback(ps.shell,XmNokCallback,ps_button_cb,NULL);
333 XtAddCallback(ps.shell,XmNcancelCallback,ps_button_cb,NULL);
334
335 rc = XtVaCreateManagedWidget("rc1",xmRowColumnWidgetClass,
336 ps.shell,NULL);
337 ps.draw = XtVaCreateManagedWidget("draw",xmDrawingAreaWidgetClass,rc,
338 XtNwidth,DRAW_SIZE,
339 XtNheight,DRAW_SIZE,
340 NULL);
341 XtAddCallback(ps.draw,XmNexposeCallback,ps_draw,NULL);
342 XtAddEventHandler(ps.draw,
343 ButtonPressMask |
344 ButtonReleaseMask |
345 ButtonMotionMask,
346 False,ps_mouse,NULL);
347 rc = XtVaCreateManagedWidget("rc2",xmRowColumnWidgetClass,
348 rc,NULL);
349
350 /* paper */
351 menu = XmCreatePulldownMenu(rc,"paperM",NULL,0);
352 XtSetArg(args[0],XmNsubMenuId,menu);
353 opt = XmCreateOptionMenu(rc,"paper",args,1);
354 XtManageChild(opt);
355 for (i = 0; formats[i].name != NULL; i++) {
356 push = XtVaCreateManagedWidget(formats[i].name,xmPushButtonWidgetClass,menu,NULL);
357 XtAddCallback(push,XmNactivateCallback,ps_paper_cb,(XtPointer)i);
358 }
359
360 /* orientation */
361 menu = XmCreatePulldownMenu(rc,"oriM",NULL,0);
362 XtSetArg(args[0],XmNsubMenuId,menu);
363 opt = XmCreateOptionMenu(rc,"ori",args,1);
364 XtManageChild(opt);
365 push = XtVaCreateManagedWidget("portrait",xmPushButtonWidgetClass,
366 menu,NULL);
367 XtAddCallback(push,XmNactivateCallback,ps_ori_cb,(XtPointer)PORTRAIT);
368 push = XtVaCreateManagedWidget("landscape",xmPushButtonWidgetClass,
369 menu,NULL);
370 XtAddCallback(push,XmNactivateCallback,ps_ori_cb,(XtPointer)LANDSCAPE);
371
372 ps.scale = XtVaCreateManagedWidget("scale",xmScaleWidgetClass,rc,NULL);
373 XtAddCallback(ps.scale,XmNdragCallback,ps_scaling_cb,NULL);
374 XtAddCallback(ps.scale,XmNvalueChangedCallback,ps_scaling_cb,NULL);
375
376 /* output */
377 ps.geo = XtVaCreateManagedWidget("geo",xmLabelWidgetClass,rc,NULL);
378 }
379
380 ps.iwidth = img->i.width;
381 ps.iheight = img->i.height;
382 ps.ires = 0;
383 if (ida->img.i.dpi)
384 ps.ires = ida->img.i.dpi;
385 ps_defaults();
386
387 XtManageChild(ps.shell);
388 return 0;
389 }
390
391 static int
ps_write(FILE * fp,struct ida_image * img)392 ps_write(FILE *fp, struct ida_image *img)
393 {
394 unsigned int width,height,xoff,yoff;
395 unsigned int iwidth,iheight;
396 unsigned int x,y;
397 unsigned char *p;
398
399 if (ps.ori == PORTRAIT) {
400 iwidth = img->i.width;
401 iheight = img->i.height;
402 width = ps.width;
403 height = ps.height;
404 xoff = ps.xcenter - ps.width/2;
405 yoff = (ps.pheight - ps.ycenter) - ps.height/2;
406 } else{
407 iwidth = img->i.height;
408 iheight = img->i.width;
409 width = ps.height;
410 height = ps.width;
411 xoff = ps.ycenter - ps.height/2;
412 yoff = ps.xcenter - ps.width/2;
413 }
414
415 /* PS header */
416 fprintf(fp,header, /* includes bbox */
417 xoff,yoff,xoff+width,yoff+height);
418 fprintf(fp,"\n"
419 "/pix %u string def\n"
420 "/grays %u string def\n"
421 "/npixls 0 def\n"
422 "/rgbindx 0 def\n"
423 "\n",
424 img->i.width*3,img->i.width);
425 fwrite(ColorImage,strlen(ColorImage),1,fp);
426
427 fprintf(fp,"%u %u translate\n",xoff,yoff);
428 fprintf(fp,"%u %u scale\n",width,height);
429
430 fprintf(fp,"\n"
431 "%u %u 8\n"
432 "[%u 0 0 -%u 0 %u]\n"
433 "{currentfile pix readhexstring pop}\n"
434 "false 3 colorimage\n",
435 iwidth,iheight,iwidth,iheight,iheight);
436
437 /* image data + ps footer */
438 if (ps.ori == PORTRAIT) {
439 p = ida_image_scanline(img, 0);
440 for (y = 0; y < img->i.height; y++) {
441 for (x = 0; x < img->i.width; x++) {
442 if (0 == (x % 10))
443 fprintf(fp,"\n");
444 fprintf(fp,"%02x%02x%02x ",p[0],p[1],p[2]);
445 p += 3;
446 }
447 fprintf(fp,"\n");
448 }
449 } else {
450 for (x = img->i.width-1; x != -1; x--) {
451 p = ida_image_scanline(img, 0) + 3*x;
452 for (y = 0; y < img->i.height; y++) {
453 if (0 == (y % 10))
454 fprintf(fp,"\n");
455 fprintf(fp,"%02x%02x%02x ",p[0],p[1],p[2]);
456 p += img->i.width*3;
457 }
458 fprintf(fp,"\n");
459 }
460 }
461 fprintf(fp, "%s", footer);
462 return 0;
463 }
464
465 struct ida_writer ps_writer = {
466 label: "PostScript",
467 ext: { "ps", "eps", NULL},
468 write: ps_write,
469 conf: ps_conf,
470 };
471
init_wr(void)472 static void __init init_wr(void)
473 {
474 write_register(&ps_writer);
475 }
476
477