]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - examples/svgtiny_display_x11.c
Add an example of using libsvgtiny. Displays an SVG using X11 and cairo.
[libsvgtiny.git] / examples / svgtiny_display_x11.c
1 /*
2 * This file is part of Libsvgtiny
3 * Licensed under the MIT License,
4 * http://opensource.org/licenses/mit-license.php
5 * Copyright 2009-2010 James Bursa <james@semichrome.net>
6 */
7
8 /*
9 * This example loads an SVG using libsvgtiny and then displays it in an X11
10 * window using cairo.
11 *
12 * Functions of interest for libsvgtiny use are:
13 * main() - loads an SVG using svgtiny_create() and svgtiny_parse()
14 * event_diagram_expose() - renders the SVG by stepping through the shapes
15 *
16 * Compile using:
17 * gcc -g -W -Wall -o svgtiny_display_x11 svgtiny_display_x11.c \
18 * `pkg-config --cflags --libs libsvgtiny cairo` -lX11
19 */
20
21
22 #include <libgen.h>
23 #include <math.h>
24 #include <stdbool.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <X11/Xlib.h>
31 #include <X11/keysym.h>
32 #include <cairo.h>
33 #include <cairo-xlib.h>
34 #include "svgtiny.h"
35
36
37 struct svgtiny_diagram *diagram;
38 Display *display;
39 Window diagram_window;
40 Atom wm_protocols_atom, wm_delete_window_atom;
41 char *svg_path;
42 float scale = 1.0;
43 bool quit = false;
44
45
46 void gui_init(void);
47 void gui_quit(void);
48 void update_window_title(void);
49 void gui_poll(void);
50 void event_diagram_key_press(XKeyEvent *key_event);
51 void event_diagram_expose(const XExposeEvent *expose_event);
52 void render_path(cairo_t *cr, float scale, struct svgtiny_shape *path);
53 void die(const char *message);
54
55
56 /**
57 * Main program.
58 */
59 int main(int argc, char *argv[])
60 {
61 FILE *fd;
62 struct stat sb;
63 char *buffer;
64 size_t size;
65 size_t n;
66 svgtiny_code code;
67
68 if (argc != 2) {
69 fprintf(stderr, "Usage: %s FILE\n", argv[0]);
70 return 1;
71 }
72 svg_path = argv[1];
73
74 /* load file into memory buffer */
75 fd = fopen(svg_path, "rb");
76 if (!fd) {
77 perror(svg_path);
78 return 1;
79 }
80
81 if (stat(svg_path, &sb)) {
82 perror(svg_path);
83 return 1;
84 }
85 size = sb.st_size;
86
87 buffer = malloc(size);
88 if (!buffer) {
89 fprintf(stderr, "Unable to allocate %lld bytes\n",
90 (long long) size);
91 return 1;
92 }
93
94 n = fread(buffer, 1, size, fd);
95 if (n != size) {
96 perror(svg_path);
97 return 1;
98 }
99
100 fclose(fd);
101
102 /* create svgtiny object */
103 diagram = svgtiny_create();
104 if (!diagram) {
105 fprintf(stderr, "svgtiny_create failed\n");
106 return 1;
107 }
108
109 /* parse */
110 code = svgtiny_parse(diagram, buffer, size, svg_path, 1000, 1000);
111 if (code != svgtiny_OK) {
112 fprintf(stderr, "svgtiny_parse failed: ");
113 switch (code) {
114 case svgtiny_OUT_OF_MEMORY:
115 fprintf(stderr, "svgtiny_OUT_OF_MEMORY");
116 break;
117 case svgtiny_LIBXML_ERROR:
118 fprintf(stderr, "svgtiny_LIBXML_ERROR");
119 break;
120 case svgtiny_NOT_SVG:
121 fprintf(stderr, "svgtiny_NOT_SVG");
122 break;
123 case svgtiny_SVG_ERROR:
124 fprintf(stderr, "svgtiny_SVG_ERROR: line %i: %s",
125 diagram->error_line,
126 diagram->error_message);
127 break;
128 default:
129 fprintf(stderr, "unknown svgtiny_code %i", code);
130 break;
131 }
132 fprintf(stderr, "\n");
133 }
134
135 free(buffer);
136
137 /*printf("viewbox 0 0 %u %u\n",
138 diagram->width, diagram->height);*/
139
140 gui_init();
141
142 while (!quit) {
143 gui_poll();
144 }
145
146 gui_quit();
147 svgtiny_free(diagram);
148
149 return 0;
150 }
151
152
153 /**
154 * Initialize X11 interface.
155 */
156 void gui_init(void)
157 {
158 display = XOpenDisplay(NULL);
159 if (!display)
160 die("XOpenDisplay failed: is DISPLAY set?");
161
162 diagram_window = XCreateSimpleWindow(display,
163 DefaultRootWindow(display),
164 0, 0, diagram->width, diagram->height, 0, 0, 0);
165
166 update_window_title();
167
168 XMapWindow(display, diagram_window);
169 XSelectInput(display, diagram_window,
170 KeyPressMask |
171 ButtonPressMask |
172 ExposureMask |
173 StructureNotifyMask);
174
175 wm_protocols_atom = XInternAtom(display, "WM_PROTOCOLS", False);
176 wm_delete_window_atom = XInternAtom(display, "WM_DELETE_WINDOW", False);
177 XSetWMProtocols(display, diagram_window, &wm_delete_window_atom, 1);
178 }
179
180
181 /**
182 * Free X11 interface.
183 */
184 void gui_quit(void)
185 {
186 XCloseDisplay(display);
187 }
188
189
190 /**
191 * Update window title to show current state.
192 */
193 void update_window_title(void)
194 {
195 char title[100];
196 char *svg_path_copy;
197 char *base_name;
198
199 svg_path_copy = strdup(svg_path);
200 if (!svg_path_copy) {
201 fprintf(stderr, "out of memory\n");
202 return;
203 }
204
205 base_name = basename(svg_path_copy);
206
207 snprintf(title, sizeof title, "%s (%i%%) - svgtiny",
208 base_name, (int) roundf(scale * 100.0));
209
210 XStoreName(display, diagram_window, title);
211
212 free(svg_path_copy);
213 }
214
215
216 /**
217 * Handle an X11 event.
218 */
219 void gui_poll(void)
220 {
221 XEvent event;
222 XNextEvent(display, &event);
223
224 switch (event.type) {
225 case KeyPress:
226 if (event.xkey.window == diagram_window) {
227 event_diagram_key_press(&event.xkey);
228 }
229 break;
230 case Expose:
231 if (event.xexpose.window == diagram_window) {
232 event_diagram_expose(&event.xexpose);
233 }
234 break;
235 case ClientMessage:
236 if (event.xclient.message_type == wm_protocols_atom &&
237 event.xclient.format == 32 &&
238 (Atom) event.xclient.data.l[0] ==
239 wm_delete_window_atom)
240 quit = true;
241 break;
242 default:
243 /*printf("unknown event %i\n", event.type);*/
244 break;
245 }
246 }
247
248
249 /**
250 * Handle an X11 KeyPress event in the diagram window.
251 */
252 void event_diagram_key_press(XKeyEvent *key_event)
253 {
254 KeySym key_sym;
255 float new_scale = scale;
256 unsigned int width, height;
257
258 key_sym = XLookupKeysym(key_event, 0);
259
260 switch (key_sym) {
261 case XK_q:
262 case XK_Escape:
263 quit = true;
264 break;
265
266 case XK_minus:
267 case XK_KP_Subtract:
268 new_scale -= 0.1;
269 break;
270
271 case XK_equal:
272 case XK_plus:
273 case XK_KP_Add:
274 new_scale += 0.1;
275 break;
276
277 case XK_1:
278 case XK_KP_Multiply:
279 case XK_KP_1:
280 new_scale = 1;
281 break;
282
283 case XK_2:
284 case XK_KP_2:
285 new_scale = 2;
286 break;
287
288 default:
289 break;
290 }
291
292 if (new_scale < 0.1)
293 new_scale = 0.1;
294 else if (5 < new_scale)
295 new_scale = 5;
296
297 if (new_scale == scale)
298 return;
299
300 scale = new_scale;
301 width = diagram->width * scale;
302 height = diagram->height * scale;
303 if (width < 400)
304 width = 400;
305 if (height < 400)
306 height = 400;
307 XResizeWindow(display, diagram_window, width, height);
308 XClearArea(display, diagram_window, 0, 0, 0, 0, True);
309 update_window_title();
310 }
311
312
313 /**
314 * Handle an X11 Expose event of the diagram window.
315 */
316 void event_diagram_expose(const XExposeEvent *expose_event)
317 {
318 cairo_surface_t *surface;
319 cairo_t *cr;
320 cairo_status_t status;
321 unsigned int i;
322
323 if (expose_event->count != 0)
324 return;
325
326 surface = cairo_xlib_surface_create(display, diagram_window,
327 DefaultVisual(display, DefaultScreen(display)),
328 diagram->width * scale, diagram->height * scale);
329 if (!surface) {
330 fprintf(stderr, "cairo_xlib_surface_create failed\n");
331 return;
332 }
333
334 cr = cairo_create(surface);
335 status = cairo_status(cr);
336 if (status != CAIRO_STATUS_SUCCESS) {
337 fprintf(stderr, "cairo_create failed: %s\n",
338 cairo_status_to_string(status));
339 cairo_destroy(cr);
340 cairo_surface_destroy(surface);
341 return;
342 }
343
344 cairo_set_source_rgb(cr, 1, 1, 1);
345 cairo_paint(cr);
346
347 for (i = 0; i != diagram->shape_count; i++) {
348 if (diagram->shape[i].path) {
349 render_path(cr, scale, &diagram->shape[i]);
350
351 } else if (diagram->shape[i].text) {
352 cairo_set_source_rgb(cr,
353 svgtiny_RED(diagram->shape[i].stroke) / 255.0,
354 svgtiny_GREEN(diagram->shape[i].stroke) / 255.0,
355 svgtiny_BLUE(diagram->shape[i].stroke) / 255.0);
356 cairo_move_to(cr,
357 scale * diagram->shape[i].text_x,
358 scale * diagram->shape[i].text_y);
359 cairo_show_text(cr, diagram->shape[i].text);
360 }
361 }
362
363 status = cairo_status(cr);
364 if (status != CAIRO_STATUS_SUCCESS) {
365 fprintf(stderr, "cairo error: %s\n",
366 cairo_status_to_string(status));
367 cairo_destroy(cr);
368 cairo_surface_destroy(surface);
369 return;
370 }
371
372 cairo_destroy(cr);
373 cairo_surface_destroy(surface);
374 }
375
376
377 /**
378 * Render an svgtiny path using cairo.
379 */
380 void render_path(cairo_t *cr, float scale, struct svgtiny_shape *path)
381 {
382 unsigned int j;
383
384 cairo_new_path(cr);
385 for (j = 0; j != path->path_length; ) {
386 switch ((int) path->path[j]) {
387 case svgtiny_PATH_MOVE:
388 cairo_move_to(cr,
389 scale * path->path[j + 1],
390 scale * path->path[j + 2]);
391 j += 3;
392 break;
393 case svgtiny_PATH_CLOSE:
394 cairo_close_path(cr);
395 j += 1;
396 break;
397 case svgtiny_PATH_LINE:
398 cairo_line_to(cr,
399 scale * path->path[j + 1],
400 scale * path->path[j + 2]);
401 j += 3;
402 break;
403 case svgtiny_PATH_BEZIER:
404 cairo_curve_to(cr,
405 scale * path->path[j + 1],
406 scale * path->path[j + 2],
407 scale * path->path[j + 3],
408 scale * path->path[j + 4],
409 scale * path->path[j + 5],
410 scale * path->path[j + 6]);
411 j += 7;
412 break;
413 default:
414 printf("error ");
415 j += 1;
416 }
417 }
418 if (path->fill != svgtiny_TRANSPARENT) {
419 cairo_set_source_rgb(cr,
420 svgtiny_RED(path->fill) / 255.0,
421 svgtiny_GREEN(path->fill) / 255.0,
422 svgtiny_BLUE(path->fill) / 255.0);
423 cairo_fill_preserve(cr);
424 }
425 if (path->stroke != svgtiny_TRANSPARENT) {
426 cairo_set_source_rgb(cr,
427 svgtiny_RED(path->stroke) / 255.0,
428 svgtiny_GREEN(path->stroke) / 255.0,
429 svgtiny_BLUE(path->stroke) / 255.0);
430 cairo_set_line_width(cr, scale * path->stroke_width);
431 cairo_stroke_preserve(cr);
432 }
433 }
434
435
436 /**
437 * Exit with fatal error.
438 */
439 void die(const char *message)
440 {
441 fprintf(stderr, "%s\n", message);
442 exit(1);
443 }
444