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