various changes from trying to build on testdrive
[imager.git] / tags.c
1 /*
2 =head1 NAME
3
4 tags.c - functions for manipulating an images tags list
5
6 =head1 SYNOPSIS
7
8   i_img_tags tags;
9   i_tags_new(&tags);
10   i_tags_destroy(&tags);
11   i_tags_addn(&tags, "name", code, idata);
12   i_tags_add(&tags, "name", code, data, data_size, idata);
13   if (i_tags_find(&tags, name, start, &entry)) { found }
14   if (i_tags_findn(&tags, code, start, &entry)) { found }
15   i_tags_delete(&tags, index);
16   count = i_tags_delbyname(tags, name);
17   count = i_tags_delbycode(tags, code);
18
19 =head1 DESCRIPTION
20
21 Provides functions which give write access to the tags list of an image.
22
23 For read access directly access the fields (do not write any fields
24 directly).
25
26 A tag is represented by an i_img_tag structure:
27
28   typedef enum {
29     itt_double,
30     iit_text
31   } i_tag_type;
32
33   typedef struct {
34     char *name; // name of a given tag, might be NULL 
35     int code; // number of a given tag, -1 if it has no meaning 
36     char *data; // value of a given tag if it's not an int, may be NULL 
37     int size; // size of the data 
38     int idata; // value of a given tag if data is NULL 
39   } i_img_tag;
40
41
42 =over
43
44 =cut
45 */
46
47 #include "image.h"
48 #include <string.h>
49 #include <stdlib.h>
50 #include <errno.h>
51 #include <limits.h>
52
53 /* useful for debugging */
54 void i_tags_print(i_img_tags *tags);
55
56 /*
57 =item i_tags_new(i_img_tags *tags)
58
59 Initialize a tags structure.  Should not be used if the tags structure
60 has been previously used.
61
62 To destroy the contents use i_tags_destroy()
63
64 =cut
65 */
66
67 void i_tags_new(i_img_tags *tags) {
68   tags->count = tags->alloc = 0;
69   tags->tags = NULL;
70 }
71
72 /*
73 =item i_tags_addn(i_img_tags *tags, char *name, int code, int idata)
74
75 Adds a tag that has an integer value.  A simple wrapper around i_tags_add().
76
77 Duplicate tags can be added.
78
79 Returns non-zero on success.
80
81 =cut
82 */
83
84 int i_tags_addn(i_img_tags *tags, char const *name, int code, int idata) {
85   return i_tags_add(tags, name, code, NULL, 0, idata);
86 }
87
88 /*
89 =item i_tags_add(i_img_tags *tags, char *name, int code, char *data, int size, i_tag_type type, int idata)
90
91 Adds a tag to the tags list.
92
93 Duplicate tags can be added.
94
95 Returns non-zero on success.
96
97 =cut
98 */
99
100 int i_tags_add(i_img_tags *tags, char const *name, int code, char const *data, 
101                int size, int idata) {
102   i_img_tag work = {0};
103   /*printf("i_tags_add(tags %p [count %d], name %s, code %d, data %p, size %d, idata %d)\n",
104     tags, tags->count, name, code, data, size, idata);*/
105   if (tags->tags == NULL) {
106     int alloc = 10;
107     tags->tags = mymalloc(sizeof(i_img_tag) * alloc);
108     if (!tags->tags)
109       return 0;
110     tags->alloc = alloc;
111   }
112   else if (tags->count == tags->alloc) {
113     int newalloc = tags->alloc + 10;
114     void *newtags = myrealloc(tags->tags, sizeof(i_img_tag) * newalloc);
115     if (!newtags) {
116       return 0;
117     }
118     tags->tags = newtags;
119     tags->alloc = newalloc;
120   }
121   if (name) {
122     work.name = mymalloc(strlen(name)+1);
123     if (!work.name)
124       return 0;
125     strcpy(work.name, name);
126   }
127   if (data) {
128     work.data = mymalloc(size+1);
129     if (!work.data) {
130       if (work.name) myfree(work.name);
131       return 0;
132     }
133     memcpy(work.data, data, size);
134     work.data[size] = '\0'; /* convenience */
135     work.size = size;
136   }
137   work.code = code;
138   work.idata = idata;
139   tags->tags[tags->count++] = work;
140
141   /*i_tags_print(tags);*/
142
143   return 1;
144 }
145
146 void i_tags_destroy(i_img_tags *tags) {
147   if (tags->tags) {
148     int i;
149     for (i = 0; i < tags->count; ++i) {
150       if (tags->tags[i].name)
151         myfree(tags->tags[i].name);
152       if (tags->tags[i].data)
153         myfree(tags->tags[i].data);
154     }
155     myfree(tags->tags);
156   }
157 }
158
159 int i_tags_find(i_img_tags *tags, char const *name, int start, int *entry) {
160   if (tags->tags) {
161     while (start < tags->count) {
162       if (tags->tags[start].name && strcmp(name, tags->tags[start].name) == 0) {
163         *entry = start;
164         return 1;
165       }
166       ++start;
167     }
168   }
169   return 0;
170 }
171
172 int i_tags_findn(i_img_tags *tags, int code, int start, int *entry) {
173   if (tags->tags) {
174     while (start < tags->count) {
175       if (tags->tags[start].code == code) {
176         *entry = start;
177         return 1;
178       }
179       ++start;
180     }
181   }
182   return 0;
183 }
184
185 int i_tags_delete(i_img_tags *tags, int entry) {
186   /*printf("i_tags_delete(tags %p [count %d], entry %d)\n",
187     tags, tags->count, entry);*/
188   if (tags->tags && entry >= 0 && entry < tags->count) {
189     i_img_tag old = tags->tags[entry];
190     memmove(tags->tags+entry, tags->tags+entry+1,
191             (tags->count-entry-1) * sizeof(i_img_tag));
192     if (old.name)
193       myfree(old.name);
194     if (old.data)
195       myfree(old.data);
196     --tags->count;
197
198     return 1;
199   }
200   return 0;
201 }
202
203 int i_tags_delbyname(i_img_tags *tags, char const *name) {
204   int count = 0;
205   int i;
206   /*printf("i_tags_delbyname(tags %p [count %d], name %s)\n",
207     tags, tags->count, name);*/
208   if (tags->tags) {
209     for (i = tags->count-1; i >= 0; --i) {
210       if (tags->tags[i].name && strcmp(name, tags->tags[i].name) == 0) {
211         ++count;
212         i_tags_delete(tags, i);
213       }
214     }
215   }
216   /*i_tags_print(tags);*/
217
218   return count;
219 }
220
221 int i_tags_delbycode(i_img_tags *tags, int code) {
222   int count = 0;
223   int i;
224   if (tags->tags) {
225     for (i = tags->count-1; i >= 0; --i) {
226       if (tags->tags[i].code == code) {
227         ++count;
228         i_tags_delete(tags, i);
229       }
230     }
231   }
232   return count;
233 }
234
235 int i_tags_get_float(i_img_tags *tags, char const *name, int code, 
236                      double *value) {
237   int index;
238   i_img_tag *entry;
239
240   if (name) {
241     if (!i_tags_find(tags, name, 0, &index))
242       return 0;
243   }
244   else {
245     if (!i_tags_findn(tags, code, 0, &index))
246       return 0;
247   }
248   entry = tags->tags+index;
249   if (entry->data)
250     *value = atof(entry->data);
251   else
252     *value = entry->idata;
253
254   return 1;
255 }
256
257 int i_tags_set_float(i_img_tags *tags, char const *name, int code, 
258                      double value) {
259   char temp[40];
260
261   sprintf(temp, "%.30g", value);
262   if (name)
263     i_tags_delbyname(tags, name);
264   else
265     i_tags_delbycode(tags, code);
266
267   return i_tags_add(tags, name, code, temp, strlen(temp), 0);
268 }
269
270 int i_tags_get_int(i_img_tags *tags, char const *name, int code, int *value) {
271   int index;
272   i_img_tag *entry;
273
274   if (name) {
275     if (!i_tags_find(tags, name, 0, &index))
276       return 0;
277   }
278   else {
279     if (!i_tags_findn(tags, code, 0, &index))
280       return 0;
281   }
282   entry = tags->tags+index;
283   if (entry->data)
284     *value = atoi(entry->data);
285   else
286     *value = entry->idata;
287
288   return 1;
289 }
290
291 static int parse_long(char *data, char **end, long *out) {
292 #if 0
293   /* I wrote this without thinking about strtol */
294   long x = 0;
295   int neg = *data == '-';
296
297   if (neg)
298     ++data;
299   if (!isdigit(*data))
300     return 0;
301   while (isdigit(*data)) {
302     /* this check doesn't guarantee we don't overflow, but it helps */
303     if (x > LONG_MAX / 10)
304       return 0; 
305     x = x * 10 + *data - '0';
306     ++data;
307   }
308   if (neg)
309     x = -x;
310
311   *end = data;
312
313   return 1;
314 #else
315   long result;
316   int savederr = errno;
317   char *myend;
318
319   errno = 0;
320   result = strtol(data, &myend, 10);
321   if ((result == LONG_MIN || result == LONG_MAX) && errno == ERANGE
322       || myend == data) {
323     return 0;
324   }
325
326   *out = result;
327   *end = myend;
328
329   return 1;
330 #endif
331 }
332
333 /* parse a comma-separated list of integers
334    returns when it has maxcount numbers, finds a non-comma after a number
335    or can't parse a number
336    if it can't parse a number after a comma, that's considered an error
337 */
338 static int parse_long_list(char *data, char **end, int maxcount, long *out) {
339   int i;
340
341   i = 0;
342   while (i < maxcount-1) {
343     if (!parse_long(data, &data, out))
344       return 0;
345     out++;
346     i++;
347     if (*data != ',')
348       return i;
349     ++data;
350   }
351   if (!parse_long(data, &data, out))
352     return 0;
353   ++i;
354   *end = data;
355   return i;
356 }
357
358 /* parse "color(red,green,blue,alpha)" */
359 static int parse_color(char *data, char **end, i_color *value) {
360   long n[4];
361   int count, i;
362   
363   if (memcmp(data, "color(", 6))
364     return 0; /* not a color */
365   data += 6;
366   count = parse_long_list(data, &data, 4, n);
367   if (count < 3)
368     return 0;
369   for (i = 0; i < count; ++i)
370     value->channel[i] = n[i];
371   if (count < 4)
372     value->channel[3] = 255;
373
374   return 1;
375 }
376
377 int i_tags_get_color(i_img_tags *tags, char const *name, int code, 
378                      i_color *value) {
379   int index;
380   i_img_tag *entry;
381   char *end;
382
383   if (name) {
384     if (!i_tags_find(tags, name, 0, &index))
385       return 0;
386   }
387   else {
388     if (!i_tags_findn(tags, code, 0, &index))
389       return 0;
390   }
391   entry = tags->tags+index;
392   if (!entry->data) 
393     return 0;
394
395   if (!parse_color(entry->data, &end, value))
396     return 0;
397   
398   /* for now we're sloppy about the end */
399
400   return 1;
401 }
402
403 int i_tags_set_color(i_img_tags *tags, char const *name, int code, 
404                      i_color const *value) {
405   char temp[80];
406
407   sprintf(temp, "color(%d,%d,%d,%d)", value->channel[0], value->channel[1],
408           value->channel[2], value->channel[3]);
409   if (name)
410     i_tags_delbyname(tags, name);
411   else
412     i_tags_delbycode(tags, code);
413
414   return i_tags_add(tags, name, code, temp, strlen(temp), 0);
415 }
416
417 int i_tags_get_string(i_img_tags *tags, char const *name, int code, 
418                       char *value, size_t value_size) {
419   int index;
420   i_img_tag *entry;
421
422   if (name) {
423     if (!i_tags_find(tags, name, 0, &index))
424       return 0;
425   }
426   else {
427     if (!i_tags_findn(tags, code, 0, &index))
428       return 0;
429   }
430   entry = tags->tags+index;
431   if (entry->data) {
432     size_t cpsize = value_size < entry->size ? value_size : entry->size;
433     memcpy(value, entry->data, cpsize);
434     if (cpsize == value_size)
435       --cpsize;
436     value[cpsize] = '\0';
437   }
438   else {
439     sprintf(value, "%d", entry->data);
440   }
441
442   return 1;
443 }
444
445 void i_tags_print(i_img_tags *tags) {
446   int i;
447   printf("Alloc %d\n", tags->alloc);
448   printf("Count %d\n", tags->count);
449   for (i = 0; i < tags->count; ++i) {
450     i_img_tag *tag = tags->tags + i;
451     printf("Tag %d\n", i);
452     if (tag->name)
453       printf(" Name : %s (%p)\n", tag->name, tag->name);
454     printf(" Code : %d\n", tag->code);
455     if (tag->data) {
456       int pos;
457       printf(" Data : %d (%p) => '", tag->size, tag->data);
458       for (pos = 0; pos < tag->size; ++pos) {
459         if (tag->data[pos] == '\\' || tag->data[pos] == '\'') {
460           putchar('\\');
461           putchar(tag->data[pos]);
462         }
463         else if (tag->data[pos] < ' ' || tag->data[pos] >= '\x7E')
464           printf("\\x%02X", tag->data[pos]);
465         else
466           putchar(tag->data[pos]);
467       }
468       printf("'\n");
469       printf(" Idata: %d\n", tag->idata);
470     }
471   }
472 }
473
474 /*
475 =back
476
477 =head1 AUTHOR
478
479 Tony Cook <tony@develop-help.com>
480
481 =head1 SEE ALSO
482
483 Imager(3)
484
485 =cut
486 */