1/* $Id$ $Revision$ */
2/* vim:set shiftwidth=4 ts=8: */
3
4/*************************************************************************
5 * Copyright (c) 2011 AT&T Intellectual Property
6 * All rights reserved. This program and the accompanying materials
7 * are made available under the terms of the Eclipse Public License v1.0
8 * which accompanies this distribution, and is available at
9 * http://www.eclipse.org/legal/epl-v10.html
10 *
11 * Contributors: See CVS logs. Details at http://www.graphviz.org/
12 *************************************************************************/
13
14/*
15 * graphics code generator wrapper
16 *
17 * This library forms the socket for run-time loadable render plugins.
18 */
19
20#include "config.h"
21
22#include <string.h>
23#include "memory.h"
24#include "const.h"
25#include "macros.h"
26#include "colorprocs.h"
27#include "gvplugin_render.h"
28#include "cgraph.h"
29#include "gvcint.h"
30#include "geom.h"
31#include "geomprocs.h"
32#include "gvcproc.h"
33
34extern int emit_once(char *str);
35extern shape_desc *find_user_shape(char *name);
36extern boolean mapbool(char *s);
37
38#ifndef HAVE_STRCASECMP
39extern int strcasecmp(const char *s1, const char *s2);
40#endif
41
42/* storage for temporary hacks until client API is FP */
43static pointf *AF;
44static int sizeAF;
45/* end hack */
46
47int gvrender_select(GVJ_t * job, const char *str)
48{
49 GVC_t *gvc = job->gvc;
50 gvplugin_available_t *plugin;
51 gvplugin_installed_t *typeptr;
52
53 gvplugin_load(gvc, API_device, str);
54
55 /* When job is created, it is zeroed out.
56 * Some flags, such as OUTPUT_NOT_REQUIRED, may already be set,
57 * so don't reset.
58 */
59 /* job->flags = 0; */
60 plugin = gvc->api[API_device];
61 if (plugin) {
62 typeptr = plugin->typeptr;
63 job->device.engine = (gvdevice_engine_t *) (typeptr->engine);
64 job->device.features = (gvdevice_features_t *) (typeptr->features);
65 job->device.id = typeptr->id;
66 job->device.type = plugin->typestr;
67
68 job->flags |= job->device.features->flags;
69 } else
70 return NO_SUPPORT; /* FIXME - should differentiate problem */
71
72 /* The device plugin has a dependency on a render plugin,
73 * so the render plugin should be available as well now */
74 plugin = gvc->api[API_render];
75 if (plugin) {
76 typeptr = plugin->typeptr;
77 job->render.engine = (gvrender_engine_t *) (typeptr->engine);
78 job->render.features = (gvrender_features_t *) (typeptr->features);
79 job->render.type = plugin->typestr;
80
81 job->flags |= job->render.features->flags;
82
83 if (job->device.engine)
84 job->render.id = typeptr->id;
85 else
86 /* A null device engine indicates that the device id is also the renderer id
87 * and that the renderer doesn't need "device" functions.
88 * Device "features" settings are still available */
89 job->render.id = job->device.id;
90 return GVRENDER_PLUGIN;
91 }
92 job->render.engine = NULL;
93 return NO_SUPPORT; /* FIXME - should differentiate problem */
94}
95
96int gvrender_features(GVJ_t * job)
97{
98 gvrender_engine_t *gvre = job->render.engine;
99 int features = 0;
100
101 if (gvre) {
102 features = job->render.features->flags;
103 }
104 return features;
105}
106
107/* gvrender_begin_job:
108 * Return 0 on success
109 */
110int gvrender_begin_job(GVJ_t * job)
111{
112 gvrender_engine_t *gvre = job->render.engine;
113
114 if (gvdevice_initialize(job))
115 return 1;
116 if (gvre) {
117 if (gvre->begin_job)
118 gvre->begin_job(job);
119 }
120 return 0;
121}
122
123void gvrender_end_job(GVJ_t * job)
124{
125 gvrender_engine_t *gvre = job->render.engine;
126
127 if (gvre) {
128 if (gvre->end_job)
129 gvre->end_job(job);
130 }
131 job->gvc->common.lib = NULL; /* FIXME - minimally this doesn't belong here */
132 gvdevice_finalize(job);
133}
134
135/* font modifiers */
136#define REGULAR 0
137#define BOLD 1
138#define ITALIC 2
139
140pointf gvrender_ptf(GVJ_t * job, pointf p)
141{
142 pointf rv, translation, scale;
143
144 translation = job->translation;
145 scale.x = job->zoom * job->devscale.x;
146 scale.y = job->zoom * job->devscale.y;
147
148 if (job->rotation) {
149 rv.x = -(p.y + translation.y) * scale.x;
150 rv.y = (p.x + translation.x) * scale.y;
151 } else {
152 rv.x = (p.x + translation.x) * scale.x;
153 rv.y = (p.y + translation.y) * scale.y;
154 }
155 return rv;
156}
157
158/* transform an array of n points */
159/* *AF and *af must be preallocated */
160/* *AF can be the same as *af for inplace transforms */
161pointf *gvrender_ptf_A(GVJ_t * job, pointf * af, pointf * AF, int n)
162{
163 int i;
164 double t;
165 pointf translation, scale;
166
167 translation = job->translation;
168 scale.x = job->zoom * job->devscale.x;
169 scale.y = job->zoom * job->devscale.y;
170
171 if (job->rotation) {
172 for (i = 0; i < n; i++) {
173 t = -(af[i].y + translation.y) * scale.x;
174 AF[i].y = (af[i].x + translation.x) * scale.y;
175 AF[i].x = t;
176 }
177 } else {
178 for (i = 0; i < n; i++) {
179 AF[i].x = (af[i].x + translation.x) * scale.x;
180 AF[i].y = (af[i].y + translation.y) * scale.y;
181 }
182 }
183 return AF;
184}
185
186static int gvrender_comparestr(const void *s1, const void *s2)
187{
188 return strcmp(*(char **) s1, *(char **) s2);
189}
190
191/* gvrender_resolve_color:
192 * N.B. strcmp cannot be used in bsearch, as it will pass a pointer
193 * to an element in the array features->knowncolors (i.e., a char**)
194 * as an argument of the compare function, while the arguments to
195 * strcmp are both char*. Given this, the first argument to
196 * bsearch must also be char**, so we use &tok.
197 */
198static void gvrender_resolve_color(gvrender_features_t * features,
199 char *name, gvcolor_t * color)
200{
201 char *tok;
202 int rc;
203
204 color->u.string = name;
205 color->type = COLOR_STRING;
206 tok = canontoken(name);
207 if (!features->knowncolors
208 ||
209 (bsearch
210 (&tok, features->knowncolors, features->sz_knowncolors,
211 sizeof(char *), gvrender_comparestr)) == NULL) {
212 /* if tok was not found in known_colors */
213 rc = colorxlate(name, color, features->color_type);
214 if (rc != COLOR_OK) {
215 if (rc == COLOR_UNKNOWN) {
216 char *missedcolor = gmalloc(strlen(name) + 16);
217 sprintf(missedcolor, "color %s", name);
218 if (emit_once(missedcolor))
219 agerr(AGWARN, "%s is not a known color.\n", name);
220 free(missedcolor);
221 } else {
222 agerr(AGERR, "error in colxlate()\n");
223 }
224 }
225 }
226}
227
228void gvrender_begin_graph(GVJ_t * job, graph_t * g)
229{
230 /* GVC_t *gvc = job->gvc; */
231 gvrender_engine_t *gvre = job->render.engine;
232 /* char *s; */
233
234 if (gvre) {
235 /* render specific init */
236 if (gvre->begin_graph)
237 gvre->begin_graph(job);
238
239#if 0
240 /* background color */
241 if (((s = agget(g, "bgcolor")) != 0) && s[0]) {
242 gvrender_resolve_color(job->render.features, s,
243 &(gvc->bgcolor));
244 if (gvre->resolve_color)
245 gvre->resolve_color(job, &(gvc->bgcolor));
246 }
247#endif
248
249 }
250}
251
252void gvrender_end_graph(GVJ_t * job)
253{
254 gvrender_engine_t *gvre = job->render.engine;
255
256 if (gvre) {
257 if (gvre->end_graph)
258 gvre->end_graph(job);
259 }
260 gvdevice_format(job);
261}
262
263void gvrender_begin_page(GVJ_t * job)
264{
265 gvrender_engine_t *gvre = job->render.engine;
266
267 if (gvre) {
268 if (gvre->begin_page)
269 gvre->begin_page(job);
270 }
271}
272
273void gvrender_end_page(GVJ_t * job)
274{
275 gvrender_engine_t *gvre = job->render.engine;
276
277 if (gvre) {
278 if (gvre->end_page)
279 gvre->end_page(job);
280 }
281}
282
283void gvrender_begin_layer(GVJ_t * job)
284{
285 gvrender_engine_t *gvre = job->render.engine;
286
287 if (gvre) {
288 if (gvre->begin_layer)
289 gvre->begin_layer(job, job->gvc->layerIDs[job->layerNum],
290 job->layerNum, job->numLayers);
291 }
292}
293
294void gvrender_end_layer(GVJ_t * job)
295{
296 gvrender_engine_t *gvre = job->render.engine;
297
298 if (gvre) {
299 if (gvre->end_layer)
300 gvre->end_layer(job);
301 }
302}
303
304void gvrender_begin_cluster(GVJ_t * job, graph_t * sg)
305{
306 gvrender_engine_t *gvre = job->render.engine;
307
308 if (gvre) {
309 if (gvre->begin_cluster)
310 gvre->begin_cluster(job);
311 }
312}
313
314void gvrender_end_cluster(GVJ_t * job, graph_t * g)
315{
316 gvrender_engine_t *gvre = job->render.engine;
317
318 if (gvre) {
319 if (gvre->end_cluster)
320 gvre->end_cluster(job);
321 }
322}
323
324void gvrender_begin_nodes(GVJ_t * job)
325{
326 gvrender_engine_t *gvre = job->render.engine;
327
328 if (gvre) {
329 if (gvre->begin_nodes)
330 gvre->begin_nodes(job);
331 }
332}
333
334void gvrender_end_nodes(GVJ_t * job)
335{
336 gvrender_engine_t *gvre = job->render.engine;
337
338 if (gvre) {
339 if (gvre->end_nodes)
340 gvre->end_nodes(job);
341 }
342}
343
344void gvrender_begin_edges(GVJ_t * job)
345{
346 gvrender_engine_t *gvre = job->render.engine;
347
348 if (gvre) {
349 if (gvre->begin_edges)
350 gvre->begin_edges(job);
351 }
352}
353
354void gvrender_end_edges(GVJ_t * job)
355{
356 gvrender_engine_t *gvre = job->render.engine;
357
358 if (gvre) {
359 if (gvre->end_edges)
360 gvre->end_edges(job);
361 }
362}
363
364void gvrender_begin_node(GVJ_t * job, node_t * n)
365{
366 gvrender_engine_t *gvre = job->render.engine;
367
368 if (gvre) {
369 if (gvre->begin_node)
370 gvre->begin_node(job);
371 }
372}
373
374void gvrender_end_node(GVJ_t * job)
375{
376 gvrender_engine_t *gvre = job->render.engine;
377
378 if (gvre) {
379 if (gvre->end_node)
380 gvre->end_node(job);
381 }
382}
383
384void gvrender_begin_edge(GVJ_t * job, edge_t * e)
385{
386 gvrender_engine_t *gvre = job->render.engine;
387
388 if (gvre) {
389 if (gvre->begin_edge)
390 gvre->begin_edge(job);
391 }
392}
393
394void gvrender_end_edge(GVJ_t * job)
395{
396 gvrender_engine_t *gvre = job->render.engine;
397
398 if (gvre) {
399 if (gvre->end_edge)
400 gvre->end_edge(job);
401 }
402}
403
404void gvrender_begin_anchor(GVJ_t * job, char *href, char *tooltip,
405 char *target, char *id)
406{
407 gvrender_engine_t *gvre = job->render.engine;
408
409 if (gvre) {
410 if (gvre->begin_anchor)
411 gvre->begin_anchor(job, href, tooltip, target, id);
412 }
413}
414
415void gvrender_end_anchor(GVJ_t * job)
416{
417 gvrender_engine_t *gvre = job->render.engine;
418
419 if (gvre) {
420 if (gvre->end_anchor)
421 gvre->end_anchor(job);
422 }
423}
424
425void gvrender_begin_label(GVJ_t * job, label_type type)
426{
427 gvrender_engine_t *gvre = job->render.engine;
428
429 if (gvre) {
430 if (gvre->begin_label)
431 gvre->begin_label(job, type);
432 }
433}
434
435void gvrender_end_label(GVJ_t * job)
436{
437 gvrender_engine_t *gvre = job->render.engine;
438
439 if (gvre) {
440 if (gvre->end_label)
441 gvre->end_label(job);
442 }
443}
444
445void gvrender_textspan(GVJ_t * job, pointf p, textspan_t * span)
446{
447 gvrender_engine_t *gvre = job->render.engine;
448 pointf PF;
449
450 if (span->str && span->str[0]
451 && (!job->obj /* because of xdgen non-conformity */
452 || job->obj->pen != PEN_NONE)) {
453 if (job->flags & GVRENDER_DOES_TRANSFORM)
454 PF = p;
455 else
456 PF = gvrender_ptf(job, p);
457 if (gvre) {
458 if (gvre->textspan)
459 gvre->textspan(job, PF, span);
460 }
461 }
462}
463
464void gvrender_set_pencolor(GVJ_t * job, char *name)
465{
466 gvrender_engine_t *gvre = job->render.engine;
467 gvcolor_t *color = &(job->obj->pencolor);
468 char *cp = NULL;
469
470 if ((cp = strstr(name, ":"))) /* if its a color list, then use only first */
471 *cp = '\0';
472 if (gvre) {
473 gvrender_resolve_color(job->render.features, name, color);
474 if (gvre->resolve_color)
475 gvre->resolve_color(job, color);
476 }
477 if (cp) /* restore color list */
478 *cp = ':';
479}
480
481void gvrender_set_fillcolor(GVJ_t * job, char *name)
482{
483 gvrender_engine_t *gvre = job->render.engine;
484 gvcolor_t *color = &(job->obj->fillcolor);
485 char *cp = NULL;
486
487 if ((cp = strstr(name, ":"))) /* if its a color list, then use only first */
488 *cp = '\0';
489 if (gvre) {
490 gvrender_resolve_color(job->render.features, name, color);
491 if (gvre->resolve_color)
492 gvre->resolve_color(job, color);
493 }
494 if (cp)
495 *cp = ':';
496}
497
498void gvrender_set_gradient_vals (GVJ_t * job, char *stopcolor, int angle, float frac)
499{
500 gvrender_engine_t *gvre = job->render.engine;
501 gvcolor_t *color = &(job->obj->stopcolor);
502
503 if (gvre) {
504 gvrender_resolve_color(job->render.features, stopcolor, color);
505 if (gvre->resolve_color)
506 gvre->resolve_color(job, color);
507 }
508 job->obj->gradient_angle = angle;
509 job->obj->gradient_frac = frac;
510}
511
512void gvrender_set_style(GVJ_t * job, char **s)
513{
514 gvrender_engine_t *gvre = job->render.engine;
515 obj_state_t *obj = job->obj;
516 char *line, *p;
517
518 obj->rawstyle = s;
519 if (gvre) {
520 if (s)
521 while ((p = line = *s++)) {
522 if (streq(line, "solid"))
523 obj->pen = PEN_SOLID;
524 else if (streq(line, "dashed"))
525 obj->pen = PEN_DASHED;
526 else if (streq(line, "dotted"))
527 obj->pen = PEN_DOTTED;
528 else if (streq(line, "invis") || streq(line, "invisible"))
529 obj->pen = PEN_NONE;
530 else if (streq(line, "bold"))
531 obj->penwidth = PENWIDTH_BOLD;
532 else if (streq(line, "setlinewidth")) {
533 while (*p)
534 p++;
535 p++;
536 obj->penwidth = atof(p);
537 } else if (streq(line, "filled"))
538 obj->fill = FILL_SOLID;
539 else if (streq(line, "unfilled"))
540 obj->fill = FILL_NONE;
541 else if (streq(line, "tapered"));
542 else {
543 agerr(AGWARN,
544 "gvrender_set_style: unsupported style %s - ignoring\n",
545 line);
546 }
547 }
548 }
549}
550
551void gvrender_ellipse(GVJ_t * job, pointf * pf, int n, int filled)
552{
553 gvrender_engine_t *gvre = job->render.engine;
554
555 if (gvre) {
556 if (gvre->ellipse && job->obj->pen != PEN_NONE) {
557 pointf af[2];
558
559 /* center */
560 af[0].x = (pf[0].x + pf[1].x) / 2.;
561 af[0].y = (pf[0].y + pf[1].y) / 2.;
562 /* corner */
563 af[1] = pf[1];
564
565 if (!(job->flags & GVRENDER_DOES_TRANSFORM))
566 gvrender_ptf_A(job, af, af, 2);
567 gvre->ellipse(job, af, filled);
568 }
569 }
570}
571
572void gvrender_polygon(GVJ_t * job, pointf * af, int n, int filled)
573{
574 int noPoly = 0;
575 gvcolor_t save_pencolor;
576
577 gvrender_engine_t *gvre = job->render.engine;
578 if (gvre) {
579 if (gvre->polygon && job->obj->pen != PEN_NONE) {
580 if (filled & NO_POLY) {
581 noPoly = 1;
582 filled &= ~NO_POLY;
583 save_pencolor = job->obj->pencolor;
584 job->obj->pencolor = job->obj->fillcolor;
585 }
586 if (job->flags & GVRENDER_DOES_TRANSFORM)
587 gvre->polygon(job, af, n, filled);
588 else {
589 if (sizeAF < n) {
590 sizeAF = n + 10;
591 AF = grealloc(AF, sizeAF * sizeof(pointf));
592 }
593 gvrender_ptf_A(job, af, AF, n);
594 gvre->polygon(job, AF, n, filled);
595 }
596 if (noPoly)
597 job->obj->pencolor = save_pencolor;
598 }
599 }
600}
601
602
603void gvrender_box(GVJ_t * job, boxf B, int filled)
604{
605 pointf A[4];
606
607 A[0] = B.LL;
608 A[2] = B.UR;
609 A[1].x = A[0].x;
610 A[1].y = A[2].y;
611 A[3].x = A[2].x;
612 A[3].y = A[0].y;
613
614 gvrender_polygon(job, A, 4, filled);
615}
616
617void gvrender_beziercurve(GVJ_t * job, pointf * af, int n,
618 int arrow_at_start, int arrow_at_end,
619 boolean filled)
620{
621 gvrender_engine_t *gvre = job->render.engine;
622
623 if (gvre) {
624 if (gvre->beziercurve && job->obj->pen != PEN_NONE) {
625 if (job->flags & GVRENDER_DOES_TRANSFORM)
626 gvre->beziercurve(job, af, n, arrow_at_start, arrow_at_end,
627 filled);
628 else {
629 if (sizeAF < n) {
630 sizeAF = n + 10;
631 AF = grealloc(AF, sizeAF * sizeof(pointf));
632 }
633 gvrender_ptf_A(job, af, AF, n);
634 gvre->beziercurve(job, AF, n, arrow_at_start, arrow_at_end,
635 filled);
636 }
637 }
638 }
639}
640
641void gvrender_polyline(GVJ_t * job, pointf * af, int n)
642{
643 gvrender_engine_t *gvre = job->render.engine;
644
645 if (gvre) {
646 if (gvre->polyline && job->obj->pen != PEN_NONE) {
647 if (job->flags & GVRENDER_DOES_TRANSFORM)
648 gvre->polyline(job, af, n);
649 else {
650 if (sizeAF < n) {
651 sizeAF = n + 10;
652 AF = grealloc(AF, sizeAF * sizeof(pointf));
653 }
654 gvrender_ptf_A(job, af, AF, n);
655 gvre->polyline(job, AF, n);
656 }
657 }
658 }
659}
660
661void gvrender_comment(GVJ_t * job, char *str)
662{
663 gvrender_engine_t *gvre = job->render.engine;
664
665 if (!str || !str[0])
666 return;
667
668 if (gvre) {
669 if (gvre->comment)
670 gvre->comment(job, str);
671 }
672}
673
674static imagescale_t get_imagescale(char *s)
675{
676 if (*s == '\0')
677 return IMAGESCALE_FALSE;
678 if (!strcasecmp(s, "width"))
679 return IMAGESCALE_WIDTH;
680 if (!strcasecmp(s, "height"))
681 return IMAGESCALE_HEIGHT;
682 if (!strcasecmp(s, "both"))
683 return IMAGESCALE_BOTH;
684 if (mapbool(s))
685 return IMAGESCALE_TRUE;
686 return IMAGESCALE_FALSE;
687}
688
689static imagepos_t get_imagepos(char *s)
690{
691 if (*s == '\0')
692 return IMAGEPOS_MIDDLE_CENTER;
693 if (!strcasecmp(s, "tl"))
694 return IMAGEPOS_TOP_LEFT;
695 if (!strcasecmp(s, "tc"))
696 return IMAGEPOS_TOP_CENTER;
697 if (!strcasecmp(s, "tr"))
698 return IMAGEPOS_TOP_RIGHT;
699 if (!strcasecmp(s, "ml"))
700 return IMAGEPOS_MIDDLE_LEFT;
701 if (!strcasecmp(s, "mc"))
702 return IMAGEPOS_MIDDLE_CENTER;
703 if (!strcasecmp(s, "mr"))
704 return IMAGEPOS_MIDDLE_RIGHT;
705 if (!strcasecmp(s, "bl"))
706 return IMAGEPOS_BOTTOM_LEFT;
707 if (!strcasecmp(s, "bc"))
708 return IMAGEPOS_BOTTOM_CENTER;
709 if (!strcasecmp(s, "br"))
710 return IMAGEPOS_BOTTOM_RIGHT;
711 return IMAGEPOS_MIDDLE_CENTER;
712}
713
714/* gvrender_usershape:
715 * Scale image to fill polygon bounding box according to "imagescale",
716 * positioned at "imagepos"
717 */
718void gvrender_usershape(GVJ_t * job, char *name, pointf * a, int n,
719 boolean filled, char *imagescale, char *imagepos)
720{
721 gvrender_engine_t *gvre = job->render.engine;
722 usershape_t *us;
723 double iw, ih, pw, ph;
724 double scalex, scaley; /* scale factors */
725 boxf b; /* target box */
726 int i;
727 point isz;
728 imagepos_t position;
729
730 assert(job);
731 assert(name);
732 assert(name[0]);
733
734 if (!(us = gvusershape_find(name))) {
735 if (find_user_shape(name)) {
736 if (gvre && gvre->library_shape)
737 gvre->library_shape(job, name, a, n, filled);
738 }
739 return;
740 }
741
742 isz = gvusershape_size_dpi(us, job->dpi);
743 if ((isz.x <= 0) && (isz.y <= 0))
744 return;
745
746 /* compute bb of polygon */
747 b.LL = b.UR = a[0];
748 for (i = 1; i < n; i++) {
749 EXPANDBP(b, a[i]);
750 }
751
752 pw = b.UR.x - b.LL.x;
753 ph = b.UR.y - b.LL.y;
754 ih = (double) isz.y;
755 iw = (double) isz.x;
756
757 scalex = pw / iw;
758 scaley = ph / ih;
759
760 switch (get_imagescale(imagescale)) {
761 case IMAGESCALE_TRUE:
762 /* keep aspect ratio fixed by just using the smaller scale */
763 if (scalex < scaley) {
764 iw *= scalex;
765 ih *= scalex;
766 } else {
767 iw *= scaley;
768 ih *= scaley;
769 }
770 break;
771 case IMAGESCALE_WIDTH:
772 iw *= scalex;
773 break;
774 case IMAGESCALE_HEIGHT:
775 ih *= scaley;
776 break;
777 case IMAGESCALE_BOTH:
778 iw *= scalex;
779 ih *= scaley;
780 break;
781 case IMAGESCALE_FALSE:
782 default:
783 break;
784 }
785
786 /* if image is smaller in any dimension, apply the specified positioning */
787 position = get_imagepos(imagepos);
788 if (iw < pw) {
789 switch (position) {
790 case IMAGEPOS_TOP_LEFT:
791 case IMAGEPOS_MIDDLE_LEFT:
792 case IMAGEPOS_BOTTOM_LEFT:
793 b.UR.x = b.LL.x + iw;
794 break;
795 case IMAGEPOS_TOP_RIGHT:
796 case IMAGEPOS_MIDDLE_RIGHT:
797 case IMAGEPOS_BOTTOM_RIGHT:
798 b.LL.x += (pw - iw);
799 b.UR.x = b.LL.x + iw;
800 break;
801 default:
802 b.LL.x += (pw - iw) / 2.0;
803 b.UR.x -= (pw - iw) / 2.0;
804 break;
805 }
806 }
807 if (ih < ph) {
808 switch (position) {
809 case IMAGEPOS_TOP_LEFT:
810 case IMAGEPOS_TOP_CENTER:
811 case IMAGEPOS_TOP_RIGHT:
812 b.LL.y = b.UR.y - ih;
813 break;
814 case IMAGEPOS_BOTTOM_LEFT:
815 case IMAGEPOS_BOTTOM_CENTER:
816 case IMAGEPOS_BOTTOM_RIGHT:
817 b.LL.y += ih;
818 b.UR.y = b.LL.y - ih;
819 break;
820 default:
821 b.LL.y += (ph - ih) / 2.0;
822 b.UR.y -= (ph - ih) / 2.0;
823 break;
824 }
825 }
826
827 /* convert from graph to device coordinates */
828 if (!(job->flags & GVRENDER_DOES_TRANSFORM)) {
829 b.LL = gvrender_ptf(job, b.LL);
830 b.UR = gvrender_ptf(job, b.UR);
831 }
832
833 if (b.LL.x > b.UR.x) {
834 double d = b.LL.x;
835 b.LL.x = b.UR.x;
836 b.UR.x = d;
837 }
838 if (b.LL.y > b.UR.y) {
839 double d = b.LL.y;
840 b.LL.y = b.UR.y;
841 b.UR.y = d;
842 }
843 if (gvre) {
844 gvloadimage(job, us, b, filled, job->render.type);
845 }
846}
847
848void gvrender_set_penwidth(GVJ_t * job, double penwidth)
849{
850 gvrender_engine_t *gvre = job->render.engine;
851
852 if (gvre) {
853 job->obj->penwidth = penwidth;
854 /*if (gvre->set_penwidth) gvre->set_penwidth(job, penwidth); */
855 }
856}
857