/[CvsGraph]/cvsgraph/cvsgraph.c
ViewVC logotype

Annotate of /cvsgraph/cvsgraph.c

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.64 - (hide annotations)
Wed May 21 01:41:18 2008 UTC (9 years, 4 months ago) by bertho
Branch: MAIN
CVS Tags: REL_1_6_2, source_head_20080521
Changes since 1.63: +224 -247 lines
File MIME type: text/plain
- Do a better job at drawing the merge lines by selecting the shortest path
  from the revision boxes. An analysis is now done whether the source and
  destinations should be on the left or right side.
- Fix the left_right case for merge lines to display correctly.
- Fix a +/-1 error on the merge lines to account for both rounding errors and
  the shadow of the revision boxes.
- Add configuration option 'merge_on_tag' to force the left_right case to
  display merge lines on the tags instead of on the top/bottom sides. This
  also solves imagemap overlaps where multiple sources or destinations would
  be displayed at the same position.
- Fix the imagemap function to record the correct position of the merges.
1 bertho 1.1 /*
2     * CvsGraph graphical representation generator of brances and revisions
3     * of a file in cvs/rcs.
4     *
5 bertho 1.62 * Copyright (C) 2001,2002,2003,2004,2005,2006 B. Stultiens
6 bertho 1.1 *
7     * This program is free software; you can redistribute it and/or modify
8     * it under the terms of the GNU General Public License as published by
9     * the Free Software Foundation; either version 2 of the License, or
10     * (at your option) any later version.
11     *
12     * This program is distributed in the hope that it will be useful,
13     * but WITHOUT ANY WARRANTY; without even the implied warranty of
14     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15     * GNU General Public License for more details.
16     *
17     * You should have received a copy of the GNU General Public License
18     * along with this program; if not, write to the Free Software
19     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20     */
21    
22 bertho 1.14 #include "config.h"
23    
24 bertho 1.1 #include <stdio.h>
25     #include <stdlib.h>
26 bertho 1.44 #include <stdarg.h>
27 bertho 1.1 #include <unistd.h>
28     #include <string.h>
29     #include <assert.h>
30     #include <sys/types.h>
31     #include <sys/stat.h>
32 bertho 1.45 #ifdef HAVE_SYS_WAIT_H
33     # include <sys/wait.h>
34     #endif
35 bertho 1.1 #include <fcntl.h>
36     #include <regex.h>
37     #include <errno.h>
38 bertho 1.8 #include <ctype.h>
39 bertho 1.9 #include <time.h>
40 bertho 1.18 #include <limits.h>
41 bertho 1.35 #include <math.h>
42 bertho 1.9
43 bertho 1.14 #ifdef HAVE_GETOPT_H
44     # include <getopt.h>
45     #endif
46    
47 bertho 1.1 #include <gd.h>
48     #include <gdfontt.h>
49    
50     #include "cvsgraph.h"
51     #include "utils.h"
52     #include "readconf.h"
53 bertho 1.7 #include "rcs.h"
54 bertho 1.1
55 bertho 1.5 #if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG) && !defined(HAVE_IMAGE_JPEG)
56     # error No image output format available. Check libgd
57     #endif
58    
59    
60 bertho 1.21 /*#define DEBUG 1*/
61 bertho 1.19 /*#define NOGDFILL 1*/
62 bertho 1.26 /*#define DEBUG_IMAGEMAP 1*/
63 bertho 1.19
64 bertho 1.32 #define LOOPSAFEGUARD 10000 /* Max itterations in possible infinite loops */
65 bertho 1.1
66     #ifndef MAX
67     # define MAX(a,b) ((a) > (b) ? (a) : (b))
68     #endif
69    
70     #ifndef MIN
71     # define MIN(a,b) ((a) < (b) ? (a) : (b))
72     #endif
73    
74     #define ALIGN_HL 0x00
75     #define ALIGN_HC 0x01
76     #define ALIGN_HR 0x02
77 bertho 1.9 #define ALIGN_HX 0x0f
78 bertho 1.1 #define ALIGN_VT 0x00
79     #define ALIGN_VC 0x10
80     #define ALIGN_VB 0x20
81     #define ALIGN_VX 0xf0
82 bertho 1.9
83 bertho 1.35 #ifndef M_PI /* math.h should have defined this */
84     # define M_PI 3.14159265358979323846
85     #endif
86     #define ROUND(f) ((f >= 0.0)?((int)(f + 0.5)):((int)(f - 0.5)))
87    
88     #define ARROW_LENGTH 12 /* Default arrow dimensions */
89     #define ARROW_WIDTH 3
90    
91 bertho 1.1 /*
92     **************************************************************************
93     * Globals
94     **************************************************************************
95     */
96    
97     config_t conf;
98 bertho 1.7 int debuglevel;
99 bertho 1.44
100 bertho 1.60 static color_t white_color = {255, 255, 255, 0, NULL};
101     static color_t black_color = {0, 0, 0, 0, NULL};
102 bertho 1.44
103     static branch_t *subtree_branch = NULL; /* Set to the (first) subtree branch that we want to show */
104     static revision_t *subtree_rev = NULL; /* Set to the subtree revision which branches we want to show */
105    
106     static msg_stack_t *msg_stack = NULL; /* Messages that would otherwise be sent to stderr goto the image */
107     static int nmsg_stack = 0;
108 bertho 1.9
109 bertho 1.30 /*
110     **************************************************************************
111     * Forwards
112     **************************************************************************
113     */
114     static void zap_string(void);
115     static char *dup_string(void);
116     static void add_string_str(const char *s);
117     static void add_string_ch(int ch);
118     static void add_string_date(const char *d);
119     static void add_string_str_html(const char *s, int maxlen);
120     static void add_string_str_len(const char *s, int maxlen);
121 bertho 1.1
122 bertho 1.44 static void calc_subtree_size(branch_t *b, int *x, int *y, int *w, int *h);
123    
124 bertho 1.1 /*
125     **************************************************************************
126 bertho 1.17 * Debug routines
127 bertho 1.1 **************************************************************************
128     */
129 bertho 1.19 static void dump_rev(char *p, rev_t *r)
130 bertho 1.1 {
131 bertho 1.7 printf("%s", p);
132     if(r)
133     printf("'%s', '%s', %d\n", r->rev, r->branch, r->isbranch);
134     else
135     printf("<null>\n");
136 bertho 1.1 }
137    
138 bertho 1.19 static void dump_id(char *p, char *d)
139 bertho 1.1 {
140 bertho 1.7 printf("%s", p);
141     if(d)
142     printf("'%s'\n", d);
143     else
144     printf("<null>\n");
145 bertho 1.1 }
146    
147 bertho 1.19 static void dump_idrev(char *p, idrev_t *t)
148 bertho 1.1 {
149 bertho 1.7 printf("%s", p);
150     if(t)
151 bertho 1.1 {
152 bertho 1.7 printf("'%s' -> ", t->id);
153     dump_rev("", t->rev);
154 bertho 1.1 }
155     else
156 bertho 1.7 printf("<null>\n");
157 bertho 1.1 }
158    
159 bertho 1.19 static void dump_tag(char *p, tag_t *t)
160 bertho 1.1 {
161 bertho 1.7 printf("%s", p);
162     if(t)
163 bertho 1.1 {
164 bertho 1.7 printf("'%s' -> ", t->tag);
165     dump_rev("", t->rev);
166 bertho 1.1 }
167 bertho 1.7 else
168     printf("<null>\n");
169 bertho 1.1 }
170    
171 bertho 1.19 static void dump_delta(char *p, delta_t *d)
172 bertho 1.1 {
173 bertho 1.7 int i;
174     printf("%sdelta.rev : ", p);
175     dump_rev("", d->rev);
176     printf("%sdelta.date : %s\n", p, d->date);
177     printf("%sdelta.author: %s\n", p, d->author);
178     printf("%sdelta.state : %s\n", p, d->state);
179     for(i = 0; d->branches && i < d->branches->nrevs; i++)
180 bertho 1.1 {
181 bertho 1.7 printf("%sdelta.branch: ", p);
182     dump_rev("", d->branches->revs[i]);
183 bertho 1.1 }
184 bertho 1.7 printf("%sdelta.next : ", p);
185     dump_rev("", d->next);
186     printf("\n");
187 bertho 1.1 }
188    
189 bertho 1.19 static void dump_dtext(char *p, dtext_t *d)
190 bertho 1.1 {
191 bertho 1.7 printf("%sdtext.rev : ", p);
192     dump_rev("", d->rev);
193     printf("%sdtext.log : %d bytes\n", p, d->log ? strlen(d->log) : -1);
194     printf("%sdtext.text : %d bytes\n", p, d->text ? strlen(d->text) : -1);
195     printf("\n");
196 bertho 1.1 }
197    
198 bertho 1.19 static void dump_rcsfile(rcsfile_t *rcs)
199 bertho 1.1 {
200 bertho 1.7 int i;
201     printf("root : '%s'\n", rcs->root);
202     printf("module : '%s'\n", rcs->module);
203     printf("file : '%s'\n", rcs->file);
204     dump_rev("head : ", rcs->head);
205     dump_rev("branch : ", rcs->branch);
206     printf("access :\n");
207     for(i = 0; rcs->access && i < rcs->access->nids; i++)
208     dump_id("\t", rcs->access->ids[i]);
209     printf("tags :\n");
210     for(i = 0; rcs->tags && i < rcs->tags->ntags; i++)
211     dump_tag("\t", rcs->tags->tags[i]);
212     printf("locks :%s\n", rcs->strict ? " (strict)" : "");
213     for(i = 0; rcs->locks && i < rcs->locks->nidrevs; i++)
214     dump_idrev("\t", rcs->locks->idrevs[i]);
215     printf("comment: '%s'\n", rcs->comment);
216     printf("expand : '%s'\n", rcs->expand ? rcs->expand : "(default -kv)");
217     printf("deltas :\n");
218     for(i = 0; rcs->deltas && i < rcs->deltas->ndeltas; i++)
219     dump_delta("\t", rcs->deltas->deltas[i]);
220     printf("desc : '%s'\n", rcs->desc);
221     printf("dtexts :\n");
222     for(i = 0; rcs->dtexts && i < rcs->dtexts->ndtexts; i++)
223     dump_dtext("\t", rcs->dtexts->dtexts[i]);
224    
225     fflush(stdout);
226 bertho 1.1 }
227    
228 bertho 1.7 /*
229     **************************************************************************
230 bertho 1.44 * Error/Warning Message helpers
231     **************************************************************************
232     */
233     #define MSGBUFSIZE 256
234     void stack_msg(int severity, const char *fmt, ...)
235     {
236     va_list va;
237     int i;
238     char *buf = xmalloc(MSGBUFSIZE);
239     switch(severity)
240     {
241     case MSG_WARN: sprintf(buf, "Warning: "); break;
242     case MSG_ERR: sprintf(buf, "Error: "); break;
243     default: sprintf(buf, "Unqualified error: "); break;
244     }
245     i = strlen(buf);
246     assert(i < MSGBUFSIZE);
247     va_start(va, fmt);
248     vsnprintf(buf+i, MSGBUFSIZE-i, fmt, va);
249     va_end(va);
250     if(!msg_stack)
251     msg_stack = xmalloc(sizeof(*msg_stack));
252     else
253     {
254     msg_stack = xrealloc(msg_stack, (nmsg_stack+1)*sizeof(*msg_stack));
255     }
256     msg_stack[nmsg_stack].msg = buf;
257     msg_stack[nmsg_stack].severity = severity;
258     nmsg_stack++;
259     }
260    
261     /*
262     **************************************************************************
263 bertho 1.7 * Read the rcs file
264     **************************************************************************
265     */
266 bertho 1.44 static rcsfile_t *get_rcsfile(const char *cvsroot, const char *module, const char *file)
267 bertho 1.1 {
268 bertho 1.7 char *cmd = NULL;
269     int rv;
270 bertho 1.1
271 bertho 1.18 if(file)
272 bertho 1.9 {
273 bertho 1.18 cmd = xmalloc(strlen(cvsroot) + strlen(module) + strlen(file) + 1);
274     sprintf(cmd, "%s%s%s", cvsroot, module, file);
275     if(!(rcsin = fopen(cmd, "rb")))
276     {
277     perror(cmd);
278     return NULL;
279     }
280     input_file = cmd;
281     }
282     else
283     {
284     rcsin = stdin;
285     input_file = "<stdin>";
286 bertho 1.9 }
287 bertho 1.7 line_number = 1;
288     rv = rcsparse();
289 bertho 1.18 if(file)
290     {
291     fclose(rcsin);
292     xfree(cmd);
293     }
294 bertho 1.7 if(rv)
295 bertho 1.1 return NULL;
296 bertho 1.7 input_file = NULL;
297 bertho 1.18 if(file)
298     {
299     rcsfile->root = xstrdup(cvsroot);
300     rcsfile->module = xstrdup(module);
301     rcsfile->file = xstrdup(file);
302     }
303     else
304     {
305     rcsfile->root = xstrdup("");
306     rcsfile->module = xstrdup("");
307     rcsfile->file = xstrdup("<stdin>");
308     }
309 bertho 1.7 return rcsfile;
310 bertho 1.1 }
311    
312     /*
313     **************************************************************************
314     * Sort and find helpers
315     **************************************************************************
316     */
317 bertho 1.44 static int count_dots(const char *s)
318 bertho 1.5 {
319     int i;
320     for(i = 0; *s; s++)
321     {
322     if(*s == '.')
323     i++;
324     }
325     return i;
326     }
327    
328 bertho 1.44 static int compare_rev(int bcmp, const rev_t *r1, const rev_t *r2)
329 bertho 1.5 {
330     int d1, d2;
331 bertho 1.7 char *c1, *c2;
332 bertho 1.5 char *v1, *v2;
333     char *s1, *s2;
334     int retval = 0;
335     assert(r1 != NULL);
336     assert(r2 != NULL);
337 bertho 1.7 if(bcmp)
338     {
339     assert(r1->branch != NULL);
340     assert(r2->branch != NULL);
341     c1 = r1->branch;
342     c2 = r2->branch;
343     }
344     else
345     {
346     assert(r1->rev != NULL);
347     assert(r2->rev != NULL);
348     c1 = r1->rev;
349     c2 = r2->rev;
350     }
351 bertho 1.5
352 bertho 1.7 d1 = count_dots(c1);
353     d2 = count_dots(c2);
354 bertho 1.5 if(d1 != d2)
355     {
356     return d1 - d2;
357     }
358    
359 bertho 1.7 s1 = v1 = xstrdup(c1);
360     s2 = v2 = xstrdup(c2);
361 bertho 1.5 while(1)
362     {
363     char *vc1 = strchr(s1, '.');
364     char *vc2 = strchr(s2, '.');
365     if(vc1 && vc2)
366     *vc1 = *vc2 = '\0';
367     if(*s1 && *s2)
368     {
369     d1 = atoi(s1);
370     d2 = atoi(s2);
371     if(d1 != d2)
372     {
373     retval = d1 - d2;
374     break;
375     }
376     }
377     if(!vc1 || !vc2)
378     break;
379     s1 = vc1 + 1;
380     s2 = vc2 + 1;
381     }
382     xfree(v1);
383     xfree(v2);
384     return retval;
385     }
386    
387 bertho 1.7 /*
388     **************************************************************************
389     * Reorganise the rcsfile for the branches
390     *
391     * Basically, we have a list of deltas (i.e. administrative info on
392     * revisions) and a list of delta text (the actual logs and diffs).
393     * The deltas are linked through the 'next' and the 'branches' fields
394     * which describe the tree-structure of revisions.
395     * The reorganisation means that we put each delta and corresponding
396     * delta text in a revision structure and assign it to a specific
397     * branch. This is required because we want to be able to calculate
398     * the bounding boxes of each branch. The revisions expand vertically
399     * and the branches expand horizontally.
400     * The reorganisation is performed in these steps:
401 bertho 1.17 * 1 - sort deltas and delta text on revision number for quick lookup
402 bertho 1.7 * 2 - start at the denoted head revision:
403     * * create a branch structure and add this revision
404     * * for each 'branches' in the delta do:
405     * - walk all 'branches' of the delta and recursively goto 2
406     * with the denoted branch delta as new head
407     * - backlink the newly create sub-branch to the head revision
408     * so that we can draw them recursively
409     * * set head to the 'next' field and goto 2 until no next is
410     * available
411     * 3 - update the administration
412     **************************************************************************
413     */
414 bertho 1.19 static int sort_delta(const void *d1, const void *d2)
415 bertho 1.7 {
416     return compare_rev(0, (*(delta_t **)d1)->rev, (*(delta_t **)d2)->rev);
417     }
418    
419 bertho 1.19 static int search_delta(const void *r, const void *d)
420 bertho 1.1 {
421 bertho 1.7 return compare_rev(0, (rev_t *)r, (*(delta_t **)d)->rev);
422 bertho 1.1 }
423    
424 bertho 1.19 static delta_t *find_delta(delta_t **dl, int n, rev_t *r)
425 bertho 1.1 {
426 bertho 1.7 delta_t **d;
427 bertho 1.23 if(!n)
428     return NULL;
429 bertho 1.7 d = bsearch(r, dl, n, sizeof(*dl), search_delta);
430     if(!d)
431     return NULL;
432     return *d;
433 bertho 1.1 }
434    
435 bertho 1.19 static int sort_dtext(const void *d1, const void *d2)
436 bertho 1.1 {
437 bertho 1.7 return compare_rev(0, (*(dtext_t **)d1)->rev, (*(dtext_t **)d2)->rev);
438 bertho 1.1 }
439    
440 bertho 1.19 static int search_dtext(const void *r, const void *d)
441 bertho 1.1 {
442 bertho 1.7 return compare_rev(0, (rev_t *)r, (*(dtext_t **)d)->rev);
443 bertho 1.1 }
444    
445 bertho 1.19 static dtext_t *find_dtext(dtext_t **dl, int n, rev_t *r)
446 bertho 1.1 {
447 bertho 1.7 dtext_t **d;
448 bertho 1.23 if(!n)
449     return NULL;
450 bertho 1.7 d = bsearch(r, dl, n, sizeof(*dl), search_dtext);
451     if(!d)
452 bertho 1.1 return NULL;
453 bertho 1.7 return *d;
454     }
455    
456 bertho 1.19 static rev_t *dup_rev(const rev_t *r)
457 bertho 1.7 {
458     rev_t *t = xmalloc(sizeof(*t));
459     t->rev = xstrdup(r->rev);
460     t->branch = xstrdup(r->branch);
461     t->isbranch = r->isbranch;
462     return t;
463     }
464    
465 bertho 1.19 static branch_t *new_branch(delta_t *d, dtext_t *t)
466 bertho 1.7 {
467     branch_t *b = xmalloc(sizeof(*b));
468     revision_t *r = xmalloc(sizeof(*r));
469     r->delta = d;
470     r->dtext = t;
471     r->rev = d->rev;
472     r->branch = b;
473     b->branch = dup_rev(d->rev);
474     b->branch->isbranch = 1;
475     b->nrevs = 1;
476     b->revs = xmalloc(sizeof(b->revs[0]));
477     b->revs[0] = r;
478     return b;
479     }
480    
481 bertho 1.19 static revision_t *add_to_branch(branch_t *b, delta_t *d, dtext_t *t)
482 bertho 1.7 {
483     revision_t *r = xmalloc(sizeof(*r));
484     r->delta = d;
485     r->dtext = t;
486     r->rev = d->rev;
487     r->branch = b;
488     b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));
489     b->revs[b->nrevs] = r;
490     b->nrevs++;
491     return r;
492     }
493    
494 bertho 1.47 static int sort_branch_height(const void *b1, const void *b2)
495     {
496     return (*(branch_t **)b1)->nrevs - (*(branch_t **)b2)->nrevs;
497     }
498    
499 bertho 1.44 static void build_branch(branch_t ***bl, int *nbl, delta_t **sdl, int nsdl, dtext_t **sdt, int nsdt, delta_t *head)
500 bertho 1.7 {
501     branch_t *b;
502     dtext_t *text;
503     revision_t *currev;
504    
505     assert(head != NULL);
506    
507     if(head->flag)
508     {
509 bertho 1.44 stack_msg(MSG_ERR, "Circular reference on '%s' in branchpoint\n", head->rev->rev);
510 bertho 1.7 return;
511     }
512     head->flag++;
513     text = find_dtext(sdt, nsdt, head->rev);
514    
515     /* Create a new branch for this head */
516     b = new_branch(head, text);
517     *bl = xrealloc(*bl, (*nbl+1)*sizeof((*bl)[0]));
518     (*bl)[*nbl] = b;
519     (*nbl)++;
520     currev = b->revs[0];
521     while(1)
522     {
523     /* Process all sub-branches */
524     if(head->branches)
525     {
526     int i;
527     for(i = 0; i < head->branches->nrevs; i++)
528     {
529     delta_t *d = find_delta(sdl, nsdl, head->branches->revs[i]);
530     int btag = *nbl;
531     if(!d)
532     continue;
533     build_branch(bl, nbl, sdl, nsdl, sdt, nsdt, d);
534    
535     /* Set the new branch's origin */
536     (*bl)[btag]->branchpoint = currev;
537    
538     /* Add branch to this revision */
539     currev->branches = xrealloc(currev->branches, (currev->nbranches+1)*sizeof(currev->branches[0]));
540     currev->branches[currev->nbranches] = (*bl)[btag];
541     currev->nbranches++;
542     }
543 bertho 1.47 if(conf.branch_resort)
544     qsort(currev->branches, currev->nbranches, sizeof(currev->branches[0]), sort_branch_height);
545 bertho 1.7 }
546    
547     /* Walk through the next list */
548     if(!head->next)
549     return;
550 bertho 1.9
551 bertho 1.7 head = find_delta(sdl, nsdl, head->next);
552     if(!head)
553     {
554 bertho 1.44 stack_msg(MSG_ERR, "Next revision (%s) not found in deltalist\n", head->next->rev);
555 bertho 1.7 return;
556     }
557     if(head->flag)
558     {
559 bertho 1.44 stack_msg(MSG_ERR, "Circular reference on '%s'\n", head->rev->rev);
560 bertho 1.7 return;
561     }
562     head->flag++;
563     text = find_dtext(sdt, nsdt, head->rev);
564     currev = add_to_branch(b, head, text);
565     }
566     }
567    
568     int reorganise_branches(rcsfile_t *rcs)
569     {
570     delta_t **sdelta;
571     int nsdelta;
572     dtext_t **sdtext;
573     int nsdtext;
574     delta_t *head;
575     branch_t **bl;
576     int nbl;
577     int i;
578    
579     assert(rcs->deltas != NULL);
580     assert(rcs->head != NULL);
581    
582     /* Make a new list for quick lookup */
583     nsdelta = rcs->deltas->ndeltas;
584     sdelta = xmalloc(nsdelta * sizeof(sdelta[0]));
585     memcpy(sdelta, rcs->deltas->deltas, nsdelta * sizeof(sdelta[0]));
586     qsort(sdelta, nsdelta, sizeof(sdelta[0]), sort_delta);
587    
588     /* Do the same for the delta text */
589 bertho 1.22 if(rcs->dtexts)
590     {
591     nsdtext = rcs->dtexts->ndtexts;
592     sdtext = xmalloc(nsdtext * sizeof(sdtext[0]));
593     memcpy(sdtext, rcs->dtexts->dtexts, nsdtext * sizeof(sdtext[0]));
594     qsort(sdtext, nsdtext, sizeof(sdtext[0]), sort_dtext);
595     }
596     else
597     {
598     nsdtext = 0;
599     sdtext = NULL;
600     }
601 bertho 1.7
602     /* Start from the head of the trunk */
603     head = find_delta(sdelta, nsdelta, rcs->head);
604     if(!head)
605     {
606 bertho 1.44 stack_msg(MSG_ERR, "Head revision (%s) not found in deltalist\n", rcs->head->rev);
607 bertho 1.7 return 0;
608     }
609     bl = NULL;
610     nbl = 0;
611     build_branch(&bl, &nbl, sdelta, nsdelta, sdtext, nsdtext, head);
612    
613     /* Reverse the head branch */
614     for(i = 0; i < bl[0]->nrevs/2; i++)
615     {
616     revision_t *r;
617     r = bl[0]->revs[i];
618     bl[0]->revs[i] = bl[0]->revs[bl[0]->nrevs-i-1];
619     bl[0]->revs[bl[0]->nrevs-i-1] = r;
620     }
621    
622     /* Update the branch-number of the head because it was reversed */
623     xfree(bl[0]->branch->branch);
624     bl[0]->branch->branch = xstrdup(bl[0]->revs[0]->rev->branch);
625    
626     /* Keep the admin */
627     rcs->branches = bl;
628     rcs->nbranches = nbl;
629     rcs->sdelta = sdelta;
630     rcs->nsdelta = nsdelta;
631     rcs->sdtext = sdtext;
632     rcs->nsdtext = nsdtext;
633     rcs->active = bl[0];
634     return 1;
635     }
636    
637     /*
638     **************************************************************************
639     * Assign the symbolic tags to the revisions and branches
640     *
641     * The tags point to revision numbers somewhere in the tree structure
642     * of branches and revisions. First we make a sorted list of all
643     * revisions and then we assign each tag to the proper revision.
644     **************************************************************************
645     */
646 bertho 1.19 static int sort_revision(const void *r1, const void *r2)
647 bertho 1.7 {
648     return compare_rev(0, (*(revision_t **)r1)->delta->rev, (*(revision_t **)r2)->delta->rev);
649     }
650    
651 bertho 1.19 static int search_revision(const void *t, const void *r)
652 bertho 1.7 {
653     return compare_rev(0, (rev_t *)t, (*(revision_t **)r)->delta->rev);
654     }
655    
656 bertho 1.19 static int sort_branch(const void *b1, const void *b2)
657 bertho 1.7 {
658     return compare_rev(1, (*(branch_t **)b1)->branch, (*(branch_t **)b2)->branch);
659 bertho 1.1 }
660    
661 bertho 1.19 static int search_branch(const void *t, const void *b)
662 bertho 1.1 {
663 bertho 1.7 return compare_rev(1, (rev_t *)t, (*(branch_t **)b)->branch);
664 bertho 1.1 }
665    
666 bertho 1.19 static char *previous_rev(const char *c)
667 bertho 1.1 {
668 bertho 1.7 int dots = count_dots(c);
669     char *cptr;
670     char *r;
671     if(!dots)
672 bertho 1.9 {
673 bertho 1.44 stack_msg(MSG_ERR, "FIXME: previous_rev(\"%s\"): Cannot determine parent branch revision\n", c);
674 bertho 1.7 return xstrdup("1.0"); /* FIXME: don't know what the parent is */
675 bertho 1.9 }
676 bertho 1.7 if(dots & 1)
677     {
678     /* Is is a revision we want the parent of */
679     r = xstrdup(c);
680     cptr = strrchr(r, '.');
681     assert(cptr != NULL);
682     if(dots == 1)
683     {
684 bertho 1.44 stack_msg(MSG_ERR, "FIXME: previous_rev(\"%s\"): Going beyond top-level?\n", c);
685 bertho 1.7 /* FIXME: What is the parent of 1.1? */
686     cptr[1] = '\0';
687     strcat(r, "0");
688     return r;
689     }
690     /* Here we have a "x.x[.x.x]+" case */
691     *cptr = '\0';
692     cptr = strrchr(r, '.');
693     assert(cptr != NULL);
694     *cptr = '\0';
695     return r;
696     }
697     /* It is a branch we want the parent of */
698     r = xstrdup(c);
699     cptr = strrchr(r, '.');
700     assert(cptr != NULL);
701     *cptr = '\0';
702     return r;
703 bertho 1.1 }
704    
705 bertho 1.59 static char *build_regex(size_t n, regmatch_t *m, const char *ms, int idx)
706 bertho 1.30 {
707     char *cptr;
708     int i;
709    
710 bertho 1.59 if(!conf.merge_to.strs[idx])
711 bertho 1.30 return NULL;
712    
713     zap_string();
714 bertho 1.59 for(cptr = conf.merge_to.strs[idx]; *cptr; cptr++)
715 bertho 1.30 {
716     if(*cptr == '%')
717     {
718     if(cptr[1] >= '1' && cptr[1] <= '9')
719     {
720     int idx = cptr[1] - '0';
721     regmatch_t *p = &m[idx];
722     if(idx < n && !(p->rm_so == -1 || p->rm_so >= p->rm_eo))
723     {
724     for(i = p->rm_so; i < p->rm_eo; i++)
725     {
726     if(strchr("^$.*+\\[{()", ms[i]))
727     add_string_ch('\\');
728     add_string_ch(ms[i]);
729     }
730     }
731     cptr++;
732     }
733     else
734     add_string_ch('%');
735     }
736     else
737     add_string_ch(*cptr);
738     }
739     return dup_string();
740     }
741    
742 bertho 1.49 static void find_merges_cvsnt(rcsfile_t *rcs)
743     {
744     int i;
745    
746     if(!conf.merge_cvsnt)
747     return;
748    
749     for(i = 0; i < rcs->nsrev; i++)
750     {
751     revision_t **r;
752    
753     if(!rcs->srev[i]->delta->mergepoint)
754     continue;
755    
756     r = bsearch(rcs->srev[i]->delta->mergepoint->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
757     if(!r)
758     continue;
759     rcs->merges = xrealloc(rcs->merges, sizeof(rcs->merges[0]) * (rcs->nmerges+1));
760     rcs->merges[rcs->nmerges].type = TR_REVISION;
761     rcs->merges[rcs->nmerges].from.rev = *r;
762     rcs->merges[rcs->nmerges].to.rev = rcs->srev[i];
763 bertho 1.59 rcs->merges[rcs->nmerges].clr = -1;
764 bertho 1.49 rcs->nmerges++;
765 bertho 1.60 (*r)->mergetarget = 1;
766     rcs->srev[i]->mergetarget = 1;
767 bertho 1.49 }
768     }
769    
770 bertho 1.31 static void find_merges(rcsfile_t *rcs)
771 bertho 1.1 {
772 bertho 1.59 int i, j;
773 bertho 1.30 int err;
774 bertho 1.31 int rcflags = REG_EXTENDED | (conf.merge_nocase ? REG_ICASE : 0);
775 bertho 1.30 regex_t *refrom = NULL;
776     regex_t *reto = NULL;
777     regmatch_t *matchfrom = NULL;
778    
779 bertho 1.59 if(!conf.merge_from.n || !conf.merge_to.n)
780 bertho 1.31 return;
781    
782 bertho 1.59 for(j = 0; j < conf.merge_from.n; j++)
783     {
784     if(!conf.merge_from.strs[0] || !conf.merge_to.strs[0])
785     continue;
786 bertho 1.31
787 bertho 1.59 refrom = xmalloc(sizeof(*refrom));
788     reto = xmalloc(sizeof(*reto));
789 bertho 1.30
790 bertho 1.59 /* Compile the 'from' regex match for merge identification */
791     err = regcomp(refrom, conf.merge_from.strs[j], rcflags);
792     if(err)
793     {
794     char *msg;
795     i = regerror(err, refrom, NULL, 0);
796     msg = xmalloc(i+1);
797     regerror(err, refrom, msg, i+1);
798     stack_msg(MSG_WARN, "%s", msg);
799     xfree(msg);
800     xfree(refrom);
801     xfree(reto);
802     return;
803     }
804     else
805     matchfrom = xmalloc((refrom->re_nsub+1) * sizeof(*matchfrom));
806 bertho 1.31
807 bertho 1.59 for(i = 0; i < rcs->tags->ntags; i++)
808     {
809     tag_t *t = rcs->tags->tags[i];
810 bertho 1.31
811 bertho 1.59 /* Must be revision tags and not detached */
812     if(t->rev->isbranch || !t->logrev)
813     continue;
814 bertho 1.31
815 bertho 1.59 /* Try to find merge tag matches */
816     if(!regexec(refrom, t->tag, refrom->re_nsub+1, matchfrom, 0))
817 bertho 1.31 {
818 bertho 1.59 int n;
819     char *to;
820    
821     to = build_regex(refrom->re_nsub+1, matchfrom, t->tag, j);
822     if(to)
823 bertho 1.31 {
824 bertho 1.59 err = regcomp(reto, to, rcflags);
825     if(err)
826     {
827     char *msg;
828     i = regerror(err, reto, NULL, 0);
829     msg = xmalloc(i+1);
830     regerror(err, reto, msg, i+1);
831     stack_msg(MSG_WARN, "%s", msg);
832     xfree(msg);
833     }
834     else if(!err)
835 bertho 1.31 {
836 bertho 1.59 for(n = 0; n < rcs->tags->ntags; n++)
837     {
838     tag_t *nt = rcs->tags->tags[n];
839     /* From and To never should match the same tag or belong to a branch */
840     if(n == i || nt->rev->isbranch || !nt->logrev)
841     continue;
842 bertho 1.31
843 bertho 1.59 if(!regexec(reto, nt->tag, 0, NULL, 0))
844 bertho 1.46 {
845 bertho 1.59 /* Tag matches */
846     rcs->merges = xrealloc(rcs->merges,
847     sizeof(rcs->merges[0]) * (rcs->nmerges+1));
848     rcs->merges[rcs->nmerges].type = TR_TAG;
849     rcs->merges[rcs->nmerges].to.tag = nt;
850     rcs->merges[rcs->nmerges].from.tag = t;
851     rcs->merges[rcs->nmerges].clr = j;
852     rcs->nmerges++;
853     if(!conf.tag_ignore_merge)
854     {
855     nt->ignore = 0;
856     t->ignore = 0;
857     }
858     /* We cannot (should not) match multiple times */
859     if(!conf.merge_findall)
860     break;
861 bertho 1.46 }
862 bertho 1.31 }
863 bertho 1.59 regfree(reto);
864 bertho 1.31 }
865 bertho 1.59 xfree(to);
866 bertho 1.31 }
867     }
868 bertho 1.30 }
869 bertho 1.59 if(matchfrom) xfree(matchfrom);
870     if(refrom) { regfree(refrom); xfree(refrom); }
871     if(reto) xfree(reto);
872     refrom = NULL;
873     reto = NULL;
874     matchfrom = NULL;
875 bertho 1.30 }
876 bertho 1.31 }
877    
878     static void assign_tags(rcsfile_t *rcs)
879     {
880     int i;
881     int nr;
882 bertho 1.40 regex_t *regextag = NULL;
883    
884     if(conf.tag_ignore && conf.tag_ignore[0])
885     {
886     int err;
887     regextag = xmalloc(sizeof(*regextag));
888     err = regcomp(regextag, conf.tag_ignore, REG_EXTENDED | REG_NOSUB | (conf.tag_nocase ? REG_ICASE : 0));
889     if(err)
890     {
891 bertho 1.44 char *msg;
892     i = regerror(err, regextag, NULL, 0);
893     msg = xmalloc(i+1);
894     regerror(err, regextag, msg, i+1);
895     stack_msg(MSG_WARN, "%s", msg);
896     xfree(msg);
897 bertho 1.40 xfree(regextag);
898     regextag = NULL;
899     }
900     }
901 bertho 1.7
902     for(i = nr = 0; i < rcs->nbranches; i++)
903     nr += rcs->branches[i]->nrevs;
904    
905     rcs->srev = xmalloc(nr * sizeof(rcs->srev[0]));
906     rcs->nsrev = nr;
907     for(i = nr = 0; i < rcs->nbranches; i++)
908     {
909     memcpy(&rcs->srev[nr], rcs->branches[i]->revs, rcs->branches[i]->nrevs * sizeof(rcs->branches[i]->revs[0]));
910     nr += rcs->branches[i]->nrevs;
911     }
912    
913     qsort(rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), sort_revision);
914     qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
915    
916     if(!rcs->branch)
917     {
918     /* The main trunk is the active trunk */
919     rcs->tags->tags = xrealloc(rcs->tags->tags, (rcs->tags->ntags+1)*sizeof(rcs->tags->tags[0]));
920     rcs->tags->tags[rcs->tags->ntags] = xmalloc(sizeof(tag_t));
921     rcs->tags->tags[rcs->tags->ntags]->tag = xstrdup("MAIN");
922     rcs->tags->tags[rcs->tags->ntags]->rev = xmalloc(sizeof(rev_t));
923     rcs->tags->tags[rcs->tags->ntags]->rev->rev = xstrdup(rcs->active->branch->rev);
924     rcs->tags->tags[rcs->tags->ntags]->rev->branch = xstrdup(rcs->active->branch->branch);
925     rcs->tags->tags[rcs->tags->ntags]->rev->isbranch = 1;
926     rcs->tags->ntags++;
927     }
928    
929     /* We should have at least two tags (HEAD and MAIN) */
930 bertho 1.44 assert(rcs->tags != NULL);
931 bertho 1.7
932     for(i = 0; i < rcs->tags->ntags; i++)
933     {
934     tag_t *t = rcs->tags->tags[i];
935     if(t->rev->isbranch)
936     {
937     branch_t **b;
938     add_btag:
939     b = bsearch(t->rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
940     if(!b)
941     {
942     rev_t rev;
943     revision_t **r;
944     /* This happens for the magic branch numbers if there are
945 bertho 1.12 * no commits within the new branch yet. So, we add the
946     * branch and try to continue.
947 bertho 1.7 */
948     rev.rev = previous_rev(t->rev->branch);
949     rev.branch = NULL;
950     rev.isbranch = 0;
951     r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
952     xfree(rev.rev);
953     if(!r)
954     {
955 bertho 1.44 stack_msg(MSG_WARN, "No branch found for tag '%s:%s'", t->tag, t->rev->branch);
956 bertho 1.7 }
957     else
958     {
959     rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1)*sizeof(rcs->branches[0]));
960     rcs->branches[rcs->nbranches] = xmalloc(sizeof(branch_t));
961     rcs->branches[rcs->nbranches]->branch = dup_rev(t->rev);
962     rcs->branches[rcs->nbranches]->branchpoint = *r;
963     (*r)->branches = xrealloc((*r)->branches, ((*r)->nbranches+1)*sizeof((*r)->branches[0]));
964     (*r)->branches[(*r)->nbranches] = rcs->branches[rcs->nbranches];
965     (*r)->nbranches++;
966     rcs->nbranches++;
967     /* Resort the branches */
968     qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
969     goto add_btag;
970     }
971     }
972     else
973     {
974     branch_t *bb = *b;
975     bb->tags = xrealloc(bb->tags, (bb->ntags+1)*sizeof(bb->tags[0]));
976     bb->tags[bb->ntags] = t;
977     bb->ntags++;
978     }
979     }
980     else
981     {
982     revision_t **r = bsearch(t->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
983     if(!r)
984 bertho 1.12 {
985 bertho 1.44 stack_msg(MSG_WARN, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);
986 bertho 1.12 }
987 bertho 1.7 else
988     {
989     revision_t *rr = *r;
990 bertho 1.30 t->logrev = rr;
991 bertho 1.28 if(!conf.rev_maxtags || rr->ntags <= conf.rev_maxtags)
992     {
993     rr->tags = xrealloc(rr->tags, (rr->ntags+1)*sizeof(rr->tags[0]));
994     if(conf.rev_maxtags && rr->ntags == conf.rev_maxtags)
995     {
996     rr->tags[rr->ntags] = xmalloc(sizeof(tag_t));
997     rr->tags[rr->ntags]->tag = xstrdup("...");
998     rr->tags[rr->ntags]->rev = t->rev;
999     }
1000     else
1001     rr->tags[rr->ntags] = t;
1002     rr->ntags++;
1003     }
1004 bertho 1.7 }
1005 bertho 1.40
1006 bertho 1.42 if(conf.tag_negate)
1007 bertho 1.43 t->ignore++;
1008 bertho 1.40 /* Mark the tag ignored if it matches the configuration */
1009     if(regextag && !regexec(regextag, t->tag, 0, NULL, 0))
1010 bertho 1.42 {
1011     if(conf.tag_negate)
1012     t->ignore--;
1013     else
1014     t->ignore++;
1015     }
1016 bertho 1.7 }
1017     }
1018    
1019     /* We need to reset the first in the list of branches to the
1020     * active branch to ensure the drawing of all
1021     */
1022     if(rcs->active != rcs->branches[0])
1023 bertho 1.1 {
1024 bertho 1.7 branch_t **b = bsearch(rcs->active->branch, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
1025     branch_t *t;
1026     assert(b != NULL);
1027     t = *b;
1028     *b = rcs->branches[0];
1029     rcs->branches[0] = t;
1030 bertho 1.1 }
1031 bertho 1.40
1032     if(regextag)
1033     {
1034     regfree(regextag);
1035     xfree(regextag);
1036     }
1037 bertho 1.1 }
1038    
1039     /*
1040     **************************************************************************
1041 bertho 1.8 * String expansion
1042     **************************************************************************
1043     */
1044     static char *_string;
1045     static int _nstring;
1046     static int _nastring;
1047    
1048 bertho 1.30 static void zap_string(void)
1049     {
1050     _nstring = 0;
1051     if(_string)
1052     _string[0] = '\0';
1053     }
1054    
1055     static char *dup_string(void)
1056     {
1057     if(_string)
1058     return xstrdup(_string);
1059     else
1060     return "";
1061     }
1062    
1063 bertho 1.19 static void add_string_str(const char *s)
1064 bertho 1.8 {
1065     int l = strlen(s) + 1;
1066     if(_nstring + l > _nastring)
1067     {
1068 bertho 1.24 _nastring += MAX(128, l);
1069 bertho 1.8 _string = xrealloc(_string, _nastring * sizeof(_string[0]));
1070     }
1071     memcpy(_string+_nstring, s, l);
1072     _nstring += l-1;
1073     }
1074    
1075 bertho 1.19 static void add_string_ch(int ch)
1076 bertho 1.8 {
1077     char buf[2];
1078     buf[0] = ch;
1079     buf[1] = '\0';
1080     add_string_str(buf);
1081     }
1082    
1083 bertho 1.19 static void add_string_date(const char *d)
1084 bertho 1.9 {
1085     struct tm tm, *tmp;
1086     int n;
1087     time_t t;
1088     char *buf;
1089     int nbuf;
1090 bertho 1.48 char *env;
1091 bertho 1.9
1092     memset(&tm, 0, sizeof(tm));
1093     n = sscanf(d, "%d.%d.%d.%d.%d.%d",
1094     &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
1095     &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
1096     tm.tm_mon--;
1097     if(tm.tm_year > 1900)
1098     tm.tm_year -= 1900;
1099 bertho 1.48
1100     env = getenv("TZ");
1101     putenv("TZ=UTC0");
1102     t = mktime(&tm);
1103     if(env)
1104 bertho 1.53 {
1105     char *c = xmalloc(strlen(env) + 3 + 1); /* Extra space for TZ and = */
1106     sprintf(c, "TZ=%s", env);
1107     putenv(c);
1108     xfree(c);
1109     }
1110 bertho 1.48 else
1111 bertho 1.53 putenv("TZ");
1112 bertho 1.48
1113 bertho 1.9 if(n != 6 || t == (time_t)(-1))
1114     {
1115     add_string_str("<invalid date>");
1116     return;
1117     }
1118    
1119     tmp = localtime(&t);
1120 bertho 1.51 nbuf = (strlen(conf.date_format)+1) * 16; /* Should be enough to hold all types of expansions */
1121 bertho 1.9 buf = xmalloc(nbuf);
1122     strftime(buf, nbuf, conf.date_format, tmp);
1123     add_string_str(buf);
1124     xfree(buf);
1125     }
1126    
1127 bertho 1.22 static void add_string_str_html(const char *s, int maxlen)
1128     {
1129     int l = 0;
1130     char *str = xmalloc(6 * strlen(s) + 1); /* Should hold all char entity-expand */
1131     char *cptr = str;
1132     for(; *s; s++)
1133     {
1134     if(maxlen && l > abs(maxlen))
1135     {
1136     cptr += sprintf(cptr, "...");
1137     break;
1138     }
1139     if(*s < 0x20)
1140     {
1141     if(*s == '\n')
1142     {
1143     if(maxlen < 0)
1144     *cptr++ = ' ';
1145     else
1146 bertho 1.37 cptr += sprintf(cptr, "<br%s>", conf.html_level == HTMLLEVEL_X ? " /" : "");
1147 bertho 1.22 }
1148     }
1149 bertho 1.30 else if(*s >= 0x7f || *s == '"')
1150 bertho 1.22 cptr += sprintf(cptr, "&#%d;", (int)(unsigned char)*s);
1151     else if(*s == '<')
1152     cptr += sprintf(cptr, "&lt;");
1153     else if(*s == '>')
1154     cptr += sprintf(cptr, "&gt;");
1155 bertho 1.37 else if(*s == '&')
1156     cptr += sprintf(cptr, "&amp;");
1157     else if(*s == '"')
1158     cptr += sprintf(cptr, "&quot;");
1159 bertho 1.22 else
1160     *cptr++ = *s;
1161     l++;
1162     }
1163     *cptr = '\0';
1164     add_string_str(str);
1165     xfree(str);
1166     }
1167    
1168     static void add_string_str_len(const char *s, int maxlen)
1169     {
1170     int l = strlen(s);
1171     char *str = xmalloc(l + 1 + 3);
1172     strcpy(str, s);
1173     if(maxlen < l)
1174     sprintf(&str[maxlen], "...");
1175     add_string_str(str);
1176     xfree(str);
1177     }
1178    
1179 bertho 1.44 static char *expand_string(const char *s, rcsfile_t *rcs, revision_t *r, rev_t *rev, rev_t *prev, tag_t *tag)
1180 bertho 1.8 {
1181     char nb[32];
1182     char nr[32];
1183     char *base;
1184 bertho 1.30 char *exp;
1185 bertho 1.22 int l;
1186     char ch;
1187 bertho 1.8
1188     if(!s)
1189     return xstrdup("");
1190    
1191 bertho 1.30 zap_string();
1192 bertho 1.8
1193 bertho 1.54 sprintf(nb, "%d", rcs->nbranches + rcs->nfolds);
1194 bertho 1.8 sprintf(nr, "%d", rcs->nsrev);
1195     for(; *s; s++)
1196     {
1197     if(*s == '%')
1198     {
1199     switch(*++s)
1200     {
1201 bertho 1.12 case 'c':
1202     case 'C':
1203     add_string_str(conf.cvsroot);
1204     if(*s == 'C' && conf.cvsroot[0] && conf.cvsroot[strlen(conf.cvsroot)-1] == '/')
1205     {
1206     /* Strip the trailing '/' */
1207     _nstring--;
1208     _string[_nstring] = '\0';
1209     }
1210     break;
1211 bertho 1.8 case 'f':
1212     case 'F':
1213     base = strrchr(rcs->file, '/');
1214     if(!base)
1215     add_string_str(rcs->file);
1216     else
1217     add_string_str(base+1);
1218     if(*s == 'F' && _string[_nstring-1] == 'v' && _string[_nstring-2] == ',')
1219     {
1220     _nstring -= 2;
1221     _string[_nstring] = '\0';
1222     }
1223     break;
1224     case 'p':
1225     base = strrchr(rcs->file, '/');
1226     if(base)
1227     {
1228     char *c = xstrdup(rcs->file);
1229     base = strrchr(c, '/');
1230     assert(base != NULL);
1231     base[1] = '\0';
1232     add_string_str(c);
1233     xfree(c);
1234     }
1235 bertho 1.9 /*
1236     * We should not add anything here because we can encounter
1237     * a completely empty path, in which case we do not want
1238 bertho 1.17 * to add any slash. This prevents an inadvertent root redirect.
1239 bertho 1.9 *
1240     * else
1241     * add_string_str("/");
1242     */
1243 bertho 1.8 break;
1244 bertho 1.12 case 'm':
1245     case 'M':
1246     add_string_str(conf.cvsmodule);
1247     if(*s == 'M' && conf.cvsmodule[0] && conf.cvsmodule[strlen(conf.cvsmodule)-1] == '/')
1248     {
1249     /* Strip the trailing '/' */
1250     _nstring--;
1251     _string[_nstring] = '\0';
1252     }
1253     break;
1254 bertho 1.8 case 'r': add_string_str(nr); break;
1255     case 'b': add_string_str(nb); break;
1256     case '%': add_string_ch('%'); break;
1257     case '0': if(conf.expand[0]) add_string_str(conf.expand[0]); break;
1258     case '1': if(conf.expand[1]) add_string_str(conf.expand[1]); break;
1259     case '2': if(conf.expand[2]) add_string_str(conf.expand[2]); break;
1260     case '3': if(conf.expand[3]) add_string_str(conf.expand[3]); break;
1261     case '4': if(conf.expand[4]) add_string_str(conf.expand[4]); break;
1262     case '5': if(conf.expand[5]) add_string_str(conf.expand[5]); break;
1263     case '6': if(conf.expand[6]) add_string_str(conf.expand[6]); break;
1264     case '7': if(conf.expand[7]) add_string_str(conf.expand[7]); break;
1265     case '8': if(conf.expand[8]) add_string_str(conf.expand[8]); break;
1266     case '9': if(conf.expand[9]) add_string_str(conf.expand[9]); break;
1267     case 'R': if(rev && rev->rev) add_string_str(rev->rev); break;
1268 bertho 1.9 case 'P': if(prev && prev->rev) add_string_str(prev->rev); break;
1269 bertho 1.8 case 'B': if(rev && rev->branch) add_string_str(rev->branch); break;
1270     case 't': if(tag && tag->tag) add_string_str(tag->tag); break;
1271 bertho 1.9 case 'd': if(r && r->delta && r->delta->date) add_string_date(r->delta->date); break;
1272     case 's': if(r && r->delta && r->delta->state) add_string_str(r->delta->state); break;
1273     case 'a': if(r && r->delta && r->delta->author) add_string_str(r->delta->author); break;
1274 bertho 1.22 case 'L':
1275     case 'l':
1276     ch = *s;
1277     l = 0;
1278     if(s[1] == '[')
1279     {
1280     char *cptr = strchr(s, ']');
1281     char *eptr;
1282     if(cptr)
1283     {
1284     l = strtol(&s[2], &eptr, 10);
1285     if(eptr != cptr)
1286     l = 0;
1287     else
1288     s = cptr;
1289     }
1290     }
1291     if(!conf.parse_logs)
1292     add_string_str("N/A");
1293     else if(r && r->dtext && r->dtext->log)
1294     {
1295     if(ch == 'l')
1296     add_string_str_html(r->dtext->log, l);
1297     else
1298     add_string_str_len(r->dtext->log, abs(l));
1299     }
1300     break;
1301 bertho 1.30 case '(':
1302     base = dup_string();
1303     exp = expand_string(s+1, rcs, r, rev, prev, tag);
1304     zap_string();
1305     add_string_str(base);
1306     add_string_str_html(exp, 0);
1307     xfree(base);
1308     xfree(exp);
1309     /* Find the %) in this recursion level */
1310     for(; *s; s++)
1311     {
1312     if(*s == '%' && s[1] == ')')
1313 bertho 1.34 {
1314     s++;
1315 bertho 1.30 break;
1316 bertho 1.34 }
1317 bertho 1.30 }
1318     if(!*s)
1319     {
1320     s--; /* To end outer loop */
1321 bertho 1.44 stack_msg(MSG_WARN, "string expand: Missing %%) in expansion");
1322 bertho 1.30 }
1323     break;
1324     case ')':
1325     return dup_string();
1326 bertho 1.8 default:
1327     add_string_ch('%');
1328     add_string_ch(*s);
1329     break;
1330     }
1331     }
1332     else
1333     add_string_ch(*s);
1334     }
1335 bertho 1.30 return dup_string();
1336 bertho 1.8 }
1337    
1338     /*
1339     **************************************************************************
1340 bertho 1.1 * Drawing routines
1341     **************************************************************************
1342     */
1343 bertho 1.56 static color_t *clr_id = NULL;
1344     static int nclr_id = 0;
1345    
1346 bertho 1.60 static int rexpr_eval(const char *key, const char *content, int flags)
1347     {
1348     int res;
1349     regex_t re;
1350     if(regcomp(&re, content, flags | REG_EXTENDED | REG_NOSUB))
1351     return 0;
1352     res = regexec(&re, key, 0, NULL, 0);
1353     regfree(&re);
1354     return res == 0;
1355     }
1356    
1357     static int expr_eval(const char *key, int op, const char *content)
1358     {
1359     switch(op)
1360     {
1361     case OP_CONTAINED: return rexpr_eval(key, content, 0);
1362     case OP_CONTAINEDI: return rexpr_eval(key, content, REG_ICASE);
1363     case OP_NCONTAINED: return !rexpr_eval(key, content, 0);
1364     case OP_NCONTAINEDI: return !rexpr_eval(key, content, REG_ICASE);
1365     case OP_EQ: return strcmp(key, content) == 0;
1366     case OP_NE: return strcmp(key, content) != 0;
1367     case OP_GE: return strcmp(key, content) >= 0;
1368     case OP_GT: return strcmp(key, content) > 0;
1369     case OP_LE: return strcmp(key, content) <= 0;
1370     case OP_LT: return strcmp(key, content) < 0;
1371     }
1372     return 0;
1373     }
1374    
1375     static char *eval_string(node_t *node, revision_t *r)
1376     {
1377     int i;
1378     assert(node != NULL);
1379     switch(node->key)
1380     {
1381     default:
1382     case TYPE_COLOR:
1383     return ""; /* This should not happen */
1384     case TYPE_STRING:
1385     return node->value.str;
1386     case KEY_STATE:
1387     if(r && expr_eval(r->delta->state, node->op, node->content))
1388     return eval_string(node->tcase, r);
1389     else
1390     return eval_string(node->fcase, r);
1391     case KEY_AUTHOR:
1392     if(r && expr_eval(r->delta->author, node->op, node->content))
1393     return eval_string(node->tcase, r);
1394     else
1395     return eval_string(node->fcase, r);
1396     case KEY_TAG:
1397     for(i = 0; r && i < r->ntags; i++)
1398     {
1399     if(expr_eval(r->tags[i]->tag, node->op, node->content))
1400     return eval_string(node->tcase, r);
1401     }
1402     return eval_string(node->fcase, r);
1403     case KEY_DATE:
1404     if(r && expr_eval(r->delta->date, node->op, node->content))
1405     return eval_string(node->tcase, r);
1406     else
1407     return eval_string(node->fcase, r);
1408     case KEY_REV:
1409     if(r && expr_eval(r->rev->rev, node->op, node->content))
1410     return eval_string(node->tcase, r);
1411     else
1412     return eval_string(node->fcase, r);
1413     }
1414     return "";
1415     }
1416     static color_t *eval_color(node_t *node, revision_t *r, branch_t *b)
1417     {
1418     int i;
1419     assert(node != NULL);
1420     switch(node->key)
1421     {
1422     default:
1423     case TYPE_STRING:
1424     return &black_color; /* This should not happen */
1425     case TYPE_COLOR:
1426     return &node->value.clr;
1427     case KEY_STATE:
1428     if(r && expr_eval(r->delta->state, node->op, node->content))
1429     return eval_color(node->tcase, r, b);
1430     else
1431     return eval_color(node->fcase, r, b);
1432     case KEY_AUTHOR:
1433     if(r && expr_eval(r->delta->author, node->op, node->content))
1434     return eval_color(node->tcase, r, b);
1435     else
1436     return eval_color(node->fcase, r, b);
1437     case KEY_TAG:
1438     for(i = 0; r && i < r->ntags; i++)
1439     {
1440     if(expr_eval(r->tags[i]->tag, node->op, node->content))
1441     return eval_color(node->tcase, r, b);
1442     }
1443     return eval_color(node->fcase, r, b);
1444     case KEY_DATE:
1445     if(r && expr_eval(r->delta->date, node->op, node->content))
1446     return eval_color(node->tcase, r, b);
1447     else
1448     return eval_color(node->fcase, r, b);
1449     case KEY_REV:
1450     if(r && expr_eval(r->rev->rev, node->op, node->content))
1451     return eval_color(node->tcase, r, b);
1452     if(b && expr_eval(b->branch->branch, node->op, node->content))
1453     return eval_color(node->tcase, r, b);
1454     return eval_color(node->fcase, r, b);
1455     }
1456     return &black_color;
1457     }
1458    
1459 bertho 1.59 static color_t *clr(gdImagePtr im, const char *s, revision_t *r, branch_t *b, int idx)
1460 bertho 1.56 {
1461     int i;
1462 bertho 1.59 color_t *c = get_colorref(s, idx);
1463 bertho 1.56 if(!c)
1464     c = &black_color;
1465 bertho 1.60 if(c->node)
1466     c = eval_color(c->node, r, b);
1467 bertho 1.56 for(i = 0; i < nclr_id; i++)
1468     {
1469     if(c->r == clr_id[i].r && c->g == clr_id[i].g && c->b == clr_id[i].b)
1470     return &clr_id[i];
1471     }
1472     clr_id = xrealloc(clr_id, (nclr_id+1) * sizeof(*clr_id));
1473     clr_id[nclr_id] = *c;
1474     clr_id[nclr_id].id = gdImageColorAllocate(im, c->r, c->g, c->b);
1475     return &clr_id[nclr_id++];
1476     }
1477    
1478 bertho 1.58 static void zap_clr(void)
1479     {
1480     if(clr_id)
1481     xfree(clr_id);
1482     clr_id = NULL;
1483     nclr_id = 0;
1484     }
1485    
1486 bertho 1.19 static int get_swidth(const char *s, font_t *f)
1487 bertho 1.1 {
1488 bertho 1.9 int n;
1489     int m;
1490     if(!s || !*s)
1491 bertho 1.1 return 0;
1492 bertho 1.19
1493     #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1494     if(conf.use_ttf && f->ttfont)
1495     {
1496     int bb[8];
1497     char *e;
1498     #ifdef HAVE_GDIMAGESTRINGFT
1499     e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1500     #else
1501     e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1502     #endif
1503     if(!e)
1504     return bb[2] - bb[6];
1505     }
1506     #endif
1507 bertho 1.9 for(n = m = 0; *s; n++, s++)
1508     {
1509     if(*s == '\n')
1510     {
1511     if(n > m)
1512     m = n;
1513     n = 0;
1514     }
1515     }
1516     if(n > m)
1517     m = n;
1518 bertho 1.22 return f->gdfont ? m * f->gdfont->w : m;
1519 bertho 1.1 }
1520    
1521 bertho 1.19 static int get_sheight(const char *s, font_t *f)
1522 bertho 1.1 {
1523     int nl;
1524 bertho 1.9 if(!s || !*s)
1525 bertho 1.1 return 0;
1526 bertho 1.19
1527     #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1528     if(conf.use_ttf && f->ttfont)
1529     {
1530     int bb[8];
1531     char *e;
1532     #ifdef HAVE_GDIMAGESTRINGFT
1533     e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1534     #else
1535     e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1536     #endif
1537     if(!e)
1538     return bb[3] - bb[7] + 4;
1539     }
1540     #endif
1541 bertho 1.1 for(nl = 1; *s; s++)
1542     {
1543     if(*s == '\n' && s[1])
1544     nl++;
1545     }
1546 bertho 1.19 return nl * f->gdfont->h;
1547 bertho 1.1 }
1548    
1549 bertho 1.19 static void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color, color_t *bgcolor)
1550 bertho 1.1 {
1551     int r2 = 2*r;
1552 bertho 1.44 if(!r)
1553     gdImageFilledRectangle(im, x1, y1, x2, y2, bgcolor->id);
1554     #ifdef HAVE_GDIMAGEFILLEDARC
1555     else
1556     {
1557     gdImageFilledArc(im, x1+r, y1+r, r2, r2, 180, 270, bgcolor->id, gdArc);
1558     gdImageFilledArc(im, x2-r, y1+r, r2, r2, 270, 360, bgcolor->id, gdArc);
1559     gdImageFilledArc(im, x1+r, y2-r, r2, r2, 90, 180, bgcolor->id, gdArc);
1560     gdImageFilledArc(im, x2-r, y2-r, r2, r2, 0, 90, bgcolor->id, gdArc);
1561     gdImageFilledRectangle(im, x1+r, y1, x2-r, y1+r, bgcolor->id);
1562     gdImageFilledRectangle(im, x1, y1+r, x2, y2-r, bgcolor->id);
1563     gdImageFilledRectangle(im, x1+r, y2-r, x2-r, y2, bgcolor->id);
1564     }
1565     #endif
1566 bertho 1.1 gdImageLine(im, x1+r, y1, x2-r, y1, color->id);
1567     gdImageLine(im, x1+r, y2, x2-r, y2, color->id);
1568     gdImageLine(im, x1, y1+r, x1, y2-r, color->id);
1569     gdImageLine(im, x2, y1+r, x2, y2-r, color->id);
1570 bertho 1.9 if(conf.box_shadow)
1571     {
1572 bertho 1.60 gdImageLine(im, x1+r+1, y2+1, x2-r, y2+1, clr(im, NULL, NULL, NULL, 0)->id);
1573     gdImageLine(im, x2+1, y1+r+1, x2+1, y2-r, clr(im, NULL, NULL, NULL, 0)->id);
1574 bertho 1.9 }
1575 bertho 1.1 if(r)
1576     {
1577 bertho 1.18 /* FIXME: Pixelization is not perfect */
1578 bertho 1.1 gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);
1579     gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);
1580     gdImageArc(im, x1+r, y2-r, r2, r2, 90, 180, color->id);
1581 bertho 1.9 if(conf.box_shadow)
1582     {
1583 bertho 1.60 gdImageArc(im, x2-r+1, y2-r+1, r2, r2, 0, 90, clr(im, NULL, NULL, NULL, 0)->id);
1584     gdImageArc(im, x2-r+1, y2-r, r2, r2, 0, 90, clr(im, NULL, NULL, NULL, 0)->id);
1585     gdImageArc(im, x2-r, y2-r+1, r2, r2, 0, 90, clr(im, NULL, NULL, NULL, 0)->id);
1586 bertho 1.9 }
1587 bertho 1.18 gdImageArc(im, x2-r, y2-r, r2, r2, 0, 90, color->id);
1588 bertho 1.44 #if !defined(NOGDFILL) && !defined(HAVE_GDIMAGEFILLEDARC)
1589     /* BUG: We clip manually because libgd segfaults on out of bound values */
1590     if((x1+x2)/2 >= 0 && (x1+x2)/2 < gdImageSX(im) && (y1+y2)/2 >= 0 && (y1+y2)/2 < gdImageSY(im))
1591     gdImageFillToBorder(im, (x1+x2)/2, (y1+y2)/2, color->id, bgcolor->id);
1592     #endif
1593 bertho 1.1 }
1594     }
1595    
1596 bertho 1.19 static void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
1597 bertho 1.1 {
1598 bertho 1.19 int h = get_sheight(s, f);
1599 bertho 1.1 int xx, yy;
1600     switch(align & ALIGN_HX)
1601     {
1602     default:
1603     case ALIGN_HL: xx = 0; break;
1604     case ALIGN_HC: xx = -get_swidth(s, f)/2; break;
1605     case ALIGN_HR: xx = -get_swidth(s, f); break;
1606     }
1607     switch(align & ALIGN_VX)
1608     {
1609     default:
1610     case ALIGN_VT: yy = 0; break;
1611 bertho 1.19 case ALIGN_VC: yy = h/2; break;
1612     case ALIGN_VB: yy = h; break;
1613 bertho 1.1 }
1614 bertho 1.19 #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1615     if(conf.use_ttf && f->ttfont)
1616     {
1617     int bb[8];
1618     char *e;
1619     int cid = conf.anti_alias ? c->id : -c->id;
1620     #ifdef HAVE_GDIMAGESTRINGFT
1621     e = gdImageStringFT(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1622     #else
1623     e = gdImageStringTTF(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1624     #endif
1625     if(!e)
1626     return;
1627     }
1628     #endif
1629     yy = -yy;
1630 bertho 1.59 gdImageString(im, f->gdfont, x+xx+1, y+yy, (unsigned char *)s, c->id);
1631 bertho 1.1 }
1632    
1633 bertho 1.19 static void draw_stringnl(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
1634 bertho 1.9 {
1635     char *t;
1636     char *d;
1637     d = s = xstrdup(s);
1638     do
1639     {
1640     t = strchr(s, '\n');
1641     if(t)
1642     *t = '\0';
1643     draw_string(im, s, f, x, y, align, c);
1644     y += get_sheight(s, f);
1645     s = t+1;
1646     } while(t);
1647     xfree(d);
1648     }
1649    
1650 bertho 1.19 static void draw_rev(gdImagePtr im, revision_t *r)
1651 bertho 1.1 {
1652 bertho 1.25 int lx;
1653     int rx;
1654     int x2;
1655 bertho 1.1 int i;
1656 bertho 1.25 int ty;
1657    
1658     if(conf.left_right)
1659     {
1660     lx = r->cx;
1661     rx = r->cx + r->w;
1662     ty = r->y - r->h/2;
1663     x2 = r->cx + r->w/2;
1664     }
1665     else
1666     {
1667     lx = r->cx - r->w/2;
1668     rx = lx + r->w;
1669     ty = r->y;
1670     x2 = r->cx;
1671     }
1672 bertho 1.59 draw_rbox(im, lx, ty, rx, ty+r->h, 0, clr(im, "rev_color", r, NULL, 0), clr(im, "rev_bgcolor", r, NULL, 0));
1673 bertho 1.1 ty += conf.rev_tspace;
1674 bertho 1.46 if(!conf.rev_hidenumber)
1675     {
1676 bertho 1.60 draw_string(im, r->revidtext, &conf.rev_font, x2, ty, ALIGN_HC, clr(im, "rev_color", r, NULL, 0));
1677     ty += get_sheight(r->revidtext, &conf.rev_font);
1678 bertho 1.46 }
1679 bertho 1.59 draw_stringnl(im, r->revtext, &conf.rev_text_font, x2, ty, ALIGN_HC, clr(im, "rev_text_color", r, NULL, 0));
1680 bertho 1.9 ty += get_sheight(r->revtext, &conf.rev_text_font);
1681 bertho 1.1 for(i = 0; i < r->ntags; i++)
1682     {
1683 bertho 1.59 draw_string(im, r->tags[i]->tag, &conf.tag_font, x2, ty, ALIGN_HC, clr(im, "tag_color", r, NULL, 0));
1684 bertho 1.19 ty += get_sheight(r->tags[i]->tag, &conf.tag_font) + conf.rev_separator;
1685 bertho 1.1 }
1686     }
1687    
1688 bertho 1.26 static void draw_branch_box(gdImagePtr im, branch_t *b, int xp, int yp)
1689 bertho 1.1 {
1690 bertho 1.25 int lx;
1691     int rx;
1692 bertho 1.16 int i;
1693 bertho 1.1 int yy;
1694 bertho 1.25 int x2;
1695 bertho 1.16
1696 bertho 1.25 if(conf.left_right)
1697     {
1698     lx = b->cx;
1699     rx = lx + b->w;
1700     x2 = b->cx + b->w/2;
1701     }
1702     else
1703     {
1704     lx = b->cx - b->w/2;
1705     rx = lx + b->w;
1706     x2 = b->cx;
1707     }
1708 bertho 1.59 draw_rbox(im, lx+xp, yp, rx+xp, yp+b->h, 5, clr(im, "branch_color", NULL, b, 0), clr(im, "branch_bgcolor", NULL, b, 0));
1709 bertho 1.1 yy = conf.branch_tspace;
1710 bertho 1.32 if(!b->nfolds)
1711 bertho 1.1 {
1712 bertho 1.46 if(!conf.rev_hidenumber)
1713     {
1714 bertho 1.59 draw_string(im, b->branch->branch, &conf.branch_font, x2+xp, yp+yy, ALIGN_HC, clr(im, "branch_color", NULL, b, 0));
1715 bertho 1.46 yy += get_sheight(b->branch->branch, &conf.branch_font);
1716     }
1717 bertho 1.32 for(i = 0; i < b->ntags; i++)
1718     {
1719 bertho 1.59 draw_string(im, b->tags[i]->tag, &conf.branch_tag_font, x2+xp, yp+yy, ALIGN_HC, clr(im, "branch_tag_color", NULL, b, 0));
1720 bertho 1.34 yy += get_sheight(b->tags[i]->tag, &conf.branch_tag_font);
1721 bertho 1.32 }
1722     }
1723     else
1724     {
1725     int y1, y2;
1726     int tx = lx + b->fw + conf.branch_lspace;
1727     int nx = tx - get_swidth(" ", &conf.branch_font);
1728 bertho 1.59 draw_string(im, b->branch->branch, &conf.branch_font, nx+xp, yp+yy, ALIGN_HR, clr(im, "branch_color", NULL, b, 0));
1729 bertho 1.32 y1 = get_sheight(b->branch->branch, &conf.branch_font);
1730 bertho 1.59 draw_string(im, b->tags[0]->tag, &conf.branch_tag_font, tx+xp, yp+yy, ALIGN_HL, clr(im, "branch_tag_color", NULL, b, 0));
1731 bertho 1.32 y2 = get_sheight(b->tags[0]->tag, &conf.branch_font);
1732     yy += MAX(y1, y2);
1733     for(i = 0; i < b->nfolds; i++)
1734     {
1735 bertho 1.59 draw_string(im, b->folds[i]->branch->branch, &conf.branch_font, nx+xp, yp+yy, ALIGN_HR, clr(im, "branch_color", NULL, b, 0));
1736 bertho 1.32 y1 = get_sheight(b->folds[i]->branch->branch, &conf.branch_font);
1737 bertho 1.59 draw_string(im, b->folds[i]->tags[0]->tag, &conf.branch_tag_font, tx+xp, yp+yy, ALIGN_HL, clr(im, "branch_tag_color", NULL, b, 0));
1738 bertho 1.34 y2 = get_sheight(b->folds[i]->tags[0]->tag, &conf.branch_tag_font);
1739 bertho 1.32 yy += MAX(y1, y2);
1740     }
1741 bertho 1.1 }
1742 bertho 1.16 }
1743    
1744 bertho 1.19 static void draw_branch(gdImagePtr im, branch_t *b)
1745 bertho 1.16 {
1746 bertho 1.25 int yy, xx;
1747 bertho 1.16 int i;
1748 bertho 1.17 int line[4];
1749 bertho 1.20 int l;
1750     int sign;
1751 bertho 1.17
1752 bertho 1.56 line[1] = line[2] = gdTransparent;
1753 bertho 1.17
1754 bertho 1.44 /* Trivial clip the branch */
1755     if(conf.left_right)
1756     {
1757     if(b->cx > gdImageSX(im) || b->cx+b->tw < 0 || b->y-b->th/2 > gdImageSY(im) || b->y+b->th/2 < 0)
1758     return;
1759     }
1760     else
1761     {
1762     if(b->cx-b->tw/2 > gdImageSX(im) || b->cx+b->tw/2 < 0 || b->y > gdImageSY(im) || b->y+b->th < 0)
1763     return;
1764     }
1765    
1766 bertho 1.26 draw_branch_box(im, b, 0, conf.left_right ? b->y - b->h/2 : b->y);
1767 bertho 1.1
1768 bertho 1.25 if(conf.left_right)
1769 bertho 1.16 {
1770 bertho 1.25 if(conf.upside_down)
1771 bertho 1.16 {
1772 bertho 1.25 xx = b->cx;
1773     for(i = 0; i < b->nrevs; i++)
1774 bertho 1.20 {
1775 bertho 1.25 revision_t *r = b->revs[i];
1776 bertho 1.59 line[0] = line[3] = clr(im, "rev_color", r, b, 0)->id;
1777 bertho 1.55 gdImageSetStyle(im, line, r->stripped > 0 ? 4 : 1);
1778 bertho 1.25 gdImageLine(im, xx, r->y, r->cx+r->w, r->y, gdStyled);
1779     for(sign = l = 1; l < conf.thick_lines; l++)
1780     {
1781     int pp = (l+1)/2*sign;
1782     gdImageLine(im, xx, r->y+pp, r->cx+r->w, r->y+pp, gdStyled);
1783     sign *= -1;
1784     }
1785     draw_rev(im, r);
1786     xx = r->cx;
1787     }
1788 bertho 1.32 if(conf.branch_dupbox && b->nrevs)
1789 bertho 1.25 {
1790     i = b->cx - b->tw + b->w;
1791 bertho 1.59 gdImageLine(im, xx, b->y, i+b->w, b->y, clr(im, "rev_color", NULL, b, 0)->id);
1792 bertho 1.25 for(sign = l = 1; l < conf.thick_lines; l++)
1793     {
1794     int pp = (l+1)/2*sign;
1795 bertho 1.59 gdImageLine(im, xx, b->y+pp, i+b->w, b->y+pp, clr(im, "rev_color", NULL, b, 0)->id);
1796 bertho 1.25 sign *= -1;
1797     }
1798 bertho 1.26 draw_branch_box(im, b, i - b->cx, b->y - b->h/2);
1799 bertho 1.20 }
1800 bertho 1.16 }
1801 bertho 1.25 else
1802 bertho 1.16 {
1803 bertho 1.25 xx = b->cx + b->w;
1804     for(i = 0; i < b->nrevs; i++)
1805     {
1806     revision_t *r = b->revs[i];
1807 bertho 1.59 line[0] = line[3] = clr(im, "rev_color", r, b, 0)->id;
1808 bertho 1.55 gdImageSetStyle(im, line, r->stripped > 0 ? 4 : 1);
1809 bertho 1.25 gdImageLine(im, xx, r->y, r->cx, r->y, gdStyled);
1810     for(sign = l = 1; l < conf.thick_lines; l++)
1811     {
1812     int pp = (l+1)/2*sign;
1813     gdImageLine(im, xx, r->y+pp, r->cx, r->y+pp, gdStyled);
1814     sign *= -1;
1815     }
1816     draw_rev(im, r);
1817     xx = r->cx + r->w;
1818     }
1819 bertho 1.32 if(conf.branch_dupbox && b->nrevs)
1820 bertho 1.20 {
1821 bertho 1.25 i = b->cx + b->tw - b->w;
1822 bertho 1.59 gdImageLine(im, xx, b->y, i, b->y, clr(im, "rev_color", NULL, b, 0)->id);
1823 bertho 1.25 for(sign = l = 1; l < conf.thick_lines; l++)
1824     {
1825     int pp = (l+1)/2*sign;
1826 bertho 1.59 gdImageLine(im, xx, b->y+pp, i, b->y+pp, clr(im, "rev_color", NULL, b, 0)->id);
1827 bertho 1.25 sign *= -1;
1828     }
1829 bertho 1.26 draw_branch_box(im, b, i - b->cx, b->y - b->h/2);
1830 bertho 1.20 }
1831 bertho 1.16 }
1832     }
1833     else
1834 bertho 1.1 {
1835 bertho 1.25 if(conf.upside_down)
1836 bertho 1.16 {
1837 bertho 1.25 yy = b->y;
1838     for(i = 0; i < b->nrevs; i++)
1839 bertho 1.20 {
1840 bertho 1.25 revision_t *r = b->revs[i];
1841 bertho 1.59 line[0] = line[3] = clr(im, "rev_color", r, b, 0)->id;
1842 bertho 1.55 gdImageSetStyle(im, line, r->stripped > 0 ? 4 : 1);
1843 bertho 1.25 gdImageLine(im, r->cx, yy, r->cx, r->y+r->h, gdStyled);
1844     for(sign = l = 1; l < conf.thick_lines; l++)
1845     {
1846     int pp = (l+1)/2*sign;
1847     gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y+r->h, gdStyled);
1848     sign *= -1;
1849     }
1850     draw_rev(im, r);
1851     yy = r->y;
1852     }
1853 bertho 1.32 if(conf.branch_dupbox && b->nrevs)
1854 bertho 1.25 {
1855     i = b->y - b->th + b->h;
1856 bertho 1.59 gdImageLine(im, b->cx, yy, b->cx, i, clr(im, "rev_color", NULL, b, 0)->id);
1857 bertho 1.25 for(sign = l = 1; l < conf.thick_lines; l++)
1858     {
1859     int pp = (l+1)/2*sign;
1860 bertho 1.59 gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, clr(im, "rev_color", NULL, b, 0)->id);
1861 bertho 1.25 sign *= -1;
1862     }
1863 bertho 1.26 draw_branch_box(im, b, 0, i);
1864 bertho 1.20 }
1865 bertho 1.16 }
1866 bertho 1.25 else
1867 bertho 1.16 {
1868 bertho 1.25 yy = b->y + b->h;
1869     for(i = 0; i < b->nrevs; i++)
1870     {
1871     revision_t *r = b->revs[i];
1872 bertho 1.59 line[0] = line[3] = clr(im, "rev_color", r, b, 0)->id;
1873 bertho 1.55 gdImageSetStyle(im, line, r->stripped > 0 ? 4 : 1);
1874 bertho 1.25 gdImageLine(im, r->cx, yy, r->cx, r->y, gdStyled);
1875     for(sign = l = 1; l < conf.thick_lines; l++)
1876     {
1877     int pp = (l+1)/2*sign;
1878     gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y, gdStyled);
1879     sign *= -1;
1880     }
1881     draw_rev(im, r);
1882     yy = r->y + r->h;
1883     }
1884 bertho 1.32 if(conf.branch_dupbox && b->nrevs)
1885 bertho 1.20 {
1886 bertho 1.25 i = b->y + b->th - b->h;
1887 bertho 1.59 gdImageLine(im, b->cx, yy, b->cx, i, clr(im, "rev_color", NULL, b, 0)->id);
1888 bertho 1.25 for(sign = l = 1; l < conf.thick_lines; l++)
1889     {
1890     int pp = (l+1)/2*sign;
1891 bertho 1.59 gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, clr(im, "rev_color", NULL, b, 0)->id);
1892 bertho 1.25 sign *= -1;
1893     }
1894 bertho 1.26 draw_branch_box(im, b, 0, i);
1895 bertho 1.20 }
1896 bertho 1.16 }
1897 bertho 1.1 }
1898     }
1899    
1900 bertho 1.19 static void draw_connector(gdImagePtr im, branch_t *b)
1901 bertho 1.1 {
1902 bertho 1.20 int l;
1903     int sign;
1904 bertho 1.1 revision_t *r = b->branchpoint;
1905 bertho 1.7 int x1 = r->cx + r->w/2 + 2;
1906 bertho 1.1 int y1 = r->y + r->h/2;
1907 bertho 1.7 int x2 = b->cx;
1908 bertho 1.1 int y2 = b->y;
1909 bertho 1.25
1910     if(conf.left_right)
1911     {
1912     x2 = r->cx + r->w/2;
1913 bertho 1.26 y2 = r->y + r->h/2 + 3;
1914 bertho 1.25 x1 = b->cx;
1915     y1 = b->y;
1916     if(conf.upside_down)
1917     x1 += b->w;
1918     }
1919     else
1920     {
1921     x1 = r->cx + r->w/2 + 2;
1922     y1 = r->y + r->h/2;
1923     x2 = b->cx;
1924     y2 = b->y;
1925     if(conf.upside_down)
1926     y2 += b->h;
1927     }
1928 bertho 1.59 gdImageLine(im, x1, y1, x2, y1, clr(im, "branch_color", NULL, b, 0)->id);
1929     gdImageLine(im, x2, y1, x2, y2, clr(im, "branch_color", NULL, b, 0)->id);
1930 bertho 1.20 for(sign = l = 1; l < conf.thick_lines; l++)
1931     {
1932     int pp = (l+1)/2*sign;
1933 bertho 1.59 gdImageLine(im, x1, y1+pp, x2, y1+pp, clr(im, "branch_color", NULL, b, 0)->id);
1934     gdImageLine(im, x2+pp, y1, x2+pp, y2, clr(im, "branch_color", NULL, b, 0)->id);
1935 bertho 1.20 sign *= -1;
1936     }
1937 bertho 1.1 }
1938    
1939 bertho 1.64 static void calc_merge_coords(merge_t *mt, revision_t *fr, revision_t *tr, int *x1, int *x2, int *y1, int *y2, int *sx1, int *sx2, int *sy1, int *sy2)
1940 bertho 1.30 {
1941 bertho 1.64 int shadow = conf.box_shadow ? 1 : 0;
1942     assert(mt != NULL);
1943     assert(fr != NULL);
1944     assert(tr != NULL);
1945     assert(x1 != NULL);
1946     assert(x2 != NULL);
1947     assert(y1 != NULL);
1948     assert(y2 != NULL);
1949     assert(sx1 != NULL);
1950     assert(sx2 != NULL);
1951     assert(sy1 != NULL);
1952     assert(sy2 != NULL);
1953     if(conf.left_right && !conf.merge_on_tag)
1954     {
1955     if(fr->branch == tr->branch)
1956     {
1957     *y1 = fr->y - (fr->h+1)/2;
1958     *y2 = tr->y - (tr->h+1)/2;
1959     *sy1 = *sy2 = -1;
1960 bertho 1.49 }
1961 bertho 1.64 else
1962 bertho 1.30 {
1963 bertho 1.64 /* See comment below on shortest path */
1964     int y1a = fr->y + (fr->h+1)/2 + shadow;
1965     int y1b = fr->y - (fr->h+1)/2;
1966     int y2a = tr->y + (tr->h+1)/2 + shadow;
1967     int y2b = tr->y - (tr->h+1)/2;
1968     int laa = abs(y2a - y1a);
1969     int lba = abs(y2a - y1b);
1970     int lab = abs(y2b - y1a);
1971     int lbb = abs(y2b - y1b);
1972     if(laa < lab)
1973 bertho 1.30 {
1974 bertho 1.64 if(laa < lba)
1975     {
1976     if(laa < lbb)
1977     {
1978     *y1 = y1a;
1979     *y2 = y2a;
1980     *sy1 = *sy2 = 1;
1981     }
1982     else
1983     {
1984     ybb:
1985     *y1 = y1b;
1986     *y2 = y2b;
1987     *sy1 = *sy2 = -1;
1988     }
1989     }
1990     else
1991     {
1992     yba:
1993     if(lba < lbb)
1994     {
1995     *y1 = y1b;
1996     *y2 = y2a;
1997     *sy1 = -1;
1998     *sy2 = 1;
1999     }
2000     else
2001     goto ybb;
2002     }
2003 bertho 1.30 }
2004     else
2005     {
2006 bertho 1.64 if(lab < lba)
2007 bertho 1.61 {
2008 bertho 1.64 if(lab < lbb)
2009     {
2010     *y1 = y1a;
2011     *y2 = y2b;
2012     *sy1 = 1;
2013     *sy2 = -1;
2014     }
2015     else
2016     goto ybb;
2017 bertho 1.32 }
2018     else
2019 bertho 1.64 goto yba;
2020 bertho 1.30 }
2021 bertho 1.64 }
2022     *x1 = fr->cx + fr->w/2;
2023     *x2 = tr->cx + tr->w/2;
2024     *sx1 = *sx2 = 1;
2025     }
2026     else
2027     {
2028     if(fr->branch == tr->branch)
2029     {
2030     /* Line on same branch always on left side */
2031     *x1 = fr->cx - fr->w/2;
2032     *x2 = tr->cx - tr->w/2;
2033     *sx1 = *sx2 = -1;
2034 bertho 1.30 }
2035     else
2036     {
2037 bertho 1.64 /* Find the shortest route from the two revisions
2038     * to determine which sides of the revision box
2039     * should be used. The basics are:
2040     * l = (x2 -x1)^2 + (y2 - y1)^2
2041     * However, (y2 -y1) is constant and hence the
2042     * determination of the shortest path is already
2043     * clear from |x2 -x1| for each permutation of left
2044     * and right x.
2045     * This strategy is still not perfect because it can
2046     * happen that a source/destination is overlayed by
2047     * the revision box. To prevent this, we need to do
2048     * a very deep analysis and I'm not prepared to do
2049     * that right now...
2050     */
2051     int x1a = fr->cx + (fr->w+1)/2 + shadow;
2052     int x1b = fr->cx - (fr->w+1)/2;
2053     int x2a = tr->cx + (tr->w+1)/2 + shadow;
2054     int x2b = tr->cx - (tr->w+1)/2;
2055     int laa = abs(x2a - x1a);
2056     int lba = abs(x2a - x1b);
2057     int lab = abs(x2b - x1a);
2058     int lbb = abs(x2b - x1b);
2059     if(laa < lab)
2060 bertho 1.30 {
2061 bertho 1.64 if(laa < lba)
2062 bertho 1.61 {
2063 bertho 1.64 if(laa < lbb)
2064     {
2065     *x1 = x1a;
2066     *x2 = x2a;
2067     *sx1 = *sx2 = 1;
2068     }
2069     else
2070     {
2071     xbb:
2072     *x1 = x1b;
2073     *x2 = x2b;
2074     *sx1 = *sx2 = -1;
2075     }
2076 bertho 1.32 }
2077     else
2078     {
2079 bertho 1.64 xba:
2080     if(lba < lbb)
2081     {
2082     *x1 = x1b;
2083     *x2 = x2a;
2084     *sx1 = -1;
2085     *sx2 = 1;
2086     }
2087     else
2088     goto xbb;
2089 bertho 1.32 }
2090 bertho 1.30 }
2091 bertho 1.64 else
2092 bertho 1.49 {
2093 bertho 1.64 if(lab < lba)
2094     {
2095     if(lab < lbb)
2096     {
2097     *x1 = x1a;
2098     *x2 = x2b;
2099     *sx1 = 1;
2100     *sx2 = -1;
2101     }
2102     else
2103     goto xbb;
2104     }
2105     else
2106     goto xba;
2107 bertho 1.49 }
2108 bertho 1.64 }
2109     if(mt->type == TR_TAG)
2110     {
2111     *y1 = fr->y + mt->from.tag->yofs;
2112     *y2 = tr->y + mt->to.tag->yofs;
2113     if(conf.left_right && conf.merge_on_tag)
2114 bertho 1.49 {
2115 bertho 1.64 *y1 -= fr->h/2;
2116     *y2 -= tr->h/2;
2117 bertho 1.49 }
2118 bertho 1.30 }
2119 bertho 1.64 else
2120     {
2121     *y1 = fr->y + fr->h/2;
2122     *y2 = tr->y + tr->h/2;
2123     }
2124     *sy1 = *sy2 = 1;
2125     if(conf.left_right && conf.merge_on_tag)
2126     {
2127     *x1 += fr->w/2;
2128     *x2 += tr->w/2;
2129     }
2130     }
2131     }
2132    
2133     static void draw_merges(gdImagePtr im, rcsfile_t *rcs, int dot)
2134     {
2135     int i;
2136     for(i = 0; i < rcs->nmerges; i++)
2137     {
2138     revision_t *fr;
2139     revision_t *tr;
2140     int colorid;
2141     int x1, x2, y1, y2; /* Edge position on revision box */
2142     int sx1, sx2, sy1, sy2; /* Direction for mergeline -1 = left/up, +1 = right/down */
2143     switch(rcs->merges[i].type)
2144     {
2145     case TR_TAG:
2146     fr = rcs->merges[i].from.tag->logrev;
2147     tr = rcs->merges[i].to.tag->logrev;
2148     colorid = clr(im, "merge_color", NULL, NULL, rcs->merges[i].clr)->id;
2149     break;
2150     case TR_REVISION:
2151     fr = rcs->merges[i].from.rev;
2152     tr = rcs->merges[i].to.rev;
2153     colorid = clr(im, "merge_cvsnt_color", NULL, NULL, 0)->id;
2154     break;
2155     default:
2156     continue;
2157     }
2158     if(!fr || !tr || fr == tr)
2159     continue; /* This can happen with detached tags and self-references */
2160    
2161     calc_merge_coords(&rcs->merges[i], fr, tr, &x1, &x2, &y1, &y2, &sx1, &sx2, &sy1, &sy2);
2162    
2163 bertho 1.35 if(dot && !conf.merge_arrows)
2164 bertho 1.30 {
2165 bertho 1.31 int o = conf.left_right ? 1 : 0;
2166 bertho 1.49 gdImageArc(im, x2, y2+o, 8, 8, 0, 360, colorid);
2167 bertho 1.44 /* BUG: We clip manually because libgd segfaults on out of bound values */
2168     if(x2+1 >= 0 && x2+1 < gdImageSX(im) && y2+o+1 >= 0 && y2+o+1 < gdImageSY(im))
2169 bertho 1.49 gdImageFillToBorder(im, x2+1, y2+o+1, colorid, colorid);
2170 bertho 1.30 }
2171 bertho 1.35 else if(dot && conf.merge_arrows)
2172     {
2173     /*
2174     * Arrow patch from Haroon Rafique <haroon.rafique@utoronto.ca>
2175     * Slightly adapted to be more configurable.
2176     */
2177     int sx, sy; /* start point coordinates */
2178     int ex, ey; /* end point coordinates */
2179     double theta;
2180     double u1, v1, u2, v2;
2181     gdPoint p[3];
2182    
2183     sx = x1; sy = y1;
2184     ex = x2; ey = y2;
2185 bertho 1.64 if(conf.left_right && !conf.merge_on_tag)
2186 bertho 1.35 {
2187     if(fr->branch == tr->branch)
2188     {
2189     int yy = (y1 < y2 ? y1 : y2) - 5;
2190     /* line from (x1,yy) to (x2,yy) */
2191     sy = ey = yy;
2192     }
2193     else
2194     {
2195 bertho 1.64 sy = y1 + 3 * sy1;
2196     ey = y2 + 3 * sy2;
2197 bertho 1.35 }
2198     }
2199     else
2200     {
2201     if(fr->branch == tr->branch)
2202     {
2203     int xx = (x1 < x2 ? x1 : x2) - 5;
2204     /* line from (xx,y1) to (xx,y2) */
2205     sx = ex = xx;
2206     }
2207     else
2208     {
2209 bertho 1.64 sx = x1 + 3 * sx1;
2210     ex = x2 + 3 * sx2;
2211 bertho 1.35 }
2212     }
2213     /*
2214     * inspiration for arrow code comes from arrows.c in the
2215     * graphviz package. Thank you, AT&T
2216     */
2217     /* theta in radians */
2218     theta = atan2((double)(sy-ey), (double)(sx-ex));
2219     u1 = (double)conf.arrow_length * cos(theta);
2220     v1 = (double)conf.arrow_length * sin(theta);
2221     u2 = (double)conf.arrow_width * cos(theta + M_PI/2.0);
2222     v2 = (double)conf.arrow_width * sin(theta + M_PI/2.0);
2223     /* points of polygon (triangle) */
2224     p[0].x = ROUND(ex + u1 - u2);
2225     p[0].y = ROUND(ey + v1 - v2);
2226     p[1].x = ex;
2227     p[1].y = ey;
2228     p[2].x = ROUND(ex + u1 + u2);
2229     p[2].y = ROUND(ey + v1 + v2);
2230     /* draw the polygon (triangle) */
2231 bertho 1.49 gdImageFilledPolygon(im, p, 3, colorid);
2232 bertho 1.35 }
2233 bertho 1.30 else
2234     {
2235 bertho 1.64 if(conf.left_right && !conf.merge_on_tag)
2236 bertho 1.30 {
2237 bertho 1.32 if(fr->branch == tr->branch)
2238 bertho 1.31 {
2239 bertho 1.32 int yy = (y1 < y2 ? y1 : y2) - 5;
2240 bertho 1.49 gdImageLine(im, x1, y1, x1, yy, colorid);
2241     gdImageLine(im, x2, y2, x2, yy, colorid);
2242     gdImageLine(im, x1, yy, x2, yy, colorid);
2243 bertho 1.31 }
2244     else
2245     {
2246 bertho 1.64 gdImageLine(im, x1, y1, x1, y1+3*sy1, colorid);
2247     gdImageLine(im, x2, y2, x2, y2+3*sy2, colorid);
2248     gdImageLine(im, x1, y1+3*sy1, x2, y2+3*sy2, colorid);
2249 bertho 1.31 }
2250 bertho 1.30 }
2251     else
2252     {
2253 bertho 1.32 if(fr->branch == tr->branch)
2254 bertho 1.31 {
2255 bertho 1.32 int xx = (x1 < x2 ? x1 : x2) - 5;
2256 bertho 1.49 gdImageLine(im, xx, y1, x1, y1, colorid);
2257     gdImageLine(im, xx, y2, x2, y2, colorid);
2258     gdImageLine(im, xx, y1, xx, y2, colorid);
2259 bertho 1.31 }
2260     else
2261     {
2262 bertho 1.64 gdImageLine(im, x1, y1, x1+3*sx1, y1, colorid);
2263     gdImageLine(im, x2, y2, x2+3*sx2, y2, colorid);
2264     gdImageLine(im, x1+3*sx1, y1, x2+3*sx2, y2, colorid);
2265 bertho 1.31 }
2266 bertho 1.30 }
2267     }
2268     }
2269     }
2270    
2271 bertho 1.44 static void draw_messages(gdImagePtr im, int offset)
2272     {
2273     int i;
2274    
2275     for(i = 0; i < nmsg_stack; i++)
2276     {
2277 bertho 1.59 draw_stringnl(im, msg_stack[i].msg, &conf.msg_font, conf.margin_left, offset, ALIGN_HL|ALIGN_VT, clr(im, "msg_color", NULL, NULL, 0));
2278 bertho 1.44 offset += msg_stack[i].h;
2279     }
2280     }
2281    
2282     static gdImagePtr make_image(rcsfile_t *rcs)
2283 bertho 1.1 {
2284     gdImagePtr im;
2285     int i;
2286 bertho 1.58 int bgid;
2287 bertho 1.8 char *cptr;
2288 bertho 1.44 int w, h;
2289     int subx = 0, suby = 0;
2290     int msgh = 0;
2291    
2292     if(subtree_branch)
2293     {
2294 bertho 1.58 w = 0;
2295     h = 0;
2296 bertho 1.44 if(subtree_rev)
2297     {
2298     for(i = 0; i < subtree_rev->nbranches; i++)
2299 bertho 1.58 calc_subtree_size(subtree_rev->branches[i], &subx, &suby, &w, &h);
2300 bertho 1.44 }
2301     else
2302 bertho 1.58 calc_subtree_size(subtree_branch, &subx, &suby, &w, &h);
2303 bertho 1.44 }
2304     else
2305     {
2306 bertho 1.58 w = rcs->tw;
2307     h = rcs->th;
2308 bertho 1.44 }
2309 bertho 1.1
2310 bertho 1.15 cptr = expand_string(conf.title, rcs, NULL, NULL, NULL, NULL);
2311     i = get_swidth(cptr, &conf.title_font);
2312 bertho 1.44 if(i > w)
2313     w = i;
2314    
2315     if(!quiet && nmsg_stack)
2316     {
2317     int msgw = 0;
2318     for(i = 0; i < nmsg_stack; i++)
2319     {
2320     int ww = msg_stack[i].w = get_swidth(msg_stack[i].msg, &conf.msg_font);
2321     int hh = msg_stack[i].h = get_sheight(msg_stack[i].msg, &conf.msg_font);
2322     msgh += hh;
2323     h += hh;
2324     if(ww > msgw)
2325     msgw = ww;
2326     }
2327     if(msgw > w)
2328     w = msgw;
2329     }
2330    
2331 bertho 1.58 w += conf.margin_left + conf.margin_right;
2332     h += conf.margin_top + conf.margin_bottom;
2333    
2334 bertho 1.44 im = gdImageCreate(w, h);
2335 bertho 1.59 bgid = clr(im, "color_bg", NULL, NULL, 0)->id; /* The background is always a unique color, */
2336 bertho 1.58 zap_clr(); /* so clear the color ref table */
2337 bertho 1.60 clr(im, NULL, NULL, NULL, 0);
2338 bertho 1.1
2339 bertho 1.19 if(conf.transparent_bg)
2340 bertho 1.58 gdImageColorTransparent(im, bgid);
2341 bertho 1.19
2342 bertho 1.30 if(!conf.merge_front)
2343 bertho 1.31 draw_merges(im, rcs, 0);
2344 bertho 1.30
2345 bertho 1.1 for(i = 0; i < rcs->nbranches; i++)
2346 bertho 1.32 {
2347 bertho 1.44 if(!rcs->branches[i]->folded && !(subtree_branch && !rcs->branches[i]->subtree_draw))
2348 bertho 1.32 draw_branch(im, rcs->branches[i]);
2349     }
2350 bertho 1.31
2351     draw_merges(im, rcs, 1); /* The dots of the merge dest */
2352    
2353 bertho 1.1 for(i = 0; i < rcs->nbranches; i++)
2354     {
2355     if(rcs->branches[i]->branchpoint)
2356     draw_connector(im, rcs->branches[i]);
2357     }
2358 bertho 1.44
2359     /* Clear the margins if we have a partial tree */
2360     if(subtree_branch)
2361     {
2362 bertho 1.58 gdImageFilledRectangle(im, 0, 0, w-1, conf.margin_top-1, bgid);
2363     gdImageFilledRectangle(im, 0, 0, conf.margin_left-1, h-1, bgid);
2364     gdImageFilledRectangle(im, 0, h-conf.margin_bottom, w-1, h-1, bgid);
2365     gdImageFilledRectangle(im, w-conf.margin_right, 0, w-1, h-1, bgid);
2366 bertho 1.44 }
2367    
2368 bertho 1.59 draw_stringnl(im, cptr, &conf.title_font, conf.title_x, conf.title_y, conf.title_align, clr(im, "title_color", NULL, NULL, 0));
2369 bertho 1.8 xfree(cptr);
2370 bertho 1.1
2371 bertho 1.30 if(conf.merge_front)
2372 bertho 1.31 draw_merges(im, rcs, 0);
2373 bertho 1.30
2374 bertho 1.44 if(!quiet)
2375     draw_messages(im, h - conf.margin_bottom/2 - msgh);
2376    
2377 bertho 1.1 return im;
2378     }
2379    
2380 bertho 1.7 /*
2381     **************************************************************************
2382     * Layout routines
2383 bertho 1.19 *
2384     * Branch BBox:
2385     * left = center_x - total_width / 2 (cx-tw)/2
2386     * right = center_x + total_width / 2 (cx+tw)/2
2387     * top = y_pos (y)
2388     * bottom = y_pos + total_height (y+th)
2389     *
2390     * Margins of branches:
2391     *
2392     * . .
2393     * . .
2394     * +--------------+
2395     * ^
2396     * | branch_margin .
2397     * v .
2398     * ----------------+ .
2399     * | ^ |
2400     * | | branch_connect |
2401     * | v |
2402     *..-+ +t-----+------+ +------+------+
2403     * | l | | |
2404     * | <--> | branch bbox | <--> | branch bbox |
2405     * | | | r | | |
2406     *..-+ | +------------b+ | +-------------+
2407     * | ^ branch_margin
2408     * | | branch_margin
2409     * | v
2410     * | +-------------+
2411     * | . .
2412     * | . .
2413     * |
2414     * branch_margin
2415     *
2416     * FIXME: There are probable som +/-1 errors in the code...
2417     * (notably shadows are not calculated in the margins)
2418 bertho 1.7 **************************************************************************
2419     */
2420 bertho 1.19 static void move_branch(branch_t *b, int x, int y)
2421 bertho 1.1 {
2422     int i;
2423 bertho 1.7 b->cx += x;
2424 bertho 1.1 b->y += y;
2425     for(i = 0; i < b->nrevs; i++)
2426     {
2427 bertho 1.7 b->revs[i]->cx += x;
2428 bertho 1.1 b->revs[i]->y += y;
2429     }
2430     }
2431    
2432 bertho 1.19 static void initial_reposition_branch(revision_t *r, int *x, int *w)
2433 bertho 1.6 {
2434     int i, j;
2435     for(j = 0; j < r->nbranches; j++)
2436     {
2437     branch_t *b = r->branches[j];
2438 bertho 1.7 *x += *w + conf.rev_minline + b->tw/2 - b->cx;
2439 bertho 1.6 *w = b->tw/2;
2440 bertho 1.7 move_branch(b, *x, r->y + r->h/2 + conf.branch_connect);
2441     *x = b->cx;
2442 bertho 1.6 /* Recurse to move branches of branched revisions */
2443     for(i = b->nrevs-1; i >= 0; i--)
2444     {
2445 bertho 1.18 initial_reposition_branch(b->revs[i], x, w);
2446 bertho 1.6 }
2447     }
2448     }
2449    
2450 bertho 1.25 static void initial_reposition_branch_lr(revision_t *r, int *y, int *h)
2451     {
2452     int i, j;
2453     for(j = 0; j < r->nbranches; j++)
2454     {
2455     branch_t *b = r->branches[j];
2456     *y += *h + conf.rev_minline + b->th/2 - b->y;
2457     *h = b->th/2;
2458     move_branch(b, r->cx + r->w/2 + conf.branch_connect, *y);
2459     *y = b->y;
2460     /* Recurse to move branches of branched revisions */
2461     for(i = b->nrevs-1; i >= 0; i--)
2462     {
2463 bertho 1.32 initial_reposition_branch_lr(b->revs[i], y, h);
2464 bertho 1.25 }
2465     }
2466     }
2467    
2468 bertho 1.19 static void rect_union(int *x, int *y, int *w, int *h, branch_t *b)
2469 bertho 1.1 {
2470     int x1 = *x;
2471     int x2 = x1 + *w;
2472     int y1 = *y;
2473     int y2 = y1 + *h;
2474 bertho 1.25 int xx1;
2475     int xx2;
2476     int yy1;
2477     int yy2;
2478    
2479     if(conf.left_right)
2480     {
2481     xx1 = b->cx;
2482     yy1 = b->y - b->th/2;
2483     }
2484     else
2485     {
2486     xx1 = b->cx - b->tw/2;
2487     yy1 = b->y;
2488     }
2489     xx2 = xx1 + b->tw;
2490     yy2 = yy1 + b->th;
2491    
2492 bertho 1.1 x1 = MIN(x1, xx1);
2493     x2 = MAX(x2, xx2);
2494     y1 = MIN(y1, yy1);
2495     y2 = MAX(y2, yy2);
2496     *x = x1;
2497     *y = y1;
2498     *w = x2 - x1;
2499     *h = y2 - y1;
2500     }
2501    
2502 bertho 1.44 static void calc_subtree_size(branch_t *b, int *x, int *y, int *w, int *h)
2503     {
2504     int i, j;
2505    
2506     rect_union(x, y, w, h, b);
2507    
2508     for(i = 0; i < b->nrevs; i++)
2509     {
2510     for(j = 0; j < b->revs[i]->nbranches; j++)
2511     calc_subtree_size(b->revs[i]->branches[j], x, y, w, h);
2512     }
2513     }
2514    
2515 bertho 1.19 static int branch_intersects(int top, int bottom, int left, branch_t *b)
2516 bertho 1.7 {
2517     int br = b->cx + b->tw/2;
2518     int bt = b->y - conf.branch_connect - conf.branch_margin/2;
2519     int bb = b->y + b->th + conf.branch_margin/2;
2520     return !(bt > bottom || bb < top || br >= left);
2521     }
2522    
2523 bertho 1.25 static int branch_intersects_lr(int left, int right, int top, branch_t *b)
2524     {
2525     int bt = b->y + b->th/2;
2526     int bl = b->cx - conf.branch_connect - conf.branch_margin/2;
2527     int br = b->cx + b->tw + conf.branch_margin/2;
2528     return !(bl > right || br < left || bt >= top);
2529     }
2530    
2531 bertho 1.19 static int kern_branch(rcsfile_t *rcs, branch_t *b)
2532 bertho 1.7 {
2533     int left = b->cx - b->tw/2;
2534     int top = b->y - conf.branch_connect - conf.branch_margin/2;
2535     int bottom = b->y + b->th + conf.branch_margin/2;
2536     int i;
2537     int xpos = 0;
2538    
2539     for(i = 0; i < rcs->nbranches; i++)
2540     {
2541     branch_t *bp = rcs->branches[i];
2542     if(bp == b)
2543     continue;
2544     if(branch_intersects(top, bottom, left, bp))
2545     {
2546     int m = bp->cx + bp->tw/2 + conf.branch_margin;
2547     if(m > xpos)
2548     xpos = m;
2549     }
2550     }
2551     if(xpos && (b->cx - b->tw/2) - xpos > 0)
2552     {
2553     move_branch(b, xpos - (b->cx - b->tw/2), 0);
2554     return 1;
2555     }
2556     return 0;
2557     }
2558    
2559 bertho 1.25 static int kern_branch_lr(rcsfile_t *rcs, branch_t *b)
2560     {
2561     int top = b->y - b->th/2;
2562     int left = b->cx - conf.branch_connect - conf.branch_margin/2;
2563     int right = b->cx + b->tw + conf.branch_margin/2;
2564     int i;
2565     int ypos = 0;
2566    
2567     for(i = 0; i < rcs->nbranches; i++)
2568     {
2569     branch_t *bp = rcs->branches[i];
2570     if(bp == b)
2571     continue;
2572     if(branch_intersects_lr(left, right, top, bp))
2573     {
2574     int m = bp->y + bp->th/2 + conf.branch_margin;
2575     if(m > ypos)
2576     ypos = m;
2577     }
2578     }
2579     if(ypos && (b->y - b->th/2) - ypos > 0)
2580     {
2581     move_branch(b, 0, ypos - (b->y - b->th/2));
2582     return 1;
2583     }
2584     return 0;
2585     }
2586    
2587 bertho 1.19 static int kern_tree(rcsfile_t *rcs)
2588 bertho 1.18 {
2589     int i;
2590     int moved;
2591 bertho 1.19 int safeguard;
2592     int totalmoved = 0;
2593     for(moved = 1, safeguard = LOOPSAFEGUARD; moved && safeguard; safeguard--)
2594 bertho 1.18 {
2595     moved = 0;
2596     for(i = 1; i < rcs->nbranches; i++)
2597     {
2598 bertho 1.25 if(conf.left_right)
2599     moved += kern_branch_lr(rcs, rcs->branches[i]);
2600     else
2601     moved += kern_branch(rcs, rcs->branches[i]);
2602 bertho 1.18 }
2603 bertho 1.19 totalmoved += moved;
2604 bertho 1.18 #ifdef DEBUG
2605     fprintf(stderr, "kern_tree: moved=%d\n", moved);
2606     #endif
2607     }
2608 bertho 1.44 if(!safeguard)
2609     stack_msg(MSG_WARN, "kern_tree: safeguard terminated possible infinite loop; please report.");
2610 bertho 1.19 return totalmoved;
2611     }
2612    
2613     static int index_of_revision(revision_t *r)
2614     {
2615     branch_t *b = r->branch;
2616     int i;
2617     for(i = 0; i < b->nrevs; i++)
2618     {
2619     if(r == b->revs[i])
2620     return i;
2621     }
2622 bertho 1.44 stack_msg(MSG_ERR, "index_of_revision: Cannot find revision in branch\n");
2623 bertho 1.19 return 0;
2624 bertho 1.18 }
2625    
2626 bertho 1.19 static void branch_bbox(branch_t *br, int *l, int *r, int *t, int *b)
2627     {
2628     if(l) *l = br->cx - br->tw/2;
2629     if(r) *r = br->cx + br->tw/2;
2630     if(t) *t = br->y;
2631 bertho 1.32 if(b) *b = br->y + br->th + ((conf.branch_dupbox && br->nrevs) ? conf.rev_minline + br->h : 0);
2632 bertho 1.19 }
2633    
2634     static void branch_ext_bbox(branch_t *br, int *l, int *r, int *t, int *b)
2635     {
2636     int extra = conf.branch_margin & 1; /* Correct +/-1 error on div 2 */
2637     branch_bbox(br, l, r, t, b);
2638     if(l) *l -= conf.branch_margin/2;
2639     if(r) *r += conf.branch_margin/2 + extra;
2640     if(t) *t -= conf.branch_connect + conf.branch_margin/2;
2641     if(b) *b += conf.branch_margin/2 + extra;
2642     }
2643    
2644     static int branch_distance(branch_t *br1, branch_t *br2)
2645     {
2646     int l1, r1, t1, b1;
2647     int l2, r2, t2, b2;
2648     assert(br1 != NULL);
2649     assert(br2 != NULL);
2650     branch_bbox(br1, &l1, &r1, NULL, NULL);
2651     branch_bbox(br2, &l2, &r2, NULL, NULL);
2652     branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
2653     branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
2654     /* Return:
2655     * - 0 if branches have no horizontal overlap
2656     * - positive if b1 is left of b2
2657     * - negative if b2 is left of b1
2658     */
2659     if((t1 > t2 && t1 < b2) || (b1 > t2 && b1 < b2))
2660     return l1 < l2 ? l2 - r1 : -(l1 - r2);
2661     else
2662     return 0;
2663     }
2664    
2665     static int space_needed(branch_t *br1, branch_t *br2)
2666     {
2667     int t1, b1;
2668     int t2, b2;
2669     assert(br1 != NULL);
2670     assert(br2 != NULL);
2671     assert(br1->cx < br2->cx); /* br1 must be left of br2 */
2672     branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
2673     branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
2674     /* Return:
2675     * - positive if top br1 is located lower than br2
2676     * - negatve is top br2 is located lower than br1
2677     */
2678     if(t1 > t2)
2679     return -(t1 - b2);
2680     else
2681     return t2 - b1;
2682     }
2683    
2684     static void move_yr_branch(branch_t *b, int dy)
2685 bertho 1.18 {
2686     int i, j;
2687     #ifdef DEBUG
2688 bertho 1.23 /* fprintf(stderr, "move_yr_branch: b=%s, dy=%d\n", b->branch->branch, dy);*/
2689 bertho 1.18 #endif
2690 bertho 1.19 b->y += dy;
2691     for(i = 0; i < b->nrevs; i++)
2692 bertho 1.18 {
2693 bertho 1.19 b->revs[i]->y += dy;
2694     for(j = 0; j < b->revs[i]->nbranches; j++)
2695 bertho 1.18 {
2696 bertho 1.19 #ifdef DEBUG
2697 bertho 1.23 /* fprintf(stderr, ".");*/
2698 bertho 1.19 #endif
2699     move_yr_branch(b->revs[i]->branches[j], dy);
2700 bertho 1.18 }
2701     }
2702     }
2703    
2704 bertho 1.19 static void move_trunk(revision_t *r, int dy)
2705 bertho 1.18 {
2706 bertho 1.19 int i, j;
2707 bertho 1.18 branch_t *b = r->branch;
2708 bertho 1.19 b->th += dy;
2709     for(i = index_of_revision(r); i < b->nrevs; i++)
2710 bertho 1.18 {
2711 bertho 1.19 #ifdef DEBUG
2712     fprintf(stderr, "move_trunk: start %s, moving %s by %d (b's %d)\n", r->rev->rev, b->revs[i]->rev->rev, dy, b->revs[i]->nbranches);
2713     #endif
2714     b->revs[i]->y += dy;
2715     for(j = 0; j < b->revs[i]->nbranches; j++)
2716     {
2717     move_yr_branch(b->revs[i]->branches[j], dy);
2718     }
2719 bertho 1.18 }
2720     }
2721    
2722 bertho 1.19 static int space_below(rcsfile_t *rcs, revision_t *r)
2723 bertho 1.18 {
2724 bertho 1.19 int i, j;
2725     int bl, br, bb;
2726 bertho 1.18 int space = INT_MAX;
2727 bertho 1.19 branch_t *b = r->branch;
2728     branch_t *minb = NULL;
2729    
2730     branch_ext_bbox(b, &bl, &br, NULL, &bb);
2731 bertho 1.18 for(i = 0; i < rcs->nbranches; i++)
2732     {
2733 bertho 1.19 int tbl, tbr, tbt;
2734 bertho 1.18 branch_t *tb = rcs->branches[i];
2735 bertho 1.19 branch_ext_bbox(tb, &tbl, &tbr, &tbt, NULL);
2736 bertho 1.18 if(tb == b)
2737     continue;
2738 bertho 1.19 if(tbt > bb) /* Must be below our branch */
2739 bertho 1.18 {
2740 bertho 1.19 if(tb->branchpoint) /* Take account for the horiz connector */
2741     tbl = tb->branchpoint->cx + tb->branchpoint->branch->tw/2;
2742     if((bl >= tbl && bl <= tbr) || (br <= tbr && br >= tbl))
2743 bertho 1.18 {
2744 bertho 1.19 int s = tbt - bb - conf.branch_connect;
2745     if(s < space)
2746 bertho 1.18 {
2747 bertho 1.19 space = s;
2748     minb = tb;
2749 bertho 1.18 }
2750     }
2751     }
2752     }
2753 bertho 1.19 if(b->branchpoint)
2754     {
2755     for(i = index_of_revision(r); i < b->nrevs; i++)
2756     {
2757     for(j = 0; j < b->revs[i]->nbranches; j++)
2758     {
2759     int s = space_below(rcs, b->revs[i]->branches[j]->revs[0]);
2760     if(s < space)
2761     space = s;
2762     }
2763     }
2764     }
2765     #ifdef DEBUG
2766     fprintf(stderr, "space_below: from %s have %d to %s\n", b->branch->branch, space, minb ? minb->branch->branch : "<recursed>");
2767     #endif
2768 bertho 1.18 return space;
2769     }
2770    
2771 bertho 1.19 static int space_available(rcsfile_t *rcs, branch_t *colbr, branch_t *tagbr, int *nl, revision_t **bpcommon)
2772 bertho 1.18 {
2773 bertho 1.19 int i;
2774     int space = 0;
2775     int nlinks = 0;
2776     revision_t *r;
2777     branch_t *b;
2778     branch_t *ancestor;
2779     revision_t *branchpoint;
2780    
2781     if(!tagbr->branchpoint || !colbr->branchpoint)
2782 bertho 1.18 {
2783 bertho 1.44 stack_msg(MSG_WARN, "space_available: Trying to stretch the top?");
2784 bertho 1.19 return 0;
2785 bertho 1.18 }
2786 bertho 1.19
2787     r = colbr->branchpoint;
2788     b = r->branch;
2789     branchpoint = tagbr->branchpoint;
2790     ancestor = branchpoint->branch;
2791     assert(b != NULL);
2792     assert(ancestor != NULL);
2793    
2794     while(1)
2795 bertho 1.18 {
2796 bertho 1.19 int s;
2797     int rtag = b == ancestor ? index_of_revision(branchpoint)+1 : 0;
2798     for(i = index_of_revision(r); i >= rtag; i--)
2799 bertho 1.18 {
2800 bertho 1.19 if(i > 0)
2801     s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
2802     else
2803     s = b->revs[i]->y - (b->y + b->h);
2804     if(s < conf.rev_maxline)
2805     {
2806     space += conf.rev_maxline - s;
2807     nlinks++;
2808     }
2809 bertho 1.18 }
2810 bertho 1.19 s = space_below(rcs, r);
2811     if(s < space)
2812     space = s;
2813     #ifdef DEBUG
2814     if(space < 0)
2815     return -1;
2816     #endif
2817     if(b == ancestor)
2818     break;
2819     r = b->branchpoint;
2820     if(!r)
2821     {
2822     /* Not a common ancestor */
2823     r = colbr->branchpoint;
2824     b = r->branch;
2825     branchpoint = ancestor->branchpoint;
2826     if(!branchpoint)
2827     {
2828 bertho 1.44 stack_msg(MSG_WARN, "space_available: No common ancestor?");
2829 bertho 1.19 return 0;
2830     }
2831     ancestor = branchpoint->branch;
2832     assert(ancestor != NULL);
2833     nlinks = 0;
2834     space = 0;
2835     continue; /* Restart with a new ancestor */
2836     }
2837     b = r->branch;
2838     }
2839     if(nl)
2840     *nl = nlinks; /* Return the number of links that can stretch */
2841     if(bpcommon)
2842     *bpcommon = branchpoint; /* Return the ancestral branchpoint on the common branch */
2843     return space;
2844 bertho 1.18 }
2845    
2846 bertho 1.19 static int stretch_branches(rcsfile_t *rcs, branch_t *br1, branch_t *br2, int totalstretch)
2847 bertho 1.18 {
2848 bertho 1.19 revision_t *r;
2849     revision_t *bpcommon = NULL;
2850     branch_t *ancestor = NULL;
2851     branch_t *b;
2852 bertho 1.18 int i;
2853 bertho 1.19 int space;
2854 bertho 1.59 int nlinks = 0;
2855 bertho 1.19 int dy;
2856     int rest;
2857    
2858     space = space_available(rcs, br1, br2, &nlinks, &bpcommon);
2859     if(bpcommon)
2860     ancestor = bpcommon->branch;
2861    
2862     #ifdef DEBUG
2863     if(space == -1)
2864     return 0;
2865     fprintf(stderr, "stretch_branches: space available %d over %d links common %s\n", space, nlinks, ancestor->branch->branch);
2866     #endif
2867     if(space < totalstretch)
2868     return 0;
2869    
2870     dy = totalstretch / nlinks;
2871     rest = totalstretch - dy * nlinks;
2872    
2873     r = br1->branchpoint;
2874     b = r->branch;
2875     while(1)
2876 bertho 1.18 {
2877 bertho 1.19 int rtag = b == ancestor ? index_of_revision(bpcommon)+1 : 0;
2878     for(i = index_of_revision(r); i >= rtag; i--)
2879     {
2880     int s, q;
2881     if(i > 0)
2882     s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
2883     else
2884     s = b->revs[i]->y - (b->y + b->h);
2885     q = conf.rev_maxline - s;
2886     if(q > 0)
2887     {
2888     int d = rest ? rest/nlinks+1 : 0;
2889     if(q >= dy+d)
2890     {
2891     move_trunk(b->revs[i], dy+d);
2892     }
2893     else
2894     {
2895     move_trunk(b->revs[i], q);
2896     rest += dy+d - q;
2897     }
2898     rest -= d;
2899     nlinks--;
2900     }
2901     }
2902     if(b == ancestor)
2903     break;
2904     r = b->branchpoint;
2905     assert(r != NULL); /* else 'space_available' wouldn't have returned positively */
2906     b = r->branch;
2907 bertho 1.18 }
2908 bertho 1.19 return 1;
2909 bertho 1.18 }
2910    
2911 bertho 1.19 static branch_t *find_collision_branch(rcsfile_t *rcs, branch_t *b)
2912 bertho 1.18 {
2913     int i;
2914 bertho 1.19 int dist = INT_MAX;
2915     branch_t *col = NULL;
2916    
2917 bertho 1.18 for(i = 0; i < rcs->nbranches; i++)
2918     {
2919 bertho 1.19 int t = branch_distance(rcs->branches[i], b);
2920     if(t > 0 && t < dist)
2921 bertho 1.18 {
2922 bertho 1.19 dist = t;
2923     col = rcs->branches[i];
2924 bertho 1.18 }
2925     }
2926 bertho 1.19 return col;
2927 bertho 1.18 }
2928    
2929 bertho 1.44 static void auto_stretch(rcsfile_t *rcs)
2930 bertho 1.18 {
2931 bertho 1.19 int i;
2932     int safeguard;
2933 bertho 1.18
2934 bertho 1.19 for(i = 0, safeguard = LOOPSAFEGUARD; i < rcs->nbranches && safeguard; i++)
2935 bertho 1.18 {
2936 bertho 1.19 int bl, pr;
2937     branch_t *b = rcs->branches[i];
2938     if(!b->branchpoint)
2939     continue;
2940     branch_bbox(b, &bl, NULL, NULL, NULL);
2941     branch_bbox(b->branchpoint->branch, NULL, &pr, NULL, NULL);
2942     if(bl - conf.branch_margin - pr > 0)
2943 bertho 1.18 {
2944 bertho 1.19 branch_t *col;
2945 bertho 1.18 int spaceneeded;
2946 bertho 1.19 /* There is a potential to move branch b further left.
2947     * All branches obstructing this one from moving further
2948     * left must be originating from revisions below
2949     * b->branchpoint until a common ancester.
2950     * So, we search all branches for a branch that lies left
2951     * of b and is closest to b. This is then the collission
2952     * branch that needs to be moved.
2953     */
2954     col = find_collision_branch(rcs, b);
2955     if(!col)
2956 bertho 1.18 continue;
2957 bertho 1.19 spaceneeded = space_needed(col, b);
2958     if(spaceneeded < 0)
2959     continue;
2960     #ifdef DEBUG
2961     fprintf(stderr, "auto_stretch: %s collides %s need %d\n", b->branch->branch, col->branch->branch, spaceneeded);
2962     #endif
2963     /* Trace the collision branch back to find the common ancester
2964     * of both col and b. All revisions encountered while traversing
2965     * backwards must be stretched, including all revisions on the
2966     * common ancester from where the branches sprout.
2967     */
2968     if(stretch_branches(rcs, col, b, spaceneeded))
2969 bertho 1.18 {
2970 bertho 1.19 if(kern_tree(rcs))
2971     {
2972     /* Restart the process because movement can
2973     * cause more movement.
2974     */
2975     i = 0 - 1; /* -1 for the i++ of the loop */
2976     safeguard--; /* Prevent infinite loop, just in case */
2977     }
2978 bertho 1.23 /*return;*/
2979 bertho 1.18 }
2980     }
2981     }
2982 bertho 1.44 if(!safeguard)
2983     stack_msg(MSG_ERR, "auto_stretch: safeguard terminated possible infinite loop; please report.");
2984 bertho 1.18 }
2985    
2986 bertho 1.32 static void fold_branch(rcsfile_t *rcs, revision_t *r)
2987     {
2988     int i, j;
2989     branch_t *btag = NULL;
2990    
2991     for(i = 0; i < r->nbranches; i++)
2992     {
2993     branch_t *b = r->branches[i];
2994     if(!b->nrevs && b->ntags < 2)
2995     {
2996     /* No commits in this branch and no duplicate tags */
2997     if(!btag)
2998     btag = b;
2999     else
3000     {
3001     /* We have consecutive empty branches, fold */
3002     b->folded = 1;
3003 bertho 1.44 b->folded_to = btag;
3004 bertho 1.32 for(j = 0; j < rcs->nbranches; j++)
3005     {
3006     if(b == rcs->branches[j])
3007     {
3008     /* Zap the branch from the admin */
3009     memmove(&rcs->branches[j],
3010     &rcs->branches[j+1],
3011     (rcs->nbranches - j - 1)*sizeof(rcs->branches[0]));
3012     rcs->nbranches--;
3013 bertho 1.54 rcs->nfolds++;
3014 bertho 1.32 break;
3015     }
3016    
3017     }
3018     memmove(&r->branches[i], &r->branches[i+1], (r->nbranches - i - 1)*sizeof(r->branches[0]));
3019     r->nbranches--;
3020     i--; /* We have one less now */
3021    
3022     /* Add to the fold-list */
3023     btag->folds = xrealloc(btag->folds, (btag->nfolds+1) * sizeof(btag->folds[0]));
3024     btag->folds[btag->nfolds] = b;
3025     btag->nfolds++;
3026     }
3027     }
3028     else
3029     {
3030 bertho 1.33 if(!conf.branch_foldall)
3031     btag = NULL; /* Start a new box */
3032 bertho 1.32 /* Recursively fold sub-branches */
3033     for(j = 0; j < b->nrevs; j++)
3034     fold_branch(rcs, b->revs[j]);
3035     }
3036     }
3037     }
3038    
3039 bertho 1.44 static void mark_subtree(branch_t *b)
3040     {
3041     int i, j;
3042     b->subtree_draw = 1;
3043     for(i = 0; i < b->nrevs; i++)
3044     {
3045     for(j = 0; j < b->revs[i]->nbranches; j++)
3046     mark_subtree(b->revs[i]->branches[j]);
3047     }
3048     }
3049    
3050     static void make_layout(rcsfile_t *rcs)
3051 bertho 1.1 {
3052     int i, j;
3053     int x, y;
3054     int w, h;
3055     int w2;
3056    
3057 bertho 1.16 /* Remove all unwanted revisions */
3058     if(conf.strip_untagged)
3059     {
3060 bertho 1.21 int fr = conf.strip_first_rev ? 0 : 1;
3061 bertho 1.16 for(i = 0; i < rcs->nbranches; i++)
3062     {
3063     branch_t *bp = rcs->branches[i];
3064 bertho 1.18 for(j = fr; j < bp->nrevs-1; j++)
3065 bertho 1.16 {
3066 bertho 1.60 if(!bp->revs[j]->ntags && bp->revs[j]->stripped >= 0 && !bp->revs[j]->mergetarget && !bp->revs[j]->nbranches)
3067 bertho 1.16 {
3068 bertho 1.60 bp->revs[j+1]->stripped = 1;
3069 bertho 1.16 memmove(&bp->revs[j], &bp->revs[j+1], (bp->nrevs-j-1) * sizeof(bp->revs[0]));
3070     bp->nrevs--;
3071     j--;
3072     }
3073     }
3074     }
3075     }
3076    
3077 bertho 1.44 /* Find the sub-tree(s) we want to see */
3078     if(conf.branch_subtree && conf.branch_subtree[0])
3079     {
3080     branch_t **b;
3081     revision_t **r;
3082     rev_t rev;
3083     int k;
3084     char *tag = conf.branch_subtree;
3085    
3086     /* First translate any symbolic tag to a real branch/revision number */
3087     if(rcs->tags)
3088     {
3089     for(k = 0; k < rcs->tags->ntags; k++)
3090     {
3091     if(!strcmp(conf.branch_subtree, rcs->tags->tags[k]->tag))
3092     {
3093     if(rcs->tags->tags[k]->rev->isbranch)
3094     tag = rcs->tags->tags[k]->rev->branch;
3095     else
3096     tag = rcs->tags->tags[k]->rev->rev;
3097     break;
3098     }
3099     }
3100     }
3101    
3102     /* Find the corresponding branch */
3103     rev.branch = tag;
3104     rev.rev = NULL;
3105     rev.isbranch = 1;
3106     b = bsearch(&rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
3107     if(b)
3108     {
3109     if((*b)->branchpoint)
3110     {
3111     subtree_branch = *b;
3112     for(k = 0; k < (*b)->branchpoint->nbranches; k++)
3113     mark_subtree((*b)->branchpoint->branches[k]);
3114     }
3115     /*
3116     * else -> we want everything.
3117     * This happens for the top level branch because it has no
3118     * branchpoint. We do not set the subtree_branch, which then
3119     * results in drawing the whole tree as if we did not select a
3120     * particular branch.
3121     */
3122     }
3123     else
3124     {
3125     /* Maybe it is a revision we want all subtrees from */
3126     rev.rev = tag;
3127     rev.branch = NULL;
3128     rev.isbranch = 0;
3129     r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
3130     if(r)
3131     {
3132     if((*r)->nbranches)
3133     {
3134     subtree_branch = (*r)->branches[0];
3135     subtree_rev = *r;
3136     for(k = 0; k < (*r)->nbranches; k++)
3137     mark_subtree((*r)->branches[k]);
3138     }
3139     /*
3140     * else -> we select everything.
3141     * This happens for the any revision that has no branches.
3142     * We do not set the subtree_branch, which then results in
3143     * drawing the whole tree as if we did not select a
3144     * particular revision's branches.
3145     */
3146     }
3147     }
3148     }
3149    
3150     /* Fold all empty branches in one box on the same branchpoint */
3151 bertho 1.32 if(conf.branch_fold)
3152     {
3153     for(i = 0; i < rcs->branches[0]->nrevs; i++)
3154     {
3155 bertho 1.44 if(rcs->branches[0]->revs[i]->nbranches > 0)
3156 bertho 1.32 fold_branch(rcs, rcs->branches[0]->revs[i]);
3157 bertho 1.40 }
3158     }
3159    
3160     /* Remove all unwanted tags */
3161     for(i = 0; i < rcs->nbranches; i++)
3162     {
3163     branch_t *bp = rcs->branches[i];
3164     for(j = 0; j < bp->nrevs; j++)
3165     {
3166     revision_t *r = bp->revs[j];
3167     int k;
3168     for(k = 0; k < r->ntags; k++)
3169     {
3170 bertho 1.43 if(r->tags[k]->ignore > 0)
3171 bertho 1.40 {
3172     memmove(&r->tags[k], &r->tags[k+1], (r->ntags-k-1) * sizeof(r->tags[0]));
3173     r->ntags--;
3174     k--;
3175     }
3176     }
3177 bertho 1.32 }
3178     }
3179    
3180 bertho 1.1 /* Calculate the box-sizes of the revisions */
3181 bertho 1.7 for(i = 0; i < rcs->nsrev; i++)
3182 bertho 1.1 {
3183     revision_t *rp;
3184     int w;
3185     int h;
3186 bertho 1.7 rp = rcs->srev[i];
3187 bertho 1.60 rp->revtext = expand_string(conf.rev_text.node ? eval_string(conf.rev_text.node, rp) : conf.rev_text.str, rcs, rp, rp->rev, NULL, rp->ntags ? rp->tags[0] : NULL);
3188     rp->revidtext = expand_string(conf.rev_idtext.node ? eval_string(conf.rev_idtext.node, rp) : conf.rev_idtext.str, rcs, rp, rp->rev, NULL, rp->ntags ? rp->tags[0] : NULL);
3189 bertho 1.9 w = get_swidth(rp->revtext, &conf.rev_text_font);
3190 bertho 1.60 j = get_swidth(rp->revidtext, &conf.rev_font);
3191 bertho 1.9 if(j > w)
3192     w = j;
3193 bertho 1.46 h = get_sheight(rp->revtext, &conf.rev_text_font);
3194     if(!conf.rev_hidenumber)
3195 bertho 1.60 h += get_sheight(rp->revidtext, &conf.rev_font);
3196 bertho 1.1 for(j = 0; j < rp->ntags; j++)
3197     {
3198     int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);
3199 bertho 1.31 int th;
3200 bertho 1.1 if(ww > w) w = ww;
3201 bertho 1.31 th = get_sheight(rp->tags[j]->tag, &conf.tag_font) + conf.rev_separator;
3202     rp->tags[j]->yofs = h + th/2 + conf.rev_tspace;
3203     h += th;
3204 bertho 1.1 }
3205     rp->w = w + conf.rev_lspace + conf.rev_rspace;
3206     rp->h = h + conf.rev_tspace + conf.rev_bspace;
3207     }
3208    
3209     /* Calculate the box-sizes of the branches */
3210     for(i = 0; i < rcs->nbranches; i++)
3211     {
3212     branch_t *bp = rcs->branches[i];
3213     int w;
3214     int h;
3215 bertho 1.32 if(!bp->nfolds)
3216     {
3217     w = get_swidth(bp->branch->branch, &conf.branch_font);
3218 bertho 1.46 if(conf.rev_hidenumber)
3219     h = 0;
3220     else
3221     h = get_sheight(bp->branch->branch, &conf.branch_font);
3222 bertho 1.32 for(j = 0; j < bp->ntags; j++)
3223     {
3224     int ww = get_swidth(bp->tags[j]->tag, &conf.branch_tag_font);
3225     if(ww > w) w = ww;
3226     h += get_sheight(bp->tags[j]->tag, &conf.branch_tag_font);
3227     }
3228     }
3229     else
3230 bertho 1.1 {
3231 bertho 1.32 int h1, h2;
3232     int w1, w2;
3233     int fw;
3234     w1 = get_swidth(bp->branch->branch, &conf.branch_font);
3235     w1 += get_swidth(" ", &conf.branch_font);
3236     w2 = get_swidth(bp->tags[0]->tag, &conf.branch_tag_font);
3237     fw = w1;
3238     w = w1 + w2;
3239     h1 = get_sheight(bp->branch->branch, &conf.branch_font);
3240     h2 = get_sheight(bp->tags[0]->tag, &conf.branch_tag_font);
3241     h = MAX(h1, h2);
3242     for(j = 0; j < bp->nfolds; j++)
3243     {
3244     w1 = get_swidth(bp->folds[j]->branch->branch, &conf.branch_font);
3245     w1 += get_swidth(" ", &conf.branch_font);
3246     w2 = get_swidth(bp->folds[j]->tags[0]->tag, &conf.branch_tag_font);
3247     if(w1 > fw)
3248     fw = w1;
3249     if(w1 + w2 > w)
3250     w = w1 + w2;
3251     h1 = get_sheight(bp->folds[j]->branch->branch, &conf.branch_font);
3252     h2 = get_sheight(bp->folds[j]->tags[0]->tag, &conf.branch_tag_font);
3253     h += MAX(h1, h2);
3254     }
3255     bp->fw = fw;
3256 bertho 1.1 }
3257     w += conf.branch_lspace + conf.branch_rspace;
3258     h += conf.branch_tspace + conf.branch_bspace;
3259     bp->w = w;
3260     bp->h = h;
3261 bertho 1.25 if(conf.left_right)
3262     {
3263     for(j = 0; j < bp->nrevs; j++)
3264     {
3265     if(bp->revs[j]->h > h)
3266     h = bp->revs[j]->h;
3267     w += bp->revs[j]->w + conf.rev_minline;
3268     }
3269 bertho 1.32 if(conf.branch_dupbox && bp->nrevs)
3270 bertho 1.25 w += bp->w + conf.rev_minline;
3271     }
3272     else
3273 bertho 1.1 {
3274 bertho 1.25 for(j = 0; j < bp->nrevs; j++)
3275     {
3276     if(bp->revs[j]->w > w)
3277     w = bp->revs[j]->w;
3278     h += bp->revs[j]->h + conf.rev_minline;
3279     }
3280 bertho 1.32 if(conf.branch_dupbox && bp->nrevs)
3281 bertho 1.25 h += bp->h + conf.rev_minline;
3282 bertho 1.1 }
3283     bp->th = h;
3284     bp->tw = w;
3285     }
3286    
3287     /* Calculate the relative positions of revs in a branch */
3288 bertho 1.25 if(conf.left_right)
3289     {
3290     for(i = 0; i < rcs->nbranches; i++)
3291     {
3292     branch_t *b = rcs->branches[i];
3293     y = b->th/2;
3294     x = b->w;
3295     b->y = y;
3296     b->cx = 0;
3297     for(j = 0; j < b->nrevs; j++)
3298     {
3299     x += conf.rev_minline;
3300     b->revs[j]->y = y;
3301     b->revs[j]->cx = x;
3302     x += b->revs[j]->w;
3303     }
3304     }
3305     }
3306     else
3307 bertho 1.1 {
3308 bertho 1.25 for(i = 0; i < rcs->nbranches; i++)
3309     {
3310     branch_t *b = rcs->branches[i];
3311     x = b->tw/2;
3312     y = b->h;
3313     b->cx = x;
3314     b->y = 0;
3315     for(j = 0; j < b->nrevs; j++)
3316     {
3317     y += conf.rev_minline;
3318     b->revs[j]->cx = x;
3319     b->revs[j]->y = y;
3320     y += b->revs[j]->h;
3321     }
3322 bertho 1.1 }
3323     }
3324    
3325 bertho 1.18 /* Initially reposition the branches from bottom to top progressively right */
3326 bertho 1.25 if(conf.left_right)
3327     {
3328     x = rcs->branches[0]->y;
3329     w2 = rcs->branches[0]->th / 2;
3330     for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
3331     {
3332     initial_reposition_branch_lr(rcs->branches[0]->revs[i], &x, &w2);
3333     }
3334     }
3335     else
3336 bertho 1.1 {
3337 bertho 1.25 x = rcs->branches[0]->cx;
3338     w2 = rcs->branches[0]->tw / 2;
3339     for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
3340     {
3341     initial_reposition_branch(rcs->branches[0]->revs[i], &x, &w2);
3342     }
3343 bertho 1.1 }
3344    
3345 bertho 1.19 /* Initially move branches left if there is room */
3346     kern_tree(rcs);
3347    
3348 bertho 1.18 /* Try to kern the branches more by expanding the inter-revision spacing */
3349 bertho 1.25 if(conf.auto_stretch && !conf.left_right)
3350 bertho 1.19 auto_stretch(rcs);
3351 bertho 1.7
3352 bertho 1.1 /* Calculate overall image size */
3353 bertho 1.25 if(conf.left_right)
3354     {
3355     x = rcs->branches[0]->cx;
3356     y = rcs->branches[0]->y - rcs->branches[0]->th/2;
3357     }
3358     else
3359     {
3360     x = rcs->branches[0]->cx - rcs->branches[0]->tw/2;
3361     y = rcs->branches[0]->y;
3362     }
3363 bertho 1.1 w = rcs->branches[0]->tw;
3364     h = rcs->branches[0]->th;
3365     for(i = 1; i < rcs->nbranches; i++)
3366     rect_union(&x, &y, &w, &h, rcs->branches[i]);
3367     rcs->tw = w;
3368     rcs->th = h;
3369 bertho 1.16
3370     /* Flip the entire tree */
3371     if(conf.upside_down)
3372     {
3373 bertho 1.25 if(conf.left_right)
3374     {
3375     x += rcs->tw;
3376     for(i = 0; i < rcs->nbranches; i++)
3377     {
3378     branch_t *b = rcs->branches[i];
3379     for(j = 0; j < b->nrevs; j++)
3380     {
3381     revision_t *r = b->revs[j];
3382 bertho 1.44 r->cx = x - r->cx - r->w;
3383 bertho 1.25 }
3384 bertho 1.44 b->cx = x - b->cx - b->w;
3385 bertho 1.25 }
3386     }
3387     else
3388 bertho 1.16 {
3389 bertho 1.25 y += rcs->th;
3390     for(i = 0; i < rcs->nbranches; i++)
3391 bertho 1.16 {
3392 bertho 1.25 branch_t *b = rcs->branches[i];
3393     for(j = 0; j < b->nrevs; j++)
3394     {
3395     revision_t *r = b->revs[j];
3396 bertho 1.44 r->y = y - r->y - r->h;
3397 bertho 1.25 }
3398 bertho 1.44 b->y = y - b->y - b->h;
3399 bertho 1.16 }
3400     }
3401     }
3402 bertho 1.44
3403     /* Relocate the lot if we only draw a sub-tree */
3404     if(subtree_branch)
3405     {
3406     int xx, yy;
3407    
3408     if(subtree_branch->folded) /* Fix the reference if the branch got folded */
3409     subtree_branch = subtree_branch->folded_to;
3410    
3411     xx = conf.left_right ? subtree_branch->cx : subtree_branch->cx - subtree_branch->tw/2;
3412     yy = conf.left_right ? subtree_branch->y - subtree_branch->th/2 : subtree_branch->y;
3413     if(subtree_branch != rcs->branches[0])
3414     {
3415     if(conf.left_right)
3416     xx -= conf.branch_connect;
3417     else
3418     yy -= conf.branch_connect;
3419     }
3420     for(i = 0; i < rcs->nbranches; i++)
3421     move_branch(rcs->branches[i], -xx, -yy);
3422     }
3423    
3424     /* Move everything w.r.t. the top-left margin */
3425     for(i = 0; i < rcs->nbranches; i++)
3426     move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
3427 bertho 1.1 }
3428    
3429     /*
3430     **************************************************************************
3431 bertho 1.6 * Imagemap functions
3432     **************************************************************************
3433     */
3434 bertho 1.44 static void map_merge_box(rcsfile_t *rcs, FILE *fp, revision_t *fr, revision_t *tr, gdImagePtr im, int x1, int y1, int x2, int y2)
3435     {
3436     char *href = expand_string(conf.map_merge_href, rcs, tr, tr->rev, fr->rev, NULL);
3437     char *alt = expand_string(conf.map_merge_alt, rcs, tr, tr->rev, fr->rev, NULL);
3438     const char *htp = conf.html_level == HTMLLEVEL_X ? " /" : "";
3439    
3440     if(x1 > 0 && x2 > 0 && y1 > 0 && y2 > 0)
3441     fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3442     href, x1, y1, x2, y2, alt, htp);
3443     xfree(alt);
3444     xfree(href);
3445    
3446     if(im)
3447     {
3448 bertho 1.59 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, clr(im, "title_color", NULL, NULL, 0)->id);
3449     gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, clr(im, "tag_color.id", NULL, NULL, 0)->id);
3450     gdImageLine(im, x1, y1, x2, y2, clr(im, "title_color", NULL, NULL, 0)->id);
3451 bertho 1.44 }
3452     }
3453    
3454     static void map_merges(rcsfile_t *rcs, FILE *fp, gdImagePtr im)
3455     {
3456     int i;
3457     int tagh2 = get_sheight("Hg", &conf.tag_font) / 2;
3458     int bm = conf.branch_margin / 2;
3459    
3460     for(i = 0; i < rcs->nmerges; i++)
3461     {
3462 bertho 1.49 revision_t *fr;
3463     revision_t *tr;
3464 bertho 1.64 int x1, x2, y1, y2; /* Edge position on revision box */
3465     int sx1, sx2, sy1, sy2; /* Direction for mergeline -1 = left/up, +1 = right/down */
3466 bertho 1.49 switch(rcs->merges[i].type)
3467     {
3468     case TR_TAG:
3469     fr = rcs->merges[i].from.tag->logrev;
3470     tr = rcs->merges[i].to.tag->logrev;
3471     break;
3472     case TR_REVISION:
3473     fr = rcs->merges[i].from.rev;
3474     tr = rcs->merges[i].to.rev;
3475     break;
3476     default:
3477     continue;
3478     }
3479 bertho 1.44 if(!fr || !tr || fr == tr)
3480     continue; /* This can happen with detached tags and self-references */
3481    
3482 bertho 1.64 calc_merge_coords(&rcs->merges[i], fr, tr, &x1, &x2, &y1, &y2, &sx1, &sx2, &sy1, &sy2);
3483    
3484     if(conf.left_right && !conf.merge_on_tag)
3485 bertho 1.44 {
3486     if(fr->branch == tr->branch)
3487     {
3488     map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-bm, x1+bm, y1);
3489     map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-bm, x2+bm, y2);
3490     }
3491     else
3492     {
3493 bertho 1.64 if(sy1 < 0)
3494 bertho 1.44 map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-bm, x1+bm, y1);
3495     else
3496     map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1, x1+bm, y1+bm);
3497 bertho 1.64 if(sy2 < 0)
3498 bertho 1.44 map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-bm, x2+bm, y2);
3499 bertho 1.64 else
3500     map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2, x2+bm, y2+bm);
3501 bertho 1.44 }
3502     }
3503     else
3504     {
3505     if(fr->branch == tr->branch)
3506     {
3507     map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-tagh2, x1, y1+tagh2);
3508     map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-tagh2, x2, y2+tagh2);
3509     }
3510     else
3511     {
3512 bertho 1.64 if(sx1 < 0)
3513 bertho 1.44 map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-tagh2, x1, y1+tagh2);
3514     else
3515     map_merge_box(rcs, fp, fr, tr, im, x1, y1-tagh2, x1+bm, y1+tagh2);
3516 bertho 1.64 if(sx2 < 0)
3517 bertho 1.44 map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-tagh2, x2, y2+tagh2);
3518 bertho 1.64 else
3519     map_merge_box(rcs, fp, fr, tr, im, x2, y2-tagh2, x2+bm, y2+tagh2);
3520 bertho 1.44 }
3521     }
3522     }
3523     }
3524    
3525     static void make_imagemap(rcsfile_t *rcs, FILE *fp, gdImagePtr im)
3526 bertho 1.6 {
3527     int i, j;
3528 bertho 1.29 const char *htp = conf.html_level == HTMLLEVEL_X ? " /" : "";
3529    
3530     switch(conf.html_level)
3531     {
3532     case HTMLLEVEL_4:
3533     fprintf(fp, "<map name=\"%s\" id=\"%s\">\n", conf.map_name, conf.map_name);
3534     break;
3535     case HTMLLEVEL_X:
3536     fprintf(fp, "<map id=\"%s\">\n", conf.map_name);
3537     break;
3538     default:
3539     fprintf(fp, "<map name=\"%s\">\n", conf.map_name);
3540     }
3541    
3542 bertho 1.6 for(i = 0; i < rcs->nbranches; i++)
3543     {
3544     branch_t *b = rcs->branches[i];
3545 bertho 1.8 tag_t *tag = b->ntags ? b->tags[0] : NULL;
3546 bertho 1.44 char *bhref;
3547     char *balt;
3548 bertho 1.26 int x1;
3549     int x2;
3550     int y1;
3551     int y2;
3552    
3553 bertho 1.44 if(subtree_branch && !b->subtree_draw)
3554     continue;
3555    
3556     bhref = expand_string(conf.map_branch_href, rcs, NULL, b->branch, NULL, tag);
3557     balt = expand_string(conf.map_branch_alt, rcs, NULL, b->branch, NULL, tag);
3558    
3559 bertho 1.34 if(!b->nfolds)
3560 bertho 1.26 {
3561 bertho 1.34 if(conf.left_right)
3562     {
3563     x1 = b->cx;
3564     y1 = b->y - b->h/2;
3565     x2 = b->cx + b->w;
3566     y2 = b->y + b->h/2;
3567     }
3568     else
3569     {
3570     x1 = b->cx - b->w/2;
3571     y1 = b->y;
3572     x2 = b->cx + b->w/2;
3573     y2 = b->y + b->h;
3574     }
3575     fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3576     bhref, x1, y1, x2, y2, balt, htp);
3577     if(im)
3578     {
3579 bertho 1.59 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, clr(im, "title_color", NULL, NULL, 0)->id);
3580     gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, clr(im, "tag_color", NULL, NULL, 0)->id);
3581     gdImageLine(im, x1, y1, x2, y2, clr(im, "title_color", NULL, NULL, 0)->id);
3582 bertho 1.34 }
3583 bertho 1.26 }
3584     else
3585     {
3586 bertho 1.34 int yy1, yy2, yy;
3587     if(conf.left_right)
3588     {
3589     x1 = b->cx + conf.branch_lspace;
3590     y1 = b->y - b->h/2 + conf.branch_tspace;
3591     }
3592     else
3593     {
3594     x1 = b->cx - b->w/2 + conf.branch_lspace;
3595     y1 = b->y + conf.branch_tspace;
3596     }
3597     x2 = x1 + b->w - conf.branch_rspace;
3598    
3599     yy1 = get_sheight(b->branch->branch, &conf.branch_font);
3600     yy2 = get_sheight(b->tags[0]->tag, &conf.branch_tag_font);
3601     yy = MAX(yy1, yy2);
3602     y2 = y1 + yy;
3603     fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3604     bhref, x1, y1, x2, y2, balt, htp);
3605    
3606     y1 += yy;
3607     y2 += yy;
3608     for(j = 0; j < b->nfolds; j++)
3609     {
3610     branch_t *fb = b->folds[j];
3611     tag_t *t = fb->tags[0];
3612     xfree(bhref);
3613     xfree(balt);
3614     bhref = expand_string(conf.map_branch_href, rcs, NULL, fb->branch, NULL, t);
3615     balt = expand_string(conf.map_branch_alt, rcs, NULL, fb->branch, NULL, t);
3616     fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3617     bhref, x1, y1, x2, y2, balt, htp);
3618     yy1 = get_sheight(fb->branch->branch, &conf.branch_font);
3619     yy2 = get_sheight(fb->tags[0]->tag, &conf.branch_tag_font);
3620     yy = MAX(yy1, yy2);
3621     y1 += yy;
3622     y2 += yy;
3623     }
3624 bertho 1.26 }
3625 bertho 1.34
3626 bertho 1.6 for(j = 0; j < b->nrevs; j++)
3627     {
3628     revision_t *r = b->revs[j];
3629 bertho 1.13 revision_t* r1;
3630 bertho 1.26 int xoff = 1;
3631     int yoff = 1;
3632 bertho 1.16 char *href;
3633     char *alt;
3634 bertho 1.13
3635 bertho 1.8 tag = r->ntags ? r->tags[0] : NULL;
3636 bertho 1.9 href = expand_string(conf.map_rev_href, rcs, r, r->rev, NULL, tag);
3637     alt = expand_string(conf.map_rev_alt, rcs, r, r->rev, NULL, tag);
3638 bertho 1.26 if(conf.left_right)
3639     {
3640     x1 = r->cx;
3641     y1 = r->y - r->h/2;
3642     x2 = r->cx + r->w;
3643     y2 = r->y + r->h/2;
3644     }
3645     else
3646     {
3647     x1 = r->cx - r->w/2;
3648     y1 = r->y;
3649     x2 = r->cx + r->w/2;
3650     y2 = r->y + r->h;
3651     }
3652 bertho 1.29 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3653     href, x1, y1, x2, y2, alt, htp);
3654 bertho 1.26 if(im)
3655     {
3656 bertho 1.59 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, clr(im, "title_color", NULL, NULL, 0)->id);
3657     gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, clr(im, "tag_color", NULL, NULL, 0)->id);
3658     gdImageLine(im, x1, y1, x2, y2, clr(im, "title_color", NULL, NULL, 0)->id);
3659 bertho 1.26 }
3660 bertho 1.8 xfree(href);
3661     xfree(alt);
3662 bertho 1.16 if(j > 0 || b->branchpoint)
3663 bertho 1.9 {
3664 bertho 1.16 if(j > 0)
3665     {
3666     r1 = b->revs[j-1];
3667 bertho 1.26 if(conf.left_right)
3668     {
3669     yoff = MIN(r->h, r1->h)/4;
3670     x1 = conf.upside_down ? r1->cx : r1->cx + r1->w;
3671     }
3672     else
3673     {
3674     xoff = MIN(r->w, r1->w)/4;
3675     y1 = conf.upside_down ? r1->y : r1->y + r1->h;
3676     }
3677 bertho 1.16 }
3678     else
3679     {
3680     r1 = b->branchpoint;
3681 bertho 1.26 if(conf.left_right)
3682     {
3683     yoff = MIN(r->h, b->h)/4;
3684     x1 = conf.upside_down ? b->cx : b->cx + b->w;
3685     }
3686     else
3687     {
3688     xoff = MIN(r->w, b->w)/4;
3689     y1 = conf.upside_down ? b->y : b->y + b->h;
3690     }
3691     }
3692     if(conf.left_right)
3693     {
3694     y1 = r->y - yoff;
3695     y2 = r->y + yoff;
3696     x2 = conf.upside_down ? r->cx + r->w : r->cx;
3697     yoff = 0;
3698     }
3699     else
3700     {
3701     x1 = r->cx - xoff;
3702     x2 = r->cx + xoff;
3703     y2 = conf.upside_down ? r->y + r->h : r->y;
3704     xoff = 0;
3705     }
3706     if(x1 > x2)
3707     {
3708     int tt = x1;
3709     x1 = x2;
3710     x2 = tt;
3711     }
3712     if(y1 > y2)
3713     {
3714     int tt = y1;
3715     y1 = y2;
3716     y2 = tt;
3717 bertho 1.16 }
3718     href = expand_string(conf.map_diff_href, rcs, r, r->rev, r1->rev, tag);
3719     alt = expand_string(conf.map_diff_alt, rcs, r, r->rev, r1->rev, tag);
3720 bertho 1.29 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3721 bertho 1.16 href,
3722 bertho 1.26 x1+xoff, y1+yoff, x2-xoff, y2-yoff,
3723 bertho 1.29 alt, htp);
3724 bertho 1.26 if(im)
3725     {
3726 bertho 1.59 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, clr(im, "title_color", NULL, NULL, 0)->id);
3727     gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, clr(im, "tag_color", NULL, NULL, 0)->id);
3728     gdImageLine(im, x1, y1, x2, y2, clr(im, "title_color", NULL, NULL, 0)->id);
3729 bertho 1.26 }
3730 bertho 1.16 xfree(href);
3731     xfree(alt);
3732 bertho 1.9 }
3733 bertho 1.6 }
3734 bertho 1.32 if(conf.branch_dupbox && b->nrevs)
3735 bertho 1.16 {
3736 bertho 1.26 if(conf.left_right)
3737     {
3738     x1 = conf.upside_down ? b->cx + b->w - b->tw : b->cx - b->w + b->tw;
3739     y1 = b->y - b->h/2;
3740     x2 = x1 + b->w;
3741     y2 = b->y + b->h/2;
3742     }
3743 bertho 1.16 else
3744 bertho 1.26 {
3745     x1 = b->cx - b->w/2;
3746     y1 = conf.upside_down ? b->y + b->h - b->th : b->y - b->h + b->th;
3747     x2 = b->cx + b->w/2;
3748     y2 = y1 + b->h;
3749     }
3750 bertho 1.29 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3751     bhref, x1, y1, x2, y2, balt, htp);
3752 bertho 1.26 if(im)
3753     {
3754 bertho 1.59 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, clr(im, "title_color", NULL, NULL, 0)->id);
3755     gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, clr(im, "tag_color", NULL, NULL, 0)->id);
3756     gdImageLine(im, x1, y1, x2, y2, clr(im, "title_color", NULL, NULL, 0)->id);
3757 bertho 1.26 }
3758 bertho 1.16 }
3759     xfree(bhref);
3760     xfree(balt);
3761 bertho 1.6 }
3762 bertho 1.44
3763     map_merges(rcs, fp, im);
3764    
3765 bertho 1.6 fprintf(fp, "</map>\n");
3766     }
3767    
3768     /*
3769     **************************************************************************
3770 bertho 1.1 * Program entry
3771     **************************************************************************
3772     */
3773     static const char usage_str[] =
3774     "Usage: cvsgraph [options] <file>\n"
3775 bertho 1.16 " -b Add a branch box at both sides of the trunk (config value is negated)\n"
3776 bertho 1.8 " -c <file> Read alternative config from <file>\n"
3777     " -d <level> Enable debug mode at <level>\n"
3778     " -h This message\n"
3779     " -i Generate an imagemap instead of image\n"
3780 bertho 1.18 " -I <file> Also write the imagemap to <file>\n"
3781 bertho 1.19 " -k Auto stretch the tree (config value is negated)\n"
3782 bertho 1.8 " -M <name> Use <name> as imagemap name\n"
3783     " -m <mod> Use <mod> as cvs module\n"
3784     " -o <file> Output to <file>\n"
3785 bertho 1.18 " -O <opt=val> Set option opt to value val\n"
3786 bertho 1.8 " -q Be quiet (i.e. no warnings)\n"
3787     " -r <path> Use <path> as cvsroot path\n"
3788 bertho 1.16 " -s Strip untagged revisions (config value is negated)\n"
3789 bertho 1.18 " -S Also strip the first revision (config value is negated)\n"
3790 bertho 1.16 " -u Upside down image (mirror vertically; config value is negated)\n"
3791 bertho 1.8 " -V Print version and exit\n"
3792 bertho 1.29 " -x [34x] Specify level of HTML 3.2 (default), 4.0 or XHTML\n"
3793 bertho 1.8 " -[0-9] <txt> Use <txt> for expansion\n"
3794 bertho 1.1 ;
3795    
3796 bertho 1.63 #define VERSION_STR "1.6.2"
3797 bertho 1.64 #define NOTICE_STR "Copyright (c) 2001-2008 B.Stultiens"
3798 bertho 1.1
3799 bertho 1.19 static void append_slash(char **path)
3800 bertho 1.9 {
3801     int l;
3802     assert(path != NULL);
3803     assert(*path != NULL);
3804     l = strlen(*path);
3805     if(!l || (*path)[l-1] == '/')
3806     return;
3807     *path = xrealloc(*path, l+2);
3808     strcat(*path, "/");
3809     }
3810    
3811 bertho 1.1 int main(int argc, char *argv[])
3812     {
3813 bertho 1.7 extern int rcs_flex_debug;
3814     extern int rcsdebug;
3815 bertho 1.1 int optc;
3816     char *confpath = NULL;
3817     char *outfile = NULL;
3818     char *cvsroot = NULL;
3819     char *cvsmodule = NULL;
3820 bertho 1.7 int imagemap = 0;
3821 bertho 1.16 int upsidedown = 0;
3822 bertho 1.17 int bdupbox = 0;
3823 bertho 1.16 int stripuntag = 0;
3824 bertho 1.18 int stripfirst = 0;
3825 bertho 1.19 int autostretch = 0;
3826 bertho 1.29 int htmllevel = 0;
3827 bertho 1.7 char *imgmapname = NULL;
3828 bertho 1.18 char *imgmapfile = NULL;
3829 bertho 1.1 int lose = 0;
3830     FILE *fp;
3831 bertho 1.18 char *rcsfilename;
3832 bertho 1.7 rcsfile_t *rcs;
3833 bertho 1.1 gdImagePtr im;
3834    
3835 bertho 1.29 while((optc = getopt(argc, argv, "0:1:2:3:4:5:6:7:8:9:bc:d:hI:ikM:m:O:o:qr:SsuVx:")) != EOF)
3836 bertho 1.1 {
3837     switch(optc)
3838     {
3839 bertho 1.16 case 'b':
3840 bertho 1.17 bdupbox = 1;
3841 bertho 1.16 break;
3842 bertho 1.1 case 'c':
3843     confpath = xstrdup(optarg);
3844     break;
3845 bertho 1.7 case 'd':
3846     debuglevel = strtol(optarg, NULL, 0);
3847     break;
3848 bertho 1.18 case 'I':
3849     imgmapfile = xstrdup(optarg);
3850     break;
3851 bertho 1.6 case 'i':
3852 bertho 1.7 imagemap = 1;
3853     break;
3854 bertho 1.18 case 'k':
3855 bertho 1.19 autostretch = 1;
3856 bertho 1.18 break;
3857 bertho 1.7 case 'M':
3858     imgmapname = xstrdup(optarg);
3859 bertho 1.6 break;
3860 bertho 1.1 case 'm':
3861     cvsmodule = xstrdup(optarg);
3862     break;
3863 bertho 1.18 case 'O':
3864     stack_option(optarg);
3865     break;
3866 bertho 1.1 case 'o':
3867     outfile = xstrdup(optarg);
3868     break;
3869 bertho 1.7 case 'q':
3870     quiet = 1;
3871     break;
3872 bertho 1.1 case 'r':
3873     cvsroot = xstrdup(optarg);
3874     break;
3875 bertho 1.18 case 'S':
3876     stripfirst = 1;
3877     break;
3878 bertho 1.16 case 's':
3879     stripuntag = 1;
3880     break;
3881     case 'u':
3882     upsidedown = 1;
3883     break;
3884 bertho 1.1 case 'V':
3885     fprintf(stdout, "cvsgraph v%s, %s\n", VERSION_STR, NOTICE_STR);
3886     return 0;
3887 bertho 1.29 case 'x':
3888     switch(optarg[0])
3889     {
3890     case '3':
3891     htmllevel = HTMLLEVEL_3;
3892     break;
3893     case '4':
3894     htmllevel = HTMLLEVEL_4;
3895     break;
3896     case 'x':
3897     htmllevel = HTMLLEVEL_X;
3898     break;
3899     default:
3900     fprintf(stderr, "Invalid HTML level in -x\n");
3901     lose++;
3902     }
3903     break;
3904 bertho 1.1 case 'h':
3905     fprintf(stdout, "%s", usage_str);
3906     return 0;
3907     default:
3908 bertho 1.8 if(isdigit(optc))
3909     {
3910     conf.expand[optc-'0'] = xstrdup(optarg);
3911     }
3912     else
3913     lose++;
3914 bertho 1.1 }
3915     }
3916    
3917     if(lose)
3918     {
3919     fprintf(stderr, "%s", usage_str);
3920     return 1;
3921     }
3922    
3923 bertho 1.7 if(debuglevel)
3924     {
3925     setvbuf(stdout, NULL, 0, _IONBF);
3926     setvbuf(stderr, NULL, 0, _IONBF);
3927     }
3928     rcs_flex_debug = (debuglevel & DEBUG_RCS_LEX) != 0;
3929     rcsdebug = (debuglevel & DEBUG_RCS_YACC) != 0;
3930    
3931 bertho 1.1 /* Set defaults */
3932 bertho 1.21 conf.tag_font.gdfont = gdFontTiny;
3933     conf.rev_font.gdfont = gdFontTiny;
3934     conf.branch_font.gdfont = gdFontTiny;
3935     conf.branch_tag_font.gdfont = gdFontTiny;
3936     conf.title_font.gdfont = gdFontTiny;
3937     conf.rev_text_font.gdfont = gdFontTiny;
3938 bertho 1.44 conf.msg_font.gdfont = gdFontTiny;
3939 bertho 1.21
3940 bertho 1.19 conf.anti_alias = 1;
3941 bertho 1.20 conf.thick_lines = 1;
3942 bertho 1.32 conf.branch_fold = 1;
3943 bertho 1.9
3944     conf.cvsroot = xstrdup("");
3945     conf.cvsmodule = xstrdup("");
3946     conf.date_format = xstrdup("%d-%b-%Y %H:%M:%S");
3947     conf.title = xstrdup("");
3948     conf.map_name = xstrdup("CvsGraphImageMap");
3949     conf.map_branch_href = xstrdup("href=\"unset: conf.map_branch_href\"");
3950     conf.map_branch_alt = xstrdup("alt=\"%B\"");
3951     conf.map_rev_href = xstrdup("href=\"unset: conf.map_rev_href\"");
3952     conf.map_rev_alt = xstrdup("alt=\"%R\"");
3953     conf.map_diff_href = xstrdup("href=\"unset: conf.map_diff_href\"");
3954 bertho 1.12 conf.map_diff_alt = xstrdup("alt=\"%P &lt;-&gt; %R\"");
3955 bertho 1.44 conf.map_merge_href = xstrdup("href=\"unset: conf.map_merge_href\"");
3956     conf.map_merge_alt = xstrdup("alt=\"%P &lt;-&gt; %R\"");
3957 bertho 1.60 conf.rev_text.str = xstrdup("%d");
3958     conf.rev_idtext.str = xstrdup("%R");
3959 bertho 1.44 conf.branch_subtree = xstrdup("");
3960     conf.tag_ignore = xstrdup("");
3961 bertho 1.59 conf.merge_from.n = 0;
3962     conf.merge_from.strs = NULL;
3963     conf.merge_to.n = 0;
39