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

Diff of /cvsgraph/cvsgraph.c

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

revision 1.13, Tue Nov 20 15:00:06 2001 UTC revision 1.44, Sun Aug 15 16:58:22 2004 UTC
# Line 2  Line 2 
2   * CvsGraph graphical representation generator of brances and revisions   * CvsGraph graphical representation generator of brances and revisions
3   * of a file in cvs/rcs.   * of a file in cvs/rcs.
4   *   *
5   * Copyright (C) 2001  B. Stultiens   * Copyright (C) 2001,2002,2003  B. Stultiens
6   *   *
7   * This program is free software; you can redistribute it and/or modify   * 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   * it under the terms of the GNU General Public License as published by
# Line 19  Line 19 
19   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20   */   */
21    
22    #include "config.h"
23    
24  #include <stdio.h>  #include <stdio.h>
25  #include <stdlib.h>  #include <stdlib.h>
26    #include <stdarg.h>
27  #include <unistd.h>  #include <unistd.h>
28  #include <string.h>  #include <string.h>
29  #include <assert.h>  #include <assert.h>
# Line 30  Line 33 
33  #include <fcntl.h>  #include <fcntl.h>
34  #include <regex.h>  #include <regex.h>
35  #include <errno.h>  #include <errno.h>
 #include <getopt.h>  
36  #include <ctype.h>  #include <ctype.h>
37  #include <time.h>  #include <time.h>
38    #include <limits.h>
39    #include <regex.h>
40    #include <math.h>
41    
42    #ifdef HAVE_GETOPT_H
43    # include <getopt.h>
44    #endif
45    
46  #include <gd.h>  #include <gd.h>
47  #include <gdfontt.h>  #include <gdfontt.h>
48    
 #include "config.h"  
49  #include "cvsgraph.h"  #include "cvsgraph.h"
50  #include "utils.h"  #include "utils.h"
51  #include "readconf.h"  #include "readconf.h"
# Line 49  Line 57 
57    
58    
59  /*#define DEBUG         1*/  /*#define DEBUG         1*/
60    /*#define NOGDFILL      1*/
61    /*#define DEBUG_IMAGEMAP        1*/
62    
63  #define CONFFILENAME    "cvsgraph.conf"  #define LOOPSAFEGUARD   10000   /* Max itterations in possible infinite loops */
   
 #ifndef ETCDIR  
 # define ETCDIR         "/usr/local/etc"  
 #endif  
64    
65  #ifndef MAX  #ifndef MAX
66  # define MAX(a,b)       ((a) > (b) ? (a) : (b))  # define MAX(a,b)       ((a) > (b) ? (a) : (b))
# Line 73  Line 79 
79  #define ALIGN_VB        0x20  #define ALIGN_VB        0x20
80  #define ALIGN_VX        0xf0  #define ALIGN_VX        0xf0
81    
82    #ifndef M_PI    /* math.h should have defined this */
83    # define M_PI 3.14159265358979323846
84    #endif
85    #define ROUND(f)        ((f >= 0.0)?((int)(f + 0.5)):((int)(f - 0.5)))
86    
87    #define ARROW_LENGTH    12      /* Default arrow dimensions */
88    #define ARROW_WIDTH     3
89    
90  /*  /*
91   **************************************************************************   **************************************************************************
92   * Globals   * Globals
# Line 81  Line 95 
95    
96  config_t conf;  config_t conf;
97  int debuglevel;  int debuglevel;
 color_t white_color = {255, 255, 255, 0};  
 color_t black_color = {0, 0, 0, 0};  
98    
99    static color_t white_color = {255, 255, 255, 0};
100    static color_t black_color = {0, 0, 0, 0};
101    
102    static branch_t *subtree_branch = NULL;         /* Set to the (first) subtree branch that we want to show */
103    static revision_t *subtree_rev = NULL;          /* Set to the subtree revision which branches we want to show */
104    
105    static msg_stack_t *msg_stack = NULL;           /* Messages that would otherwise be sent to stderr goto the image */
106    static int nmsg_stack = 0;
107    
108    /*
109     **************************************************************************
110     * Forwards
111     **************************************************************************
112     */
113    static void zap_string(void);
114    static char *dup_string(void);
115    static void add_string_str(const char *s);
116    static void add_string_ch(int ch);
117    static void add_string_date(const char *d);
118    static void add_string_str_html(const char *s, int maxlen);
119    static void add_string_str_len(const char *s, int maxlen);
120    
121    static void calc_subtree_size(branch_t *b, int *x, int *y, int *w, int *h);
122    
123  /*  /*
124   **************************************************************************   **************************************************************************
125   * Dubug routines   * Debug routines
126   **************************************************************************   **************************************************************************
127   */   */
128  void dump_rev(char *p, rev_t *r)  static void dump_rev(char *p, rev_t *r)
129  {  {
130          printf("%s", p);          printf("%s", p);
131          if(r)          if(r)
# Line 99  Line 134 
134                  printf("<null>\n");                  printf("<null>\n");
135  }  }
136    
137  void dump_id(char *p, char *d)  static void dump_id(char *p, char *d)
138  {  {
139          printf("%s", p);          printf("%s", p);
140          if(d)          if(d)
# Line 108  Line 143 
143                  printf("<null>\n");                  printf("<null>\n");
144  }  }
145    
146  void dump_idrev(char *p, idrev_t *t)  static void dump_idrev(char *p, idrev_t *t)
147  {  {
148          printf("%s", p);          printf("%s", p);
149          if(t)          if(t)
# Line 120  Line 155 
155                  printf("<null>\n");                  printf("<null>\n");
156  }  }
157    
158  void dump_tag(char *p, tag_t *t)  static void dump_tag(char *p, tag_t *t)
159  {  {
160          printf("%s", p);          printf("%s", p);
161          if(t)          if(t)
# Line 132  Line 167 
167                  printf("<null>\n");                  printf("<null>\n");
168  }  }
169    
170  void dump_delta(char *p, delta_t *d)  static void dump_delta(char *p, delta_t *d)
171  {  {
172          int i;          int i;
173          printf("%sdelta.rev   : ", p);          printf("%sdelta.rev   : ", p);
# Line 150  Line 185 
185          printf("\n");          printf("\n");
186  }  }
187    
188  void dump_dtext(char *p, dtext_t *d)  static void dump_dtext(char *p, dtext_t *d)
189  {  {
190          printf("%sdtext.rev  : ", p);          printf("%sdtext.rev  : ", p);
191          dump_rev("", d->rev);          dump_rev("", d->rev);
# Line 159  Line 194 
194          printf("\n");          printf("\n");
195  }  }
196    
197  void dump_rcsfile(rcsfile_t *rcs)  static void dump_rcsfile(rcsfile_t *rcs)
198  {  {
199          int i;          int i;
200          printf("root   : '%s'\n", rcs->root);          printf("root   : '%s'\n", rcs->root);
# Line 191  Line 226 
226    
227  /*  /*
228   **************************************************************************   **************************************************************************
229     * Error/Warning Message helpers
230     **************************************************************************
231     */
232    #define MSGBUFSIZE      256
233    void stack_msg(int severity, const char *fmt, ...)
234    {
235            va_list va;
236            int i;
237            char *buf = xmalloc(MSGBUFSIZE);
238            switch(severity)
239            {
240            case MSG_WARN:  sprintf(buf, "Warning: "); break;
241            case MSG_ERR:   sprintf(buf, "Error: "); break;
242            default:        sprintf(buf, "Unqualified error: "); break;
243            }
244            i = strlen(buf);
245            assert(i < MSGBUFSIZE);
246            va_start(va, fmt);
247            vsnprintf(buf+i, MSGBUFSIZE-i, fmt, va);
248            va_end(va);
249            if(!msg_stack)
250                    msg_stack = xmalloc(sizeof(*msg_stack));
251            else
252            {
253                    msg_stack = xrealloc(msg_stack, (nmsg_stack+1)*sizeof(*msg_stack));
254            }
255            msg_stack[nmsg_stack].msg = buf;
256            msg_stack[nmsg_stack].severity = severity;
257            nmsg_stack++;
258    }
259    
260    /*
261     **************************************************************************
262   * Read the rcs file   * Read the rcs file
263   **************************************************************************   **************************************************************************
264   */   */
265  rcsfile_t *get_rcsfile(const char *cvsroot, const char *module, const char *file)  static rcsfile_t *get_rcsfile(const char *cvsroot, const char *module, const char *file)
266  {  {
267          char *cmd = NULL;          char *cmd = NULL;
268          int rv;          int rv;
269    
270          cmd = xmalloc(strlen(cvsroot) + strlen(module) + strlen(file) + 1);          if(file)
         sprintf(cmd, "%s%s%s", cvsroot, module, file);  
         if(!(rcsin = fopen(cmd, "r")))  
271          {          {
272                  perror(cmd);                  cmd = xmalloc(strlen(cvsroot) + strlen(module) + strlen(file) + 1);
273                  return NULL;                  sprintf(cmd, "%s%s%s", cvsroot, module, file);
274                    if(!(rcsin = fopen(cmd, "rb")))
275                    {
276                            perror(cmd);
277                            return NULL;
278                    }
279                    input_file = cmd;
280            }
281            else
282            {
283                    rcsin = stdin;
284                    input_file = "<stdin>";
285          }          }
         input_file = cmd;  
286          line_number = 1;          line_number = 1;
287          rv = rcsparse();          rv = rcsparse();
288          fclose(rcsin);          if(file)
289            {
290                    fclose(rcsin);
291                    xfree(cmd);
292            }
293          if(rv)          if(rv)
294                  return NULL;                  return NULL;
         xfree(cmd);  
295          input_file = NULL;          input_file = NULL;
296          rcsfile->root = xstrdup(cvsroot);          if(file)
297          rcsfile->module = xstrdup(module);          {
298          rcsfile->file = xstrdup(file);                  rcsfile->root = xstrdup(cvsroot);
299                    rcsfile->module = xstrdup(module);
300                    rcsfile->file = xstrdup(file);
301            }
302            else
303            {
304                    rcsfile->root = xstrdup("");
305                    rcsfile->module = xstrdup("");
306                    rcsfile->file = xstrdup("<stdin>");
307            }
308          return rcsfile;          return rcsfile;
309  }  }
310    
# Line 225  Line 313 
313   * Sort and find helpers   * Sort and find helpers
314   **************************************************************************   **************************************************************************
315   */   */
316  int count_dots(const char *s)  static int count_dots(const char *s)
317  {  {
318          int i;          int i;
319          for(i = 0; *s; s++)          for(i = 0; *s; s++)
# Line 236  Line 324 
324          return i;          return i;
325  }  }
326    
327  int compare_rev(int bcmp, const rev_t *r1, const rev_t *r2)  static int compare_rev(int bcmp, const rev_t *r1, const rev_t *r2)
328  {  {
329          int d1, d2;          int d1, d2;
330          char *c1, *c2;          char *c1, *c2;
# Line 309  Line 397 
397   * the bounding boxes of each branch. The revisions expand vertically   * the bounding boxes of each branch. The revisions expand vertically
398   * and the branches expand horizontally.   * and the branches expand horizontally.
399   * The reorganisation is performed in these steps:   * The reorganisation is performed in these steps:
400   * 1 - sort deltas and detla text on revision number for quick lookup   * 1 - sort deltas and delta text on revision number for quick lookup
401   * 2 - start at the denoted head revision:   * 2 - start at the denoted head revision:
402   *      * create a branch structure and add this revision   *      * create a branch structure and add this revision
403   *      * for each 'branches' in the delta do:   *      * for each 'branches' in the delta do:
# Line 322  Line 410 
410   * 3 - update the administration   * 3 - update the administration
411   **************************************************************************   **************************************************************************
412   */   */
413  int sort_delta(const void *d1, const void *d2)  static int sort_delta(const void *d1, const void *d2)
414  {  {
415          return compare_rev(0, (*(delta_t **)d1)->rev, (*(delta_t **)d2)->rev);          return compare_rev(0, (*(delta_t **)d1)->rev, (*(delta_t **)d2)->rev);
416  }  }
417    
418  int search_delta(const void *r, const void *d)  static int search_delta(const void *r, const void *d)
419  {  {
420          return compare_rev(0, (rev_t *)r, (*(delta_t **)d)->rev);          return compare_rev(0, (rev_t *)r, (*(delta_t **)d)->rev);
421  }  }
422    
423  delta_t *find_delta(delta_t **dl, int n, rev_t *r)  static delta_t *find_delta(delta_t **dl, int n, rev_t *r)
424  {  {
425          delta_t **d;          delta_t **d;
426            if(!n)
427                    return NULL;
428          d = bsearch(r, dl, n, sizeof(*dl), search_delta);          d = bsearch(r, dl, n, sizeof(*dl), search_delta);
429          if(!d)          if(!d)
430                  return NULL;                  return NULL;
431          return *d;          return *d;
432  }  }
433    
434  int sort_dtext(const void *d1, const void *d2)  static int sort_dtext(const void *d1, const void *d2)
435  {  {
436          return compare_rev(0, (*(dtext_t **)d1)->rev, (*(dtext_t **)d2)->rev);          return compare_rev(0, (*(dtext_t **)d1)->rev, (*(dtext_t **)d2)->rev);
437  }  }
438    
439  int search_dtext(const void *r, const void *d)  static int search_dtext(const void *r, const void *d)
440  {  {
441          return compare_rev(0, (rev_t *)r, (*(dtext_t **)d)->rev);          return compare_rev(0, (rev_t *)r, (*(dtext_t **)d)->rev);
442  }  }
443    
444  dtext_t *find_dtext(dtext_t **dl, int n, rev_t *r)  static dtext_t *find_dtext(dtext_t **dl, int n, rev_t *r)
445  {  {
446          dtext_t **d;          dtext_t **d;
447            if(!n)
448                    return NULL;
449          d = bsearch(r, dl, n, sizeof(*dl), search_dtext);          d = bsearch(r, dl, n, sizeof(*dl), search_dtext);
450          if(!d)          if(!d)
451                  return NULL;                  return NULL;
452          return *d;          return *d;
453  }  }
454    
455  rev_t *dup_rev(const rev_t *r)  static rev_t *dup_rev(const rev_t *r)
456  {  {
457          rev_t *t = xmalloc(sizeof(*t));          rev_t *t = xmalloc(sizeof(*t));
458          t->rev = xstrdup(r->rev);          t->rev = xstrdup(r->rev);
# Line 369  Line 461 
461          return t;          return t;
462  }  }
463    
464  branch_t *new_branch(delta_t *d, dtext_t *t)  static branch_t *new_branch(delta_t *d, dtext_t *t)
465  {  {
466          branch_t *b = xmalloc(sizeof(*b));          branch_t *b = xmalloc(sizeof(*b));
467          revision_t *r = xmalloc(sizeof(*r));          revision_t *r = xmalloc(sizeof(*r));
# Line 385  Line 477 
477          return b;          return b;
478  }  }
479    
480  revision_t *add_to_branch(branch_t *b, delta_t *d, dtext_t *t)  static revision_t *add_to_branch(branch_t *b, delta_t *d, dtext_t *t)
481  {  {
482          revision_t *r = xmalloc(sizeof(*r));          revision_t *r = xmalloc(sizeof(*r));
483          r->delta = d;          r->delta = d;
# Line 398  Line 490 
490          return r;          return r;
491  }  }
492    
493  void build_branch(branch_t ***bl, int *nbl, delta_t **sdl, int nsdl, dtext_t **sdt, int nsdt, delta_t *head)  static void build_branch(branch_t ***bl, int *nbl, delta_t **sdl, int nsdl, dtext_t **sdt, int nsdt, delta_t *head)
494  {  {
495          branch_t *b;          branch_t *b;
496          dtext_t *text;          dtext_t *text;
# Line 408  Line 500 
500    
501          if(head->flag)          if(head->flag)
502          {          {
503                  fprintf(stderr, "Circular reference on '%s' in branchpoint\n", head->rev->rev);                  stack_msg(MSG_ERR, "Circular reference on '%s' in branchpoint\n", head->rev->rev);
504                  return;                  return;
505          }          }
506          head->flag++;          head->flag++;
# Line 451  Line 543 
543                  head = find_delta(sdl, nsdl, head->next);                  head = find_delta(sdl, nsdl, head->next);
544                  if(!head)                  if(!head)
545                  {                  {
546                          fprintf(stderr, "Next revision (%s) not found in deltalist\n", head->next->rev);                          stack_msg(MSG_ERR, "Next revision (%s) not found in deltalist\n", head->next->rev);
547                          return;                          return;
548                  }                  }
549                  if(head->flag)                  if(head->flag)
550                  {                  {
551                          fprintf(stderr, "Circular reference on '%s'\n", head->rev->rev);                          stack_msg(MSG_ERR, "Circular reference on '%s'\n", head->rev->rev);
552                          return;                          return;
553                  }                  }
554                  head->flag++;                  head->flag++;
# Line 486  Line 578 
578          qsort(sdelta, nsdelta, sizeof(sdelta[0]), sort_delta);          qsort(sdelta, nsdelta, sizeof(sdelta[0]), sort_delta);
579    
580          /* Do the same for the delta text */          /* Do the same for the delta text */
581          nsdtext = rcs->dtexts->ndtexts;          if(rcs->dtexts)
582          sdtext = xmalloc(nsdtext * sizeof(sdtext[0]));          {
583          memcpy(sdtext, rcs->dtexts->dtexts, nsdtext * sizeof(sdtext[0]));                  nsdtext = rcs->dtexts->ndtexts;
584          qsort(sdtext, nsdtext, sizeof(sdtext[0]), sort_dtext);                  sdtext = xmalloc(nsdtext * sizeof(sdtext[0]));
585                    memcpy(sdtext, rcs->dtexts->dtexts, nsdtext * sizeof(sdtext[0]));
586                    qsort(sdtext, nsdtext, sizeof(sdtext[0]), sort_dtext);
587            }
588            else
589            {
590                    nsdtext = 0;
591                    sdtext = NULL;
592            }
593    
594          /* Start from the head of the trunk */          /* Start from the head of the trunk */
595          head = find_delta(sdelta, nsdelta, rcs->head);          head = find_delta(sdelta, nsdelta, rcs->head);
596          if(!head)          if(!head)
597          {          {
598                  fprintf(stderr, "Head revision (%s) not found in deltalist\n", rcs->head->rev);                  stack_msg(MSG_ERR, "Head revision (%s) not found in deltalist\n", rcs->head->rev);
599                  return 0;                  return 0;
600          }          }
601          bl = NULL;          bl = NULL;
# Line 535  Line 635 
635   * revisions and then we assign each tag to the proper revision.   * revisions and then we assign each tag to the proper revision.
636   **************************************************************************   **************************************************************************
637   */   */
638  int sort_revision(const void *r1, const void *r2)  static int sort_revision(const void *r1, const void *r2)
639  {  {
640          return compare_rev(0, (*(revision_t **)r1)->delta->rev, (*(revision_t **)r2)->delta->rev);          return compare_rev(0, (*(revision_t **)r1)->delta->rev, (*(revision_t **)r2)->delta->rev);
641  }  }
642    
643  int search_revision(const void *t, const void *r)  static int search_revision(const void *t, const void *r)
644  {  {
645          return compare_rev(0, (rev_t *)t, (*(revision_t **)r)->delta->rev);          return compare_rev(0, (rev_t *)t, (*(revision_t **)r)->delta->rev);
646  }  }
647    
648  int sort_branch(const void *b1, const void *b2)  static int sort_branch(const void *b1, const void *b2)
649  {  {
650          return compare_rev(1, (*(branch_t **)b1)->branch, (*(branch_t **)b2)->branch);          return compare_rev(1, (*(branch_t **)b1)->branch, (*(branch_t **)b2)->branch);
651  }  }
652    
653  int search_branch(const void *t, const void *b)  static int search_branch(const void *t, const void *b)
654  {  {
655          return compare_rev(1, (rev_t *)t, (*(branch_t **)b)->branch);          return compare_rev(1, (rev_t *)t, (*(branch_t **)b)->branch);
656  }  }
657    
658  char *previous_rev(const char *c)  static char *previous_rev(const char *c)
659  {  {
660          int dots = count_dots(c);          int dots = count_dots(c);
661          char *cptr;          char *cptr;
662          char *r;          char *r;
663          if(!dots)          if(!dots)
664          {          {
665                  fprintf(stderr, "FIXME: previous_rev(\"%s\"): Cannot determine parent branch revision\n", c);                  stack_msg(MSG_ERR, "FIXME: previous_rev(\"%s\"): Cannot determine parent branch revision\n", c);
666                  return xstrdup("1.0");  /* FIXME: don't know what the parent is */                  return xstrdup("1.0");  /* FIXME: don't know what the parent is */
667          }          }
668          if(dots & 1)          if(dots & 1)
# Line 573  Line 673 
673                  assert(cptr != NULL);                  assert(cptr != NULL);
674                  if(dots == 1)                  if(dots == 1)
675                  {                  {
676                          fprintf(stderr, "FIXME: previous_rev(\"%s\"): Going beyond top-level?\n", c);                          stack_msg(MSG_ERR, "FIXME: previous_rev(\"%s\"): Going beyond top-level?\n", c);
677                          /* FIXME: What is the parent of 1.1? */                          /* FIXME: What is the parent of 1.1? */
678                          cptr[1] = '\0';                          cptr[1] = '\0';
679                          strcat(r, "0");                          strcat(r, "0");
# Line 594  Line 694 
694          return r;          return r;
695  }  }
696    
697  int assign_tags(rcsfile_t *rcs)  static char *build_regex(size_t n, regmatch_t *m, const char *ms)
698    {
699            char *cptr;
700            int i;
701    
702            if(!conf.merge_to || !conf.merge_to[0])
703                    return NULL;
704    
705            zap_string();
706            for(cptr = conf.merge_to; *cptr; cptr++)
707            {
708                    if(*cptr == '%')
709                    {
710                            if(cptr[1] >= '1' && cptr[1] <= '9')
711                            {
712                                    int idx = cptr[1] - '0';
713                                    regmatch_t *p = &m[idx];
714                                    if(idx < n && !(p->rm_so == -1 || p->rm_so >= p->rm_eo))
715                                    {
716                                            for(i = p->rm_so; i < p->rm_eo; i++)
717                                            {
718                                                    if(strchr("^$.*+\\[{()", ms[i]))
719                                                            add_string_ch('\\');
720                                                    add_string_ch(ms[i]);
721                                            }
722                                    }
723                                    cptr++;
724                            }
725                            else
726                                    add_string_ch('%');
727                    }
728                    else
729                            add_string_ch(*cptr);
730            }
731            return dup_string();
732    }
733    
734    static void find_merges(rcsfile_t *rcs)
735    {
736            int i;
737            int err;
738            int rcflags = REG_EXTENDED | (conf.merge_nocase ? REG_ICASE : 0);
739            regex_t *refrom = NULL;
740            regex_t *reto = NULL;
741            regmatch_t *matchfrom = NULL;
742    
743            if(!conf.merge_from || !conf.merge_from[0] || !conf.merge_to || !conf.merge_to[0])
744                    return;
745    
746            refrom = xmalloc(sizeof(*refrom));
747            reto = xmalloc(sizeof(*reto));
748    
749            /* Compile the 'from' regex match for merge identification */
750            err = regcomp(refrom, conf.merge_from, rcflags);
751            if(err)
752            {
753                    char *msg;
754                    i = regerror(err, refrom, NULL, 0);
755                    msg = xmalloc(i+1);
756                    regerror(err, refrom, msg, i+1);
757                    stack_msg(MSG_WARN, "%s", msg);
758                    xfree(msg);
759                    xfree(refrom);
760                    xfree(reto);
761                    return;
762            }
763            else
764                    matchfrom = xmalloc((refrom->re_nsub+1) * sizeof(*matchfrom));
765    
766            for(i = 0; i < rcs->tags->ntags; i++)
767            {
768                    tag_t *t = rcs->tags->tags[i];
769    
770                    /* Must be revision tags and not detached */
771                    if(t->rev->isbranch || !t->logrev)
772                            continue;
773    
774                    /* Try to find merge tag matches */
775                    if(!regexec(refrom, t->tag, refrom->re_nsub+1, matchfrom, 0))
776                    {
777                            int n;
778                            char *to;
779    
780                            to = build_regex(refrom->re_nsub+1, matchfrom, t->tag);
781                            if(to)
782                            {
783                                    err = regcomp(reto, to, rcflags);
784                                    if(err)
785                                    {
786                                            char *msg;
787                                            i = regerror(err, reto, NULL, 0);
788                                            msg = xmalloc(i+1);
789                                            regerror(err, reto, msg, i+1);
790                                            stack_msg(MSG_WARN, "%s", msg);
791                                            xfree(msg);
792                                    }
793                                    else if(!err)
794                                    {
795                                            for(n = 0; n < rcs->tags->ntags; n++)
796                                            {
797                                                    tag_t *nt = rcs->tags->tags[n];
798                                                    /* From and To never should match the same tag or belong to a branch */
799                                                    if(n == i || nt->rev->isbranch || !nt->logrev)
800                                                            continue;
801    
802                                                    if(!regexec(reto, nt->tag, 0, NULL, 0))
803                                                    {
804                                                            /* Tag matches */
805                                                            rcs->merges = xrealloc(rcs->merges,
806                                                                            sizeof(rcs->merges[0]) * (rcs->nmerges+1));
807                                                            rcs->merges[rcs->nmerges].to = nt;
808                                                            rcs->merges[rcs->nmerges].from = t;
809                                                            rcs->nmerges++;
810                                                            /* Merges cannot be ignored tags */
811                                                            nt->ignore = 0;
812                                                            t->ignore = 0;
813                                                            /* We cannot (should not) match multiple times */
814                                                            break;
815                                                    }
816                                            }
817                                            regfree(reto);
818                                    }
819                                    xfree(to);
820                            }
821                    }
822            }
823            if(matchfrom)   xfree(matchfrom);
824            if(refrom)      { regfree(refrom); xfree(refrom); }
825            if(reto)        xfree(reto);
826    }
827    
828    static void assign_tags(rcsfile_t *rcs)
829  {  {
830          int i;          int i;
831          int nr;          int nr;
832            regex_t *regextag = NULL;
833    
834            if(conf.tag_ignore && conf.tag_ignore[0])
835            {
836                    int err;
837                    regextag = xmalloc(sizeof(*regextag));
838                    err = regcomp(regextag, conf.tag_ignore, REG_EXTENDED | REG_NOSUB | (conf.tag_nocase ? REG_ICASE : 0));
839                    if(err)
840                    {
841                            char *msg;
842                            i = regerror(err, regextag, NULL, 0);
843                            msg = xmalloc(i+1);
844                            regerror(err, regextag, msg, i+1);
845                            stack_msg(MSG_WARN, "%s", msg);
846                            xfree(msg);
847                            xfree(regextag);
848                            regextag = NULL;
849                    }
850            }
851    
852          for(i = nr = 0; i < rcs->nbranches; i++)          for(i = nr = 0; i < rcs->nbranches; i++)
853                  nr += rcs->branches[i]->nrevs;                  nr += rcs->branches[i]->nrevs;
# Line 627  Line 877 
877          }          }
878    
879          /* We should have at least two tags (HEAD and MAIN) */          /* We should have at least two tags (HEAD and MAIN) */
880          assert(rcs->tags != 0);          assert(rcs->tags != NULL);
881    
882          for(i = 0; i < rcs->tags->ntags; i++)          for(i = 0; i < rcs->tags->ntags; i++)
883          {          {
# Line 652  Line 902 
902                                  xfree(rev.rev);                                  xfree(rev.rev);
903                                  if(!r)                                  if(!r)
904                                  {                                  {
905                                          if(!quiet)                                          stack_msg(MSG_WARN, "No branch found for tag '%s:%s'", t->tag, t->rev->branch);
                                                 fprintf(stderr, "No branch found for tag '%s:%s'\n", t->tag, t->rev->branch);  
906                                  }                                  }
907                                  else                                  else
908                                  {                                  {
# Line 683  Line 932 
932                          revision_t **r = bsearch(t->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);                          revision_t **r = bsearch(t->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
933                          if(!r)                          if(!r)
934                          {                          {
935                                  if(!quiet)                                  stack_msg(MSG_WARN, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);
                                         fprintf(stderr, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);  
936                          }                          }
937                          else                          else
938                          {                          {
939                                  revision_t *rr = *r;                                  revision_t *rr = *r;
940                                  rr->tags = xrealloc(rr->tags, (rr->ntags+1)*sizeof(rr->tags[0]));                                  t->logrev = rr;
941                                  rr->tags[rr->ntags] = t;                                  if(!conf.rev_maxtags || rr->ntags <= conf.rev_maxtags)
942                                  rr->ntags++;                                  {
943                                            rr->tags = xrealloc(rr->tags, (rr->ntags+1)*sizeof(rr->tags[0]));
944                                            if(conf.rev_maxtags && rr->ntags == conf.rev_maxtags)
945                                            {
946                                                    rr->tags[rr->ntags] = xmalloc(sizeof(tag_t));
947                                                    rr->tags[rr->ntags]->tag = xstrdup("...");
948                                                    rr->tags[rr->ntags]->rev = t->rev;
949                                            }
950                                            else
951                                                    rr->tags[rr->ntags] = t;
952                                            rr->ntags++;
953                                    }
954                            }
955    
956                            if(conf.tag_negate)
957                                    t->ignore++;
958                            /* Mark the tag ignored if it matches the configuration */
959                            if(regextag && !regexec(regextag, t->tag, 0, NULL, 0))
960                            {
961                                    if(conf.tag_negate)
962                                            t->ignore--;
963                                    else
964                                            t->ignore++;
965                          }                          }
966                  }                  }
967          }          }
# Line 708  Line 978 
978                  *b = rcs->branches[0];                  *b = rcs->branches[0];
979                  rcs->branches[0] = t;                  rcs->branches[0] = t;
980          }          }
981          return 1;  
982            if(regextag)
983            {
984                    regfree(regextag);
985                    xfree(regextag);
986            }
987  }  }
988    
989  /*  /*
# Line 720  Line 995 
995  static int _nstring;  static int _nstring;
996  static int _nastring;  static int _nastring;
997    
998  void add_string_str(const char *s)  static void zap_string(void)
999    {
1000            _nstring = 0;
1001            if(_string)
1002                    _string[0] = '\0';
1003    }
1004    
1005    static char *dup_string(void)
1006    {
1007            if(_string)
1008                    return xstrdup(_string);
1009            else
1010                    return "";
1011    }
1012    
1013    static void add_string_str(const char *s)
1014  {  {
1015          int l = strlen(s) + 1;          int l = strlen(s) + 1;
1016          if(_nstring + l > _nastring)          if(_nstring + l > _nastring)
1017          {          {
1018                  _nastring += 128;                  _nastring += MAX(128, l);
1019                  _string = xrealloc(_string, _nastring * sizeof(_string[0]));                  _string = xrealloc(_string, _nastring * sizeof(_string[0]));
1020          }          }
1021          memcpy(_string+_nstring, s, l);          memcpy(_string+_nstring, s, l);
1022          _nstring += l-1;          _nstring += l-1;
1023  }  }
1024    
1025  void add_string_ch(int ch)  static void add_string_ch(int ch)
1026  {  {
1027          char buf[2];          char buf[2];
1028          buf[0] = ch;          buf[0] = ch;
# Line 740  Line 1030 
1030          add_string_str(buf);          add_string_str(buf);
1031  }  }
1032    
1033  void add_string_date(const char *d)  static void add_string_date(const char *d)
1034  {  {
1035          struct tm tm, *tmp;          struct tm tm, *tmp;
1036          int n;          int n;
# Line 755  Line 1045 
1045          tm.tm_mon--;          tm.tm_mon--;
1046          if(tm.tm_year > 1900)          if(tm.tm_year > 1900)
1047                  tm.tm_year -= 1900;                  tm.tm_year -= 1900;
1048          t = mktime(&tm);          t = mktime(&tm) - timezone;
1049          if(n != 6 || t == (time_t)(-1))          if(n != 6 || t == (time_t)(-1))
1050          {          {
1051                  add_string_str("<invalid date>");                  add_string_str("<invalid date>");
# Line 770  Line 1060 
1060          xfree(buf);          xfree(buf);
1061  }  }
1062    
1063  char *expand_string(const char *s, rcsfile_t *rcs, revision_t *r, rev_t *rev, rev_t *prev, tag_t *tag)  static void add_string_str_html(const char *s, int maxlen)
1064    {
1065            int l = 0;
1066            char *str = xmalloc(6 * strlen(s) + 1); /* Should hold all char entity-expand */
1067            char *cptr = str;
1068            for(; *s; s++)
1069            {
1070                    if(maxlen && l > abs(maxlen))
1071                    {
1072                            cptr += sprintf(cptr, "...");
1073                            break;
1074                    }
1075                    if(*s < 0x20)
1076                    {
1077                            if(*s == '\n')
1078                            {
1079                                    if(maxlen < 0)
1080                                            *cptr++ = ' ';
1081                                    else
1082                                            cptr += sprintf(cptr, "<br%s>", conf.html_level == HTMLLEVEL_X ? " /" : "");
1083                            }
1084                    }
1085                    else if(*s >= 0x7f || *s == '"')
1086                            cptr += sprintf(cptr, "&#%d;", (int)(unsigned char)*s);
1087                    else if(*s == '<')
1088                            cptr += sprintf(cptr, "&lt;");
1089                    else if(*s == '>')
1090                            cptr += sprintf(cptr, "&gt;");
1091                    else if(*s == '&')
1092                            cptr += sprintf(cptr, "&amp;");
1093                    else if(*s == '"')
1094                            cptr += sprintf(cptr, "&quot;");
1095                    else
1096                            *cptr++ = *s;
1097                    l++;
1098            }
1099            *cptr = '\0';
1100            add_string_str(str);
1101            xfree(str);
1102    }
1103    
1104    static void add_string_str_len(const char *s, int maxlen)
1105    {
1106            int l = strlen(s);
1107            char *str = xmalloc(l + 1 + 3);
1108            strcpy(str, s);
1109            if(maxlen < l)
1110                    sprintf(&str[maxlen], "...");
1111            add_string_str(str);
1112            xfree(str);
1113    }
1114    
1115    static char *expand_string(const char *s, rcsfile_t *rcs, revision_t *r, rev_t *rev, rev_t *prev, tag_t *tag)
1116  {  {
1117          char nb[32];          char nb[32];
1118          char nr[32];          char nr[32];
1119          char *base;          char *base;
1120            char *exp;
1121            int l;
1122            char ch;
1123    
1124          if(!s)          if(!s)
1125                  return xstrdup("");                  return xstrdup("");
1126    
1127          _nstring = 0;          zap_string();
         if(_string)  
                 _string[0] = '\0';  
1128    
1129          sprintf(nb, "%d", rcs->nbranches);          sprintf(nb, "%d", rcs->nbranches);
1130          sprintf(nr, "%d", rcs->nsrev);          sprintf(nr, "%d", rcs->nsrev);
# Line 828  Line 1171 
1171                                  /*                                  /*
1172                                   * We should not add anything here because we can encounter                                   * We should not add anything here because we can encounter
1173                                   * a completely empty path, in which case we do not want                                   * a completely empty path, in which case we do not want
1174                                   * to add any slash. This prevents a inadvertent root redirect.                                   * to add any slash. This prevents an inadvertent root redirect.
1175                                   *                                   *
1176                                   * else                                   * else
1177                                   *      add_string_str("/");                                   *      add_string_str("/");
# Line 864  Line 1207 
1207                          case 'd': if(r && r->delta && r->delta->date) add_string_date(r->delta->date); break;                          case 'd': if(r && r->delta && r->delta->date) add_string_date(r->delta->date); break;
1208                          case 's': if(r && r->delta && r->delta->state) add_string_str(r->delta->state); break;                          case 's': if(r && r->delta && r->delta->state) add_string_str(r->delta->state); break;
1209                          case 'a': if(r && r->delta && r->delta->author) add_string_str(r->delta->author); break;                          case 'a': if(r && r->delta && r->delta->author) add_string_str(r->delta->author); break;
1210                            case 'L':
1211                            case 'l':
1212                                    ch = *s;
1213                                    l = 0;
1214                                    if(s[1] == '[')
1215                                    {
1216                                            char *cptr = strchr(s, ']');
1217                                            char *eptr;
1218                                            if(cptr)
1219                                            {
1220                                                    l = strtol(&s[2], &eptr, 10);
1221                                                    if(eptr != cptr)
1222                                                            l = 0;
1223                                                    else
1224                                                            s = cptr;
1225                                            }
1226                                    }
1227                                    if(!conf.parse_logs)
1228                                            add_string_str("N/A");
1229                                    else if(r && r->dtext && r->dtext->log)
1230                                    {
1231                                            if(ch == 'l')
1232                                                    add_string_str_html(r->dtext->log, l);
1233                                            else
1234                                                    add_string_str_len(r->dtext->log, abs(l));
1235                                    }
1236                                    break;
1237                            case '(':
1238                                    base = dup_string();
1239                                    exp = expand_string(s+1, rcs, r, rev, prev, tag);
1240                                    zap_string();
1241                                    add_string_str(base);
1242                                    add_string_str_html(exp, 0);
1243                                    xfree(base);
1244                                    xfree(exp);
1245                                    /* Find the %) in this recursion level */
1246                                    for(; *s; s++)
1247                                    {
1248                                            if(*s == '%' && s[1] == ')')
1249                                            {
1250                                                    s++;
1251                                                    break;
1252                                            }
1253                                    }
1254                                    if(!*s)
1255                                    {
1256                                            s--;    /* To end outer loop */
1257                                            stack_msg(MSG_WARN, "string expand: Missing %%) in expansion");
1258                                    }
1259                                    break;
1260                            case ')':
1261                                    return dup_string();
1262                          default:                          default:
1263                                  add_string_ch('%');                                  add_string_ch('%');
1264                                  add_string_ch(*s);                                  add_string_ch(*s);
# Line 873  Line 1268 
1268                  else                  else
1269                          add_string_ch(*s);                          add_string_ch(*s);
1270          }          }
1271          return xstrdup(_string);          return dup_string();
1272  }  }
1273    
1274  /*  /*
# Line 881  Line 1276 
1276   * Drawing routines   * Drawing routines
1277   **************************************************************************   **************************************************************************
1278   */   */
1279  int get_swidth(const char *s, font_t *f)  static int get_swidth(const char *s, font_t *f)
1280  {  {
1281          int n;          int n;
1282          int m;          int m;
1283          if(!s || !*s)          if(!s || !*s)
1284                  return 0;                  return 0;
1285    
1286    #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1287            if(conf.use_ttf && f->ttfont)
1288            {
1289                    int bb[8];
1290                    char *e;
1291    #ifdef HAVE_GDIMAGESTRINGFT
1292                    e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1293    #else
1294                    e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1295    #endif
1296                    if(!e)
1297                            return bb[2] - bb[6];
1298            }
1299    #endif
1300          for(n = m = 0; *s; n++, s++)          for(n = m = 0; *s; n++, s++)
1301          {          {
1302                  if(*s == '\n')                  if(*s == '\n')
# Line 898  Line 1308 
1308          }          }
1309          if(n > m)          if(n > m)
1310                  m = n;                  m = n;
1311          return m * (*f)->w;          return f->gdfont ? m * f->gdfont->w : m;
1312  }  }
1313    
1314  int get_sheight(const char *s, font_t *f)  static int get_sheight(const char *s, font_t *f)
1315  {  {
1316          int nl;          int nl;
1317          if(!s || !*s)          if(!s || !*s)
1318                  return 0;                  return 0;
1319    
1320    #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1321            if(conf.use_ttf && f->ttfont)
1322            {
1323                    int bb[8];
1324                    char *e;
1325    #ifdef HAVE_GDIMAGESTRINGFT
1326                    e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1327    #else
1328                    e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1329    #endif
1330                    if(!e)
1331                            return bb[3] - bb[7] + 4;
1332            }
1333    #endif
1334          for(nl = 1; *s; s++)          for(nl = 1; *s; s++)
1335          {          {
1336                  if(*s == '\n' && s[1])                  if(*s == '\n' && s[1])
1337                          nl++;                          nl++;
1338          }          }
1339          return nl * (*f)->h;          return nl * f->gdfont->h;
1340  }  }
1341    
1342  void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color, color_t *bgcolor)  static void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color, color_t *bgcolor)
1343  {  {
1344          int r2 = 2*r;          int r2 = 2*r;
1345            if(!r)
1346                    gdImageFilledRectangle(im, x1, y1, x2, y2, bgcolor->id);
1347    #ifdef HAVE_GDIMAGEFILLEDARC
1348            else
1349            {
1350                    gdImageFilledArc(im, x1+r, y1+r, r2, r2, 180, 270, bgcolor->id, gdArc);
1351                    gdImageFilledArc(im, x2-r, y1+r, r2, r2, 270, 360, bgcolor->id, gdArc);
1352                    gdImageFilledArc(im, x1+r, y2-r, r2, r2,  90, 180, bgcolor->id, gdArc);
1353                    gdImageFilledArc(im, x2-r, y2-r, r2, r2,   0,  90, bgcolor->id, gdArc);
1354                    gdImageFilledRectangle(im, x1+r, y1, x2-r, y1+r, bgcolor->id);
1355                    gdImageFilledRectangle(im, x1, y1+r, x2, y2-r, bgcolor->id);
1356                    gdImageFilledRectangle(im, x1+r, y2-r, x2-r, y2, bgcolor->id);
1357            }
1358    #endif
1359          gdImageLine(im, x1+r, y1, x2-r, y1, color->id);          gdImageLine(im, x1+r, y1, x2-r, y1, color->id);
1360          gdImageLine(im, x1+r, y2, x2-r, y2, color->id);          gdImageLine(im, x1+r, y2, x2-r, y2, color->id);
1361          gdImageLine(im, x1, y1+r, x1, y2-r, color->id);          gdImageLine(im, x1, y1+r, x1, y2-r, color->id);
# Line 928  Line 1367 
1367          }          }
1368          if(r)          if(r)
1369          {          {
1370                    /* FIXME: Pixelization is not perfect */
1371                  gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);                  gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);
1372                  gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);                  gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);
1373                  gdImageArc(im, x1+r, y2-r, r2, r2,  90, 180, color->id);                  gdImageArc(im, x1+r, y2-r, r2, r2,  90, 180, color->id);
                 gdImageArc(im, x2-r, y2-r, r2, r2,   0,  90, color->id);  
1374                  if(conf.box_shadow)                  if(conf.box_shadow)
1375                  {                  {
                         /* FIXME: Pixelization is not correct here */  
1376                          gdImageArc(im, x2-r+1, y2-r+1, r2, r2,   0,  90, black_color.id);                          gdImageArc(im, x2-r+1, y2-r+1, r2, r2,   0,  90, black_color.id);
1377                            gdImageArc(im, x2-r+1, y2-r, r2, r2,   0,  90, black_color.id);
1378                            gdImageArc(im, x2-r, y2-r+1, r2, r2,   0,  90, black_color.id);
1379                  }                  }
1380                    gdImageArc(im, x2-r, y2-r, r2, r2,   0,  90, color->id);
1381    #if !defined(NOGDFILL) && !defined(HAVE_GDIMAGEFILLEDARC)
1382                    /* BUG: We clip manually because libgd segfaults on out of bound values */
1383                    if((x1+x2)/2 >= 0 && (x1+x2)/2 < gdImageSX(im) && (y1+y2)/2 >= 0 && (y1+y2)/2 < gdImageSY(im))
1384                            gdImageFillToBorder(im, (x1+x2)/2, (y1+y2)/2, color->id, bgcolor->id);
1385    #endif
1386          }          }
         gdImageFillToBorder(im, (x1+x2)/2, (y1+y2)/2, color->id, bgcolor->id);  
1387  }  }
1388    
1389  void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)  static void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
1390  {  {
1391            int h = get_sheight(s, f);
1392          int xx, yy;          int xx, yy;
1393          switch(align & ALIGN_HX)          switch(align & ALIGN_HX)
1394          {          {
# Line 955  Line 1401 
1401          {          {
1402          default:          default:
1403          case ALIGN_VT: yy = 0; break;          case ALIGN_VT: yy = 0; break;
1404          case ALIGN_VC: yy = -get_sheight(s, f)/2; break;          case ALIGN_VC: yy = h/2; break;
1405          case ALIGN_VB: yy = -get_sheight(s, f); break;          case ALIGN_VB: yy = h; break;
1406            }
1407    #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1408            if(conf.use_ttf && f->ttfont)
1409            {
1410                    int bb[8];
1411                    char *e;
1412                    int cid = conf.anti_alias ? c->id : -c->id;
1413    #ifdef HAVE_GDIMAGESTRINGFT
1414                    e = gdImageStringFT(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1415    #else
1416                    e = gdImageStringTTF(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1417    #endif
1418                    if(!e)
1419                            return;
1420          }          }
1421          gdImageString(im, *f, x+xx+1, y+yy, s, c->id);  #endif
1422            yy = -yy;
1423            gdImageString(im, f->gdfont, x+xx+1, y+yy, s, c->id);
1424  }  }
1425    
1426  void draw_stringnl(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)  static void draw_stringnl(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
1427  {  {
1428          char *t;          char *t;
1429          char *d;          char *d;
# Line 978  Line 1440 
1440          xfree(d);          xfree(d);
1441  }  }
1442    
1443  void draw_rev(gdImagePtr im, int cx, int ty, revision_t *r)  static void draw_rev(gdImagePtr im, revision_t *r)
1444  {  {
1445          int lx = cx - r->w/2;          int lx;
1446          int rx = lx + r->w;          int rx;
1447            int x2;
1448          int i;          int i;
1449            int ty;
1450    
1451            if(conf.left_right)
1452            {
1453                    lx = r->cx;
1454                    rx = r->cx + r->w;
1455                    ty = r->y - r->h/2;
1456                    x2 = r->cx + r->w/2;
1457            }
1458            else
1459            {
1460                    lx = r->cx - r->w/2;
1461                    rx = lx + r->w;
1462                    ty = r->y;
1463                    x2 = r->cx;
1464            }
1465          draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color, &conf.rev_bgcolor);          draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color, &conf.rev_bgcolor);
1466          ty += conf.rev_tspace;          ty += conf.rev_tspace;
1467          draw_string(im, r->rev->rev, &conf.rev_font, cx, ty, ALIGN_HC, &conf.rev_color);          draw_string(im, r->rev->rev, &conf.rev_font, x2, ty, ALIGN_HC, &conf.rev_color);
1468          ty += get_sheight(r->rev->rev, &conf.rev_font);          ty += get_sheight(r->rev->rev, &conf.rev_font);
1469          draw_stringnl(im, r->revtext, &conf.rev_text_font, cx, ty, ALIGN_HC, &conf.rev_text_color);          draw_stringnl(im, r->revtext, &conf.rev_text_font, x2, ty, ALIGN_HC, &conf.rev_text_color);
1470          ty += get_sheight(r->revtext, &conf.rev_text_font);          ty += get_sheight(r->revtext, &conf.rev_text_font);
1471          for(i = 0; i < r->ntags; i++)          for(i = 0; i < r->ntags; i++)
1472          {          {
1473                  draw_string(im, r->tags[i]->tag, &conf.tag_font, cx, ty, ALIGN_HC, &conf.tag_color);                  draw_string(im, r->tags[i]->tag, &conf.tag_font, x2, ty, ALIGN_HC, &conf.tag_color);
1474                  ty += get_sheight(r->tags[i]->tag, &conf.tag_font);                  ty += get_sheight(r->tags[i]->tag, &conf.tag_font) + conf.rev_separator;
1475          }          }
1476  }  }
1477    
1478  void draw_branch(gdImagePtr im, int cx, int ty, branch_t *b)  static void draw_branch_box(gdImagePtr im, branch_t *b, int xp, int yp)
1479  {  {
1480          int lx = cx - b->w/2;          int lx;
1481          int rx = lx + b->w;          int rx;
         int yy;  
1482          int i;          int i;
1483          /*draw_rbox(im, cx-b->tw/2-1, ty-1, cx+b->tw/2+1, ty+b->th+1, 0, &conf.title_color);*/          int yy;
1484          draw_rbox(im, lx, ty, rx, ty+b->h, 5, &conf.branch_color, &conf.branch_bgcolor);          int x2;
1485    
1486            if(conf.left_right)
1487            {
1488                    lx = b->cx;
1489                    rx = lx + b->w;
1490                    x2 = b->cx + b->w/2;
1491            }
1492            else
1493            {
1494                    lx = b->cx - b->w/2;
1495                    rx = lx + b->w;
1496                    x2 = b->cx;
1497            }
1498            draw_rbox(im, lx+xp, yp, rx+xp, yp+b->h, 5, &conf.branch_color, &conf.branch_bgcolor);
1499          yy = conf.branch_tspace;          yy = conf.branch_tspace;
1500          draw_string(im, b->branch->branch, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);          if(!b->nfolds)
         yy += get_sheight(b->branch->branch, &conf.branch_font);  
         for(i = 0; i < b->ntags; i++)  
1501          {          {
1502                  draw_string(im, b->tags[i]->tag, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);                  draw_string(im, b->branch->branch, &conf.branch_font, x2+xp, yp+yy, ALIGN_HC, &conf.branch_color);
1503                  yy += get_sheight(b->tags[i]->tag, &conf.branch_font);                  yy += get_sheight(b->branch->branch, &conf.branch_font);
1504                    for(i = 0; i < b->ntags; i++)
1505                    {
1506                            draw_string(im, b->tags[i]->tag, &conf.branch_tag_font, x2+xp, yp+yy, ALIGN_HC, &conf.branch_tag_color);
1507                            yy += get_sheight(b->tags[i]->tag, &conf.branch_tag_font);
1508                    }
1509          }          }
1510            else
         ty += b->h;  
         for(i = 0; i < b->nrevs; i++)  
1511          {          {
1512                  gdImageLine(im, cx, ty, cx, ty+conf.rev_minline, conf.rev_color.id);                  int y1, y2;
1513                  ty += conf.rev_minline;                  int tx = lx + b->fw + conf.branch_lspace;
1514                  draw_rev(im, cx, ty, b->revs[i]);                  int nx = tx - get_swidth(" ", &conf.branch_font);
1515                  ty += b->revs[i]->h;                  draw_string(im, b->branch->branch, &conf.branch_font, nx+xp, yp+yy, ALIGN_HR, &conf.branch_color);
1516                    y1 = get_sheight(b->branch->branch, &conf.branch_font);
1517                    draw_string(im, b->tags[0]->tag, &conf.branch_tag_font, tx+xp, yp+yy, ALIGN_HL, &conf.branch_tag_color);
1518                    y2 = get_sheight(b->tags[0]->tag, &conf.branch_font);
1519                    yy += MAX(y1, y2);
1520                    for(i = 0; i < b->nfolds; i++)
1521                    {
1522                            draw_string(im, b->folds[i]->branch->branch, &conf.branch_font, nx+xp, yp+yy, ALIGN_HR, &conf.branch_color);
1523                            y1 = get_sheight(b->folds[i]->branch->branch, &conf.branch_font);
1524                            draw_string(im, b->folds[i]->tags[0]->tag, &conf.branch_tag_font, tx+xp, yp+yy, ALIGN_HL, &conf.branch_tag_color);
1525                            y2 = get_sheight(b->folds[i]->tags[0]->tag, &conf.branch_tag_font);
1526                            yy += MAX(y1, y2);
1527                    }
1528          }          }
1529  }  }
1530    
1531  void draw_connector(gdImagePtr im, branch_t *b)  static void draw_branch(gdImagePtr im, branch_t *b)
 {  
         revision_t *r = b->branchpoint;  
         int x1 = r->cx + r->w/2 + 2;  
         int y1 = r->y + r->h/2;  
         int x2 = b->cx;  
         int y2 = b->y;  
         gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);  
         gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);  
 }  
   
 void alloc_color(gdImagePtr im, color_t *c)  
 {  
         c->id = gdImageColorAllocate(im, c->r, c->g, c->b);  
 }  
   
 gdImagePtr make_image(rcsfile_t *rcs)  
1532  {  {
1533          gdImagePtr im;          int yy, xx;
1534          int i;          int i;
1535          char *cptr;          int line[4];
1536            int l;
1537            int sign;
1538    
1539          im = gdImageCreate(rcs->tw+conf.margin_left+conf.margin_right, rcs->th+conf.margin_top+conf.margin_bottom);          line[0] = conf.rev_color.id;
1540          alloc_color(im, &conf.color_bg);          line[1] = gdTransparent;
1541          alloc_color(im, &conf.tag_color);          line[1] = gdTransparent;
1542          alloc_color(im, &conf.rev_color);          line[3] = conf.rev_color.id;
         alloc_color(im, &conf.rev_bgcolor);  
         alloc_color(im, &conf.rev_text_color);  
         alloc_color(im, &conf.branch_color);  
         alloc_color(im, &conf.branch_bgcolor);  
         alloc_color(im, &conf.title_color);  
         alloc_color(im, &black_color);  
         alloc_color(im, &white_color);  
1543    
1544          for(i = 0; i < rcs->nbranches; i++)          /* Trivial clip the branch */
1545                  draw_branch(im, rcs->branches[i]->cx, rcs->branches[i]->y, rcs->branches[i]);          if(conf.left_right)
         for(i = 0; i < rcs->nbranches; i++)  
1546          {          {
1547                  if(rcs->branches[i]->branchpoint)                  if(b->cx > gdImageSX(im) || b->cx+b->tw < 0 || b->y-b->th/2 > gdImageSY(im) || b->y+b->th/2 < 0)
1548                          draw_connector(im, rcs->branches[i]);                          return;
1549          }          }
1550          cptr = expand_string(conf.title, rcs, NULL, NULL, NULL, NULL);          else
         draw_stringnl(im, cptr, &conf.title_font, conf.title_x, conf.title_y, conf.title_align, &conf.title_color);  
         xfree(cptr);  
   
         return im;  
 }  
   
 /*  
  **************************************************************************  
  * Layout routines  
  **************************************************************************  
  */  
 void move_branch(branch_t *b, int x, int y)  
 {  
         int i;  
         b->cx += x;  
         b->y += y;  
         for(i = 0; i < b->nrevs; i++)  
1551          {          {
1552                  b->revs[i]->cx += x;                  if(b->cx-b->tw/2 > gdImageSX(im) || b->cx+b->tw/2 < 0 || b->y > gdImageSY(im) || b->y+b->th < 0)
1553                  b->revs[i]->y += y;                          return;
1554          }          }
 }  
1555    
1556  void reposition_branch(revision_t *r, int *x, int *w)          draw_branch_box(im, b, 0, conf.left_right ? b->y - b->h/2 : b->y);
1557  {  
1558          int i, j;          if(conf.left_right)
1559          for(j = 0; j < r->nbranches; j++)          {
1560                    if(conf.upside_down)
1561                    {
1562                            xx = b->cx;
1563                            for(i = 0; i < b->nrevs; i++)
1564                            {
1565                                    revision_t *r = b->revs[i];
1566                                    gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1567                                    gdImageLine(im, xx, r->y, r->cx+r->w, r->y, gdStyled);
1568                                    for(sign = l = 1; l < conf.thick_lines; l++)
1569                                    {
1570                                            int pp = (l+1)/2*sign;
1571                                            gdImageLine(im, xx, r->y+pp, r->cx+r->w, r->y+pp, gdStyled);
1572                                            sign *= -1;
1573                                    }
1574                                    draw_rev(im, r);
1575                                    xx = r->cx;
1576                            }
1577                            if(conf.branch_dupbox && b->nrevs)
1578                            {
1579                                    i = b->cx - b->tw + b->w;
1580                                    gdImageLine(im, xx, b->y, i+b->w, b->y, conf.rev_color.id);
1581                                    for(sign = l = 1; l < conf.thick_lines; l++)
1582                                    {
1583                                            int pp = (l+1)/2*sign;
1584                                            gdImageLine(im, xx, b->y+pp, i+b->w, b->y+pp, conf.rev_color.id);
1585                                            sign *= -1;
1586                                    }
1587                                    draw_branch_box(im, b, i - b->cx, b->y - b->h/2);
1588                            }
1589                    }
1590                    else
1591                    {
1592                            xx = b->cx + b->w;
1593                            for(i = 0; i < b->nrevs; i++)
1594                            {
1595                                    revision_t *r = b->revs[i];
1596                                    gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1597                                    gdImageLine(im, xx, r->y, r->cx, r->y, gdStyled);
1598                                    for(sign = l = 1; l < conf.thick_lines; l++)
1599                                    {
1600                                            int pp = (l+1)/2*sign;
1601                                            gdImageLine(im, xx, r->y+pp, r->cx, r->y+pp, gdStyled);
1602                                            sign *= -1;
1603                                    }
1604                                    draw_rev(im, r);
1605                                    xx = r->cx + r->w;
1606                            }
1607                            if(conf.branch_dupbox && b->nrevs)
1608                            {
1609                                    i = b->cx + b->tw - b->w;
1610                                    gdImageLine(im, xx, b->y, i, b->y, conf.rev_color.id);
1611                                    for(sign = l = 1; l < conf.thick_lines; l++)
1612                                    {
1613                                            int pp = (l+1)/2*sign;
1614                                            gdImageLine(im, xx, b->y+pp, i, b->y+pp, conf.rev_color.id);
1615                                            sign *= -1;
1616                                    }
1617                                    draw_branch_box(im, b, i - b->cx, b->y - b->h/2);
1618                            }
1619                    }
1620            }
1621            else
1622            {
1623                    if(conf.upside_down)
1624                    {
1625                            yy = b->y;
1626                            for(i = 0; i < b->nrevs; i++)
1627                            {
1628                                    revision_t *r = b->revs[i];
1629                                    gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1630                                    gdImageLine(im, r->cx, yy, r->cx, r->y+r->h, gdStyled);
1631                                    for(sign = l = 1; l < conf.thick_lines; l++)
1632                                    {
1633                                            int pp = (l+1)/2*sign;
1634                                            gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y+r->h, gdStyled);
1635                                            sign *= -1;
1636                                    }
1637                                    draw_rev(im, r);
1638                                    yy = r->y;
1639                            }
1640                            if(conf.branch_dupbox && b->nrevs)
1641                            {
1642                                    i = b->y - b->th + b->h;
1643                                    gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);
1644                                    for(sign = l = 1; l < conf.thick_lines; l++)
1645                                    {
1646                                            int pp = (l+1)/2*sign;
1647                                            gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, conf.rev_color.id);
1648                                            sign *= -1;
1649                                    }
1650                                    draw_branch_box(im, b, 0, i);
1651                            }
1652                    }
1653                    else
1654                    {
1655                            yy = b->y + b->h;
1656                            for(i = 0; i < b->nrevs; i++)
1657                            {
1658                                    revision_t *r = b->revs[i];
1659                                    gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1660                                    gdImageLine(im, r->cx, yy, r->cx, r->y, gdStyled);
1661                                    for(sign = l = 1; l < conf.thick_lines; l++)
1662                                    {
1663                                            int pp = (l+1)/2*sign;
1664                                            gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y, gdStyled);
1665                                            sign *= -1;
1666                                    }
1667                                    draw_rev(im, r);
1668                                    yy = r->y + r->h;
1669                            }
1670                            if(conf.branch_dupbox && b->nrevs)
1671                            {
1672                                    i = b->y + b->th - b->h;
1673                                    gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);
1674                                    for(sign = l = 1; l < conf.thick_lines; l++)
1675                                    {
1676                                            int pp = (l+1)/2*sign;
1677                                            gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, conf.rev_color.id);
1678                                            sign *= -1;
1679                                    }
1680                                    draw_branch_box(im, b, 0, i);
1681                            }
1682                    }
1683            }
1684    }
1685    
1686    static void draw_connector(gdImagePtr im, branch_t *b)
1687    {
1688            int l;
1689            int sign;
1690            revision_t *r = b->branchpoint;
1691            int x1 = r->cx + r->w/2 + 2;
1692            int y1 = r->y + r->h/2;
1693            int x2 = b->cx;
1694            int y2 = b->y;
1695    
1696            if(conf.left_right)
1697            {
1698                    x2 = r->cx + r->w/2;
1699                    y2 = r->y + r->h/2 + 3;
1700                    x1 = b->cx;
1701                    y1 = b->y;
1702                    if(conf.upside_down)
1703                            x1 += b->w;
1704            }
1705            else
1706            {
1707                    x1 = r->cx + r->w/2 + 2;
1708                    y1 = r->y + r->h/2;
1709                    x2 = b->cx;
1710                    y2 = b->y;
1711                    if(conf.upside_down)
1712                            y2 += b->h;
1713            }
1714            gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);
1715            gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);
1716            for(sign = l = 1; l < conf.thick_lines; l++)
1717            {
1718                    int pp = (l+1)/2*sign;
1719                    gdImageLine(im, x1, y1+pp, x2, y1+pp, conf.branch_color.id);
1720                    gdImageLine(im, x2+pp, y1, x2+pp, y2, conf.branch_color.id);
1721                    sign *= -1;
1722            }
1723    }
1724    
1725    static void draw_merges(gdImagePtr im, rcsfile_t *rcs, int dot)
1726    {
1727            int i;
1728            for(i = 0; i < rcs->nmerges; i++)
1729            {
1730                    revision_t *fr = rcs->merges[i].from->logrev;
1731                    revision_t *tr = rcs->merges[i].to->logrev;
1732                    int x1, x2, y1, y2;
1733                    if(!fr || !tr || fr == tr)
1734                            continue;       /* This can happen with detached tags and self-references */
1735                    if(conf.left_right)
1736                    {
1737                            if(fr->branch == tr->branch)
1738                            {
1739                                    y1 = fr->y - fr->h/2;
1740                                    y2 = tr->y - tr->h/2;
1741                            }
1742                            else
1743                            {
1744                                    if(fr->y < tr->y)
1745                                    {
1746                                            y1 = fr->y + fr->h/2;
1747                                            y2 = tr->y - tr->h/2;
1748                                    }
1749                                    else
1750                                    {
1751                                            y1 = fr->y - fr->h/2;
1752                                            y2 = tr->y + tr->h/2;
1753                                    }
1754                            }
1755                            x1 = fr->cx + fr->w/2;
1756                            x2 = tr->cx + tr->w/2;
1757                    }
1758                    else
1759                    {
1760                            if(fr->branch == tr->branch)
1761                            {
1762                                    x1 = fr->cx - fr->w/2;
1763                                    x2 = tr->cx - tr->w/2;
1764                            }
1765                            else
1766                            {
1767                                    if(fr->cx < tr->cx)
1768                                    {
1769                                            x1 = fr->cx + fr->w/2;
1770                                            x2 = tr->cx - tr->w/2;
1771                                    }
1772                                    else
1773                                    {
1774                                            x1 = fr->cx - fr->w/2;
1775                                            x2 = tr->cx + tr->w/2;
1776                                    }
1777                            }
1778                            y1 = fr->y + rcs->merges[i].from->yofs;
1779                            y2 = tr->y + rcs->merges[i].to->yofs;
1780                    }
1781                    if(dot && !conf.merge_arrows)
1782                    {
1783                            int o = conf.left_right ? 1 : 0;
1784                            gdImageArc(im, x2, y2+o, 8, 8, 0, 360, conf.merge_color.id);
1785                            /* BUG: We clip manually because libgd segfaults on out of bound values */
1786                            if(x2+1 >= 0 && x2+1 < gdImageSX(im) && y2+o+1 >= 0 && y2+o+1 < gdImageSY(im))
1787                                    gdImageFillToBorder(im, x2+1, y2+o+1, conf.merge_color.id, conf.merge_color.id);
1788                    }
1789                    else if(dot && conf.merge_arrows)
1790                    {
1791                            /*
1792                             * Arrow patch from Haroon Rafique <haroon.rafique@utoronto.ca>
1793                             * Slightly adapted to be more configurable.
1794                             */
1795                            int sx, sy;     /* start point coordinates */
1796                            int ex, ey;     /* end point coordinates */
1797                            double theta;
1798                            double u1, v1, u2, v2;
1799                            gdPoint p[3];
1800    
1801                            sx = x1; sy = y1;
1802                            ex = x2; ey = y2;
1803                            if(conf.left_right)
1804                            {
1805                                    if(fr->branch == tr->branch)
1806                                    {
1807                                            int yy = (y1 < y2 ? y1 : y2) - 5;
1808                                            /* line from (x1,yy) to (x2,yy) */
1809                                            sy = ey = yy;
1810                                    }
1811                                    else
1812                                    {
1813                                            if(y1 > y2)
1814                                            {
1815                                                    /* line from (x1,y1-3) to (x2,y2+3+1) */
1816                                                    sy = y1-3;
1817                                                    ey = y2+3+1;
1818                                            }
1819                                            else
1820                                            {
1821                                                    /* line from (x1,y1+3+1) to (x2,y2-3) */
1822                                                    sy = y1+3+1;
1823                                                    ey = y2-3;
1824                                            }
1825                                    }
1826                            }
1827                            else
1828                            {
1829                                    if(fr->branch == tr->branch)
1830                                    {
1831                                            int xx = (x1 < x2 ? x1 : x2) - 5;
1832                                            /* line from (xx,y1) to (xx,y2) */
1833                                            sx = ex = xx;
1834                                    }
1835                                    else
1836                                    {
1837                                            if(x1 > x2)
1838                                            {
1839                                                    /* line from (x1-3,y1) to (x2+3,y2) */
1840                                                    sx = x1-3;
1841                                                    ex = x2+3;
1842                                            }
1843                                            else
1844                                            {
1845                                                    /* line from (x1+3,y1) to (x2-3,y2) */
1846                                                    sx = x1+3;
1847                                                    ex = x2-3;
1848                                            }
1849                                    }
1850                            }
1851                            /*
1852                             * inspiration for arrow code comes from arrows.c in the
1853                             * graphviz package. Thank you, AT&T
1854                             */
1855                            /* theta in radians */
1856                            theta = atan2((double)(sy-ey), (double)(sx-ex));
1857                            u1 = (double)conf.arrow_length * cos(theta);
1858                            v1 = (double)conf.arrow_length * sin(theta);
1859                            u2 = (double)conf.arrow_width  * cos(theta + M_PI/2.0);
1860                            v2 = (double)conf.arrow_width  * sin(theta + M_PI/2.0);
1861                            /* points of polygon (triangle) */
1862                            p[0].x = ROUND(ex + u1 - u2);
1863                            p[0].y = ROUND(ey + v1 - v2);
1864                            p[1].x = ex;
1865                            p[1].y = ey;
1866                            p[2].x = ROUND(ex + u1 + u2);
1867                            p[2].y = ROUND(ey + v1 + v2);
1868                            /* draw the polygon (triangle) */
1869                            gdImageFilledPolygon(im, p, 3, conf.merge_color.id);
1870                    }
1871                    else
1872                    {
1873                            if(conf.left_right)
1874                            {
1875                                    if(fr->branch == tr->branch)
1876                                    {
1877                                            int yy = (y1 < y2 ? y1 : y2) - 5;
1878                                            gdImageLine(im, x1, y1, x1, yy, conf.merge_color.id);
1879                                            gdImageLine(im, x2, y2, x2, yy, conf.merge_color.id);
1880                                            gdImageLine(im, x1, yy, x2, yy, conf.merge_color.id);
1881                                    }
1882                                    else
1883                                    {
1884                                            if(y1 > y2)
1885                                            {
1886                                                    gdImageLine(im, x1, y1, x1, y1-3, conf.merge_color.id);
1887                                                    gdImageLine(im, x2, y2+1, x2, y2+3+1, conf.merge_color.id);
1888                                                    gdImageLine(im, x1, y1-3, x2, y2+3+1, conf.merge_color.id);
1889                                            }
1890                                            else
1891                                            {
1892                                                    gdImageLine(im, x1, y1+1, x1, y1+3+1, conf.merge_color.id);
1893                                                    gdImageLine(im, x2, y2, x2, y2-3, conf.merge_color.id);
1894                                                    gdImageLine(im, x1, y1+3+1, x2, y2-3, conf.merge_color.id);
1895                                            }
1896                                    }
1897                            }
1898                            else
1899                            {
1900                                    if(fr->branch == tr->branch)
1901                                    {
1902                                            int xx = (x1 < x2 ? x1 : x2) - 5;
1903                                            gdImageLine(im, xx, y1, x1, y1, conf.merge_color.id);
1904                                            gdImageLine(im, xx, y2, x2, y2, conf.merge_color.id);
1905                                            gdImageLine(im, xx, y1, xx, y2, conf.merge_color.id);
1906                                    }
1907                                    else
1908                                    {
1909                                            if(x1 > x2)
1910                                            {
1911                                                    gdImageLine(im, x1, y1, x1-3, y1, conf.merge_color.id);
1912                                                    gdImageLine(im, x2, y2, x2+3, y2, conf.merge_color.id);
1913                                                    gdImageLine(im, x1-3, y1, x2+3, y2, conf.merge_color.id);
1914                                            }
1915                                            else
1916                                            {
1917                                                    gdImageLine(im, x1, y1, x1+3, y1, conf.merge_color.id);
1918                                                    gdImageLine(im, x2, y2, x2-3, y2, conf.merge_color.id);
1919                                                    gdImageLine(im, x1+3, y1, x2-3, y2, conf.merge_color.id);
1920                                            }
1921                                    }
1922                            }
1923                    }
1924            }
1925    }
1926    
1927    static void draw_messages(gdImagePtr im, int offset)
1928    {
1929            int i;
1930    
1931            for(i = 0; i < nmsg_stack; i++)
1932            {
1933                    draw_stringnl(im, msg_stack[i].msg, &conf.msg_font, conf.margin_left, offset, ALIGN_HL|ALIGN_VT, &conf.msg_color);
1934                    offset += msg_stack[i].h;
1935            }
1936    }
1937    
1938    static void alloc_color(gdImagePtr im, color_t *c)
1939    {
1940            c->id = gdImageColorAllocate(im, c->r, c->g, c->b);
1941    }
1942    
1943    static gdImagePtr make_image(rcsfile_t *rcs)
1944    {
1945            gdImagePtr im;
1946            int i;
1947            char *cptr;
1948            int w, h;
1949            int subx = 0, suby = 0;
1950            int subw, subh;
1951            int msgh = 0;
1952    
1953            if(subtree_branch)
1954            {
1955                    subw = 0;
1956                    subh = 0;
1957                    if(subtree_rev)
1958                    {
1959                            for(i = 0; i < subtree_rev->nbranches; i++)
1960                                    calc_subtree_size(subtree_rev->branches[i], &subx, &suby, &subw, &subh);
1961                    }
1962                    else
1963                            calc_subtree_size(subtree_branch, &subx, &suby, &subw, &subh);
1964            }
1965            else
1966            {
1967                    subw = rcs->tw;
1968                    subh = rcs->th;
1969            }
1970    
1971            cptr = expand_string(conf.title, rcs, NULL, NULL, NULL, NULL);
1972            w = subw + conf.margin_left + conf.margin_right;
1973            h = subh + conf.margin_top + conf.margin_bottom;
1974            i = get_swidth(cptr, &conf.title_font);
1975            if(i > w)
1976                    w = i;
1977    
1978            if(!quiet && nmsg_stack)
1979            {
1980                    int msgw = 0;
1981                    for(i = 0; i < nmsg_stack; i++)
1982                    {
1983                            int ww = msg_stack[i].w = get_swidth(msg_stack[i].msg, &conf.msg_font);
1984                            int hh = msg_stack[i].h = get_sheight(msg_stack[i].msg, &conf.msg_font);
1985                            msgh += hh;
1986                            h += hh;
1987                            if(ww > msgw)
1988                                    msgw = ww;
1989                    }
1990                    if(msgw > w)
1991                            w = msgw;
1992            }
1993    
1994            im = gdImageCreate(w, h);
1995            alloc_color(im, &conf.color_bg);
1996            alloc_color(im, &conf.tag_color);
1997            alloc_color(im, &conf.rev_color);
1998            alloc_color(im, &conf.rev_bgcolor);
1999            alloc_color(im, &conf.rev_text_color);
2000            alloc_color(im, &conf.branch_color);
2001            alloc_color(im, &conf.branch_tag_color);
2002            alloc_color(im, &conf.branch_bgcolor);
2003            alloc_color(im, &conf.title_color);
2004            alloc_color(im, &conf.merge_color);
2005            alloc_color(im, &conf.msg_color);
2006            alloc_color(im, &black_color);
2007            alloc_color(im, &white_color);
2008    
2009            if(conf.transparent_bg)
2010                    gdImageColorTransparent(im, conf.color_bg.id);
2011    
2012            if(!conf.merge_front)
2013                    draw_merges(im, rcs, 0);
2014    
2015            for(i = 0; i < rcs->nbranches; i++)
2016            {
2017                    if(!rcs->branches[i]->folded && !(subtree_branch && !rcs->branches[i]->subtree_draw))
2018                            draw_branch(im, rcs->branches[i]);
2019            }
2020    
2021            draw_merges(im, rcs, 1);        /* The dots of the merge dest */
2022    
2023            for(i = 0; i < rcs->nbranches; i++)
2024            {
2025                    if(rcs->branches[i]->branchpoint)
2026                            draw_connector(im, rcs->branches[i]);
2027            }
2028    
2029            /* Clear the margins if we have a partial tree */
2030            if(subtree_branch)
2031            {
2032                    gdImageFilledRectangle(im, 0, 0, w-1, conf.margin_top-1, conf.color_bg.id);
2033                    gdImageFilledRectangle(im, 0, 0, conf.margin_left-1, h-1, conf.color_bg.id);
2034                    gdImageFilledRectangle(im, 0, h-conf.margin_bottom, w-1, h-1, conf.color_bg.id);
2035                    gdImageFilledRectangle(im, w-conf.margin_right, 0, w-1, h-1, conf.color_bg.id);
2036            }
2037    
2038            draw_stringnl(im, cptr, &conf.title_font, conf.title_x, conf.title_y, conf.title_align, &conf.title_color);
2039            xfree(cptr);
2040    
2041            if(conf.merge_front)
2042                    draw_merges(im, rcs, 0);
2043    
2044            if(!quiet)
2045                    draw_messages(im, h - conf.margin_bottom/2 - msgh);
2046    
2047            return im;
2048    }
2049    
2050    /*
2051     **************************************************************************
2052     * Layout routines
2053     *
2054     * Branch BBox:
2055     *      left   = center_x - total_width / 2     (cx-tw)/2
2056     *      right  = center_x + total_width / 2     (cx+tw)/2
2057     *      top    = y_pos                          (y)
2058     *      bottom = y_pos + total_height           (y+th)
2059     *
2060     * Margins of branches:
2061     *
2062     *         .              .
2063     *         .              .
2064     *         +--------------+
2065     *            ^
2066     *            | branch_margin           .
2067     *            v                         .
2068     * ----------------+                    .
2069     *                 | ^                  |
2070     *                 | | branch_connect   |
2071     *                 | v                  |
2072     *..-+      +t-----+------+      +------+------+
2073     *   |      l             |      |             |
2074     *   | <--> | branch bbox | <--> | branch bbox |
2075     *   |   |  |             r   |  |             |
2076     *..-+   |  +------------b+   |  +-------------+
2077     *       |    ^               branch_margin
2078     *       |    | branch_margin
2079     *       |    v
2080     *       |  +-------------+
2081     *       |  .             .
2082     *       |  .             .
2083     *       |
2084     *       branch_margin
2085     *
2086     * FIXME: There are probable som +/-1 errors in the code...
2087     *        (notably shadows are not calculated in the margins)
2088     **************************************************************************
2089     */
2090    static void move_branch(branch_t *b, int x, int y)
2091    {
2092            int i;
2093            b->cx += x;
2094            b->y += y;
2095            for(i = 0; i < b->nrevs; i++)
2096            {
2097                    b->revs[i]->cx += x;
2098                    b->revs[i]->y += y;
2099            }
2100    }
2101    
2102    static void initial_reposition_branch(revision_t *r, int *x, int *w)
2103    {
2104            int i, j;
2105            for(j = 0; j < r->nbranches; j++)
2106          {          {
2107                  branch_t *b = r->branches[j];                  branch_t *b = r->branches[j];
2108                  *x += *w + conf.rev_minline + b->tw/2 - b->cx;                  *x += *w + conf.rev_minline + b->tw/2 - b->cx;
# Line 1101  Line 2112 
2112                  /* Recurse to move branches of branched revisions */                  /* Recurse to move branches of branched revisions */
2113                  for(i = b->nrevs-1; i >= 0; i--)                  for(i = b->nrevs-1; i >= 0; i--)
2114                  {                  {
2115                          reposition_branch(b->revs[i], x, w);                          initial_reposition_branch(b->revs[i], x, w);
2116                    }
2117            }
2118    }
2119    
2120    static void initial_reposition_branch_lr(revision_t *r, int *y, int *h)
2121    {
2122            int i, j;
2123            for(j = 0; j < r->nbranches; j++)
2124            {
2125                    branch_t *b = r->branches[j];
2126                    *y += *h + conf.rev_minline + b->th/2 - b->y;
2127                    *h = b->th/2;
2128                    move_branch(b, r->cx + r->w/2 + conf.branch_connect, *y);
2129                    *y = b->y;
2130                    /* Recurse to move branches of branched revisions */
2131                    for(i = b->nrevs-1; i >= 0; i--)
2132                    {
2133                            initial_reposition_branch_lr(b->revs[i], y, h);
2134                  }                  }
2135          }          }
2136  }  }
2137    
2138  void rect_union(int *x, int *y, int *w, int *h, branch_t *b)  static void rect_union(int *x, int *y, int *w, int *h, branch_t *b)
2139  {  {
2140          int x1 = *x;          int x1 = *x;
2141          int x2 = x1 + *w;          int x2 = x1 + *w;
2142          int y1 = *y;          int y1 = *y;
2143          int y2 = y1 + *h;          int y2 = y1 + *h;
2144          int xx1 = b->cx - b->tw/2;          int xx1;
2145          int xx2 = xx1 + b->tw;          int xx2;
2146          int yy1 = b->y;          int yy1;
2147          int yy2 = yy1 + b->th;          int yy2;
2148    
2149            if(conf.left_right)
2150            {
2151                    xx1 = b->cx;
2152                    yy1 = b->y - b->th/2;
2153            }
2154            else
2155            {
2156                    xx1 = b->cx - b->tw/2;
2157                    yy1 = b->y;
2158            }
2159            xx2 = xx1 + b->tw;
2160            yy2 = yy1 + b->th;
2161    
2162          x1 = MIN(x1, xx1);          x1 = MIN(x1, xx1);
2163          x2 = MAX(x2, xx2);          x2 = MAX(x2, xx2);
2164          y1 = MIN(y1, yy1);          y1 = MIN(y1, yy1);
# Line 1126  Line 2169 
2169          *h = y2 - y1;          *h = y2 - y1;
2170  }  }
2171    
2172  int branch_intersects(int top, int bottom, int left, branch_t *b)  static void calc_subtree_size(branch_t *b, int *x, int *y, int *w, int *h)
2173    {
2174            int i, j;
2175    
2176            rect_union(x, y, w, h, b);
2177    
2178            for(i = 0; i < b->nrevs; i++)
2179            {
2180                    for(j = 0; j < b->revs[i]->nbranches; j++)
2181                            calc_subtree_size(b->revs[i]->branches[j], x, y, w, h);
2182            }
2183    }
2184    
2185    static int branch_intersects(int top, int bottom, int left, branch_t *b)
2186  {  {
2187          int br = b->cx + b->tw/2;          int br = b->cx + b->tw/2;
2188          int bt = b->y - conf.branch_connect - conf.branch_margin/2;          int bt = b->y - conf.branch_connect - conf.branch_margin/2;
# Line 1134  Line 2190 
2190          return !(bt > bottom || bb < top || br >= left);          return !(bt > bottom || bb < top || br >= left);
2191  }  }
2192    
2193  int kern_branch(rcsfile_t *rcs, branch_t *b)  static int branch_intersects_lr(int left, int right, int top, branch_t *b)
2194    {
2195            int bt = b->y + b->th/2;
2196            int bl = b->cx - conf.branch_connect - conf.branch_margin/2;
2197            int br = b->cx + b->tw + conf.branch_margin/2;
2198            return !(bl > right || br < left || bt >= top);
2199    }
2200    
2201    static int kern_branch(rcsfile_t *rcs, branch_t *b)
2202  {  {
2203          int left = b->cx - b->tw/2;          int left = b->cx - b->tw/2;
2204          int top = b->y - conf.branch_connect - conf.branch_margin/2;          int top = b->y - conf.branch_connect - conf.branch_margin/2;
# Line 1162  Line 2226 
2226          return 0;          return 0;
2227  }  }
2228    
2229  void make_layout(rcsfile_t *rcs)  static int kern_branch_lr(rcsfile_t *rcs, branch_t *b)
2230    {
2231            int top = b->y - b->th/2;
2232            int left = b->cx - conf.branch_connect - conf.branch_margin/2;
2233            int right = b->cx + b->tw + conf.branch_margin/2;
2234            int i;
2235            int ypos = 0;
2236    
2237            for(i = 0; i < rcs->nbranches; i++)
2238            {
2239                    branch_t *bp = rcs->branches[i];
2240                    if(bp == b)
2241                            continue;
2242                    if(branch_intersects_lr(left, right, top, bp))
2243                    {
2244                            int m = bp->y + bp->th/2 + conf.branch_margin;
2245                            if(m > ypos)
2246                                    ypos = m;
2247                    }
2248            }
2249            if(ypos && (b->y - b->th/2) - ypos > 0)
2250            {
2251                    move_branch(b, 0, ypos - (b->y - b->th/2));
2252                    return 1;
2253            }
2254            return 0;
2255    }
2256    
2257    static int kern_tree(rcsfile_t *rcs)
2258    {
2259            int i;
2260            int moved;
2261            int safeguard;
2262            int totalmoved = 0;
2263            for(moved = 1, safeguard = LOOPSAFEGUARD; moved && safeguard; safeguard--)
2264            {
2265                    moved = 0;
2266                    for(i = 1; i < rcs->nbranches; i++)
2267                    {
2268                            if(conf.left_right)
2269                                    moved += kern_branch_lr(rcs, rcs->branches[i]);
2270                            else
2271                                    moved += kern_branch(rcs, rcs->branches[i]);
2272                    }
2273                    totalmoved += moved;
2274    #ifdef DEBUG
2275                    fprintf(stderr, "kern_tree: moved=%d\n", moved);
2276    #endif
2277            }
2278            if(!safeguard)
2279                    stack_msg(MSG_WARN, "kern_tree: safeguard terminated possible infinite loop; please report.");
2280            return totalmoved;
2281    }
2282    
2283    static int index_of_revision(revision_t *r)
2284    {
2285            branch_t *b = r->branch;
2286            int i;
2287            for(i = 0; i < b->nrevs; i++)
2288            {
2289                    if(r == b->revs[i])
2290                            return i;
2291            }
2292            stack_msg(MSG_ERR, "index_of_revision: Cannot find revision in branch\n");
2293            return 0;
2294    }
2295    
2296    static void branch_bbox(branch_t *br, int *l, int *r, int *t, int *b)
2297    {
2298            if(l)   *l = br->cx - br->tw/2;
2299            if(r)   *r = br->cx + br->tw/2;
2300            if(t)   *t = br->y;
2301            if(b)   *b = br->y + br->th + ((conf.branch_dupbox && br->nrevs) ? conf.rev_minline + br->h : 0);
2302    }
2303    
2304    static void branch_ext_bbox(branch_t *br, int *l, int *r, int *t, int *b)
2305    {
2306            int extra = conf.branch_margin & 1;     /* Correct +/-1 error on div 2 */
2307            branch_bbox(br, l, r, t, b);
2308            if(l)   *l -= conf.branch_margin/2;
2309            if(r)   *r += conf.branch_margin/2 + extra;
2310            if(t)   *t -= conf.branch_connect + conf.branch_margin/2;
2311            if(b)   *b += conf.branch_margin/2 + extra;
2312    }
2313    
2314    static int branch_distance(branch_t *br1, branch_t *br2)
2315    {
2316            int l1, r1, t1, b1;
2317            int l2, r2, t2, b2;
2318            assert(br1 != NULL);
2319            assert(br2 != NULL);
2320            branch_bbox(br1, &l1, &r1, NULL, NULL);
2321            branch_bbox(br2, &l2, &r2, NULL, NULL);
2322            branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
2323            branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
2324            /* Return:
2325             * - 0 if branches have no horizontal overlap
2326             * - positive if b1 is left of b2
2327             * - negative if b2 is left of b1
2328             */
2329            if((t1 > t2 && t1 < b2) || (b1 > t2 && b1 < b2))
2330                    return l1 < l2 ? l2 - r1 : -(l1 - r2);
2331            else
2332                    return 0;
2333    }
2334    
2335    static int space_needed(branch_t *br1, branch_t *br2)
2336    {
2337            int t1, b1;
2338            int t2, b2;
2339            assert(br1 != NULL);
2340            assert(br2 != NULL);
2341            assert(br1->cx < br2->cx);      /* br1 must be left of br2 */
2342            branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
2343            branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
2344            /* Return:
2345             * - positive if top br1 is located lower than br2
2346             * - negatve is top br2 is located lower than br1
2347             */
2348            if(t1 > t2)
2349                    return -(t1 - b2);
2350            else
2351                    return t2 - b1;
2352    }
2353    
2354    static void move_yr_branch(branch_t *b, int dy)
2355    {
2356            int i, j;
2357    #ifdef DEBUG
2358    /*      fprintf(stderr, "move_yr_branch: b=%s, dy=%d\n", b->branch->branch, dy);*/
2359    #endif
2360            b->y += dy;
2361            for(i = 0; i < b->nrevs; i++)
2362            {
2363                    b->revs[i]->y += dy;
2364                    for(j = 0; j < b->revs[i]->nbranches; j++)
2365                    {
2366    #ifdef DEBUG
2367    /*                      fprintf(stderr, ".");*/
2368    #endif
2369                            move_yr_branch(b->revs[i]->branches[j], dy);
2370                    }
2371            }
2372    }
2373    
2374    static void move_trunk(revision_t *r, int dy)
2375    {
2376            int i, j;
2377            branch_t *b = r->branch;
2378            b->th += dy;
2379            for(i = index_of_revision(r); i < b->nrevs; i++)
2380            {
2381    #ifdef DEBUG
2382                    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);
2383    #endif
2384                    b->revs[i]->y += dy;
2385                    for(j = 0; j < b->revs[i]->nbranches; j++)
2386                    {
2387                            move_yr_branch(b->revs[i]->branches[j], dy);
2388                    }
2389            }
2390    }
2391    
2392    static int space_below(rcsfile_t *rcs, revision_t *r)
2393    {
2394            int i, j;
2395            int bl, br, bb;
2396            int space = INT_MAX;
2397            branch_t *b = r->branch;
2398            branch_t *minb = NULL;
2399    
2400            branch_ext_bbox(b, &bl, &br, NULL, &bb);
2401            for(i = 0; i < rcs->nbranches; i++)
2402            {
2403                    int tbl, tbr, tbt;
2404                    branch_t *tb = rcs->branches[i];
2405                    branch_ext_bbox(tb, &tbl, &tbr, &tbt, NULL);
2406                    if(tb == b)
2407                            continue;
2408                    if(tbt > bb)    /* Must be below our branch */
2409                    {
2410                            if(tb->branchpoint)     /* Take account for the horiz connector */
2411                                    tbl = tb->branchpoint->cx + tb->branchpoint->branch->tw/2;
2412                            if((bl >= tbl && bl <= tbr) || (br <= tbr && br >= tbl))
2413                            {
2414                                    int s = tbt - bb - conf.branch_connect;
2415                                    if(s < space)
2416                                    {
2417                                            space = s;
2418                                            minb = tb;
2419                                    }
2420                            }
2421                    }
2422            }
2423            if(b->branchpoint)
2424            {
2425                    for(i = index_of_revision(r); i < b->nrevs; i++)
2426                    {
2427                            for(j = 0; j < b->revs[i]->nbranches; j++)
2428                            {
2429                                    int s = space_below(rcs, b->revs[i]->branches[j]->revs[0]);
2430                                    if(s < space)
2431                                            space = s;
2432                            }
2433                    }
2434            }
2435    #ifdef DEBUG
2436            fprintf(stderr, "space_below: from %s have %d to %s\n", b->branch->branch, space, minb ? minb->branch->branch : "<recursed>");
2437    #endif
2438            return space;
2439    }
2440    
2441    static int space_available(rcsfile_t *rcs, branch_t *colbr, branch_t *tagbr, int *nl, revision_t **bpcommon)
2442    {
2443            int i;
2444            int space = 0;
2445            int nlinks = 0;
2446            revision_t *r;
2447            branch_t *b;
2448            branch_t *ancestor;
2449            revision_t *branchpoint;
2450    
2451            if(!tagbr->branchpoint || !colbr->branchpoint)
2452            {
2453                    stack_msg(MSG_WARN, "space_available: Trying to stretch the top?");
2454                    return 0;
2455            }
2456    
2457            r = colbr->branchpoint;
2458            b = r->branch;
2459            branchpoint = tagbr->branchpoint;
2460            ancestor = branchpoint->branch;
2461            assert(b != NULL);
2462            assert(ancestor != NULL);
2463    
2464            while(1)
2465            {
2466                    int s;
2467                    int rtag = b == ancestor ? index_of_revision(branchpoint)+1 : 0;
2468                    for(i = index_of_revision(r); i >= rtag; i--)
2469                    {
2470                            if(i > 0)
2471                                    s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
2472                            else
2473                                    s = b->revs[i]->y - (b->y + b->h);
2474                            if(s < conf.rev_maxline)
2475                            {
2476                                    space += conf.rev_maxline - s;
2477                                    nlinks++;
2478                            }
2479                    }
2480                    s = space_below(rcs, r);
2481                    if(s < space)
2482                            space = s;
2483    #ifdef DEBUG
2484                    if(space < 0)
2485                            return -1;
2486    #endif
2487                    if(b == ancestor)
2488                            break;
2489                    r = b->branchpoint;
2490                    if(!r)
2491                    {
2492                            /* Not a common ancestor */
2493                            r = colbr->branchpoint;
2494                            b = r->branch;
2495                            branchpoint = ancestor->branchpoint;
2496                            if(!branchpoint)
2497                            {
2498                                    stack_msg(MSG_WARN, "space_available: No common ancestor?");
2499                                    return 0;
2500                            }
2501                            ancestor = branchpoint->branch;
2502                            assert(ancestor != NULL);
2503                            nlinks = 0;
2504                            space = 0;
2505                            continue;       /* Restart with a new ancestor */
2506                    }
2507                    b = r->branch;
2508            }
2509            if(nl)
2510                    *nl = nlinks;           /* Return the number of links that can stretch */
2511            if(bpcommon)
2512                    *bpcommon = branchpoint;        /* Return the ancestral branchpoint on the common branch */
2513            return space;
2514    }
2515    
2516    static int stretch_branches(rcsfile_t *rcs, branch_t *br1, branch_t *br2, int totalstretch)
2517    {
2518            revision_t *r;
2519            revision_t *bpcommon = NULL;
2520            branch_t *ancestor = NULL;
2521            branch_t *b;
2522            int i;
2523            int space;
2524            int nlinks;
2525            int dy;
2526            int rest;
2527    
2528            space = space_available(rcs, br1, br2, &nlinks, &bpcommon);
2529            if(bpcommon)
2530                    ancestor = bpcommon->branch;
2531    
2532    #ifdef DEBUG
2533            if(space == -1)
2534                    return 0;
2535            fprintf(stderr, "stretch_branches: space available %d over %d links common %s\n", space, nlinks, ancestor->branch->branch);
2536    #endif
2537            if(space < totalstretch)
2538                    return 0;
2539    
2540            dy = totalstretch / nlinks;
2541            rest = totalstretch - dy * nlinks;
2542    
2543            r = br1->branchpoint;
2544            b = r->branch;
2545            while(1)
2546            {
2547                    int rtag = b == ancestor ? index_of_revision(bpcommon)+1 : 0;
2548                    for(i = index_of_revision(r); i >= rtag; i--)
2549                    {
2550                            int s, q;
2551                            if(i > 0)
2552                                    s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
2553                            else
2554                                    s = b->revs[i]->y - (b->y + b->h);
2555                            q = conf.rev_maxline - s;
2556                            if(q > 0)
2557                            {
2558                                    int d = rest ? rest/nlinks+1 : 0;
2559                                    if(q >= dy+d)
2560                                    {
2561                                            move_trunk(b->revs[i], dy+d);
2562                                    }
2563                                    else
2564                                    {
2565                                            move_trunk(b->revs[i], q);
2566                                            rest += dy+d - q;
2567                                    }
2568                                    rest -= d;
2569                                    nlinks--;
2570                            }
2571                    }
2572                    if(b == ancestor)
2573                            break;
2574                    r = b->branchpoint;
2575                    assert(r != NULL);      /* else 'space_available' wouldn't have returned positively */
2576                    b = r->branch;
2577            }
2578            return 1;
2579    }
2580    
2581    static branch_t *find_collision_branch(rcsfile_t *rcs, branch_t *b)
2582    {
2583            int i;
2584            int dist = INT_MAX;
2585            branch_t *col = NULL;
2586    
2587            for(i = 0; i < rcs->nbranches; i++)
2588            {
2589                    int t = branch_distance(rcs->branches[i], b);
2590                    if(t > 0 && t < dist)
2591                    {
2592                            dist = t;
2593                            col = rcs->branches[i];
2594                    }
2595            }
2596            return col;
2597    }
2598    
2599    static void auto_stretch(rcsfile_t *rcs)
2600    {
2601            int i;
2602            int safeguard;
2603    
2604            for(i = 0, safeguard = LOOPSAFEGUARD; i < rcs->nbranches && safeguard; i++)
2605            {
2606                    int bl, pr;
2607                    branch_t *b = rcs->branches[i];
2608                    if(!b->branchpoint)
2609                            continue;
2610                    branch_bbox(b, &bl, NULL, NULL, NULL);
2611                    branch_bbox(b->branchpoint->branch, NULL, &pr, NULL, NULL);
2612                    if(bl - conf.branch_margin - pr > 0)
2613                    {
2614                            branch_t *col;
2615                            int spaceneeded;
2616                            /* There is a potential to move branch b further left.
2617                             * All branches obstructing this one from moving further
2618                             * left must be originating from revisions below
2619                             * b->branchpoint until a common ancester.
2620                             * So, we search all branches for a branch that lies left
2621                             * of b and is closest to b. This is then the collission
2622                             * branch that needs to be moved.
2623                             */
2624                            col = find_collision_branch(rcs, b);
2625                            if(!col)
2626                                    continue;
2627                            spaceneeded = space_needed(col, b);
2628                            if(spaceneeded < 0)
2629                                    continue;
2630    #ifdef DEBUG
2631                            fprintf(stderr, "auto_stretch: %s collides %s need %d\n", b->branch->branch, col->branch->branch, spaceneeded);
2632    #endif
2633                            /* Trace the collision branch back to find the common ancester
2634                             * of both col and b. All revisions encountered while traversing
2635                             * backwards must be stretched, including all revisions on the
2636                             * common ancester from where the branches sprout.
2637                             */
2638                            if(stretch_branches(rcs, col, b, spaceneeded))
2639                            {
2640                                    if(kern_tree(rcs))
2641                                    {
2642                                            /* Restart the process because movement can
2643                                             * cause more movement.
2644                                             */
2645                                            i = 0 - 1;      /* -1 for the i++ of the loop */
2646                                            safeguard--;    /* Prevent infinite loop, just in case */
2647                                    }
2648                                    /*return;*/
2649                            }
2650                    }
2651            }
2652            if(!safeguard)
2653                    stack_msg(MSG_ERR, "auto_stretch: safeguard terminated possible infinite loop; please report.");
2654    }
2655    
2656    static void fold_branch(rcsfile_t *rcs, revision_t *r)
2657    {
2658            int i, j;
2659            branch_t *btag = NULL;
2660    
2661            for(i = 0; i < r->nbranches; i++)
2662            {
2663                    branch_t *b = r->branches[i];
2664                    if(!b->nrevs && b->ntags < 2)
2665                    {
2666                            /* No commits in this branch and no duplicate tags */
2667                            if(!btag)
2668                                    btag = b;
2669                            else
2670                            {
2671                                    /* We have consecutive empty branches, fold */
2672                                    b->folded = 1;
2673                                    b->folded_to = btag;
2674                                    for(j = 0; j < rcs->nbranches; j++)
2675                                    {
2676                                            if(b == rcs->branches[j])
2677                                            {
2678                                                    /* Zap the branch from the admin */
2679                                                    memmove(&rcs->branches[j],
2680                                                            &rcs->branches[j+1],
2681                                                            (rcs->nbranches - j - 1)*sizeof(rcs->branches[0]));
2682                                                    rcs->nbranches--;
2683                                                    break;
2684                                            }
2685    
2686                                    }
2687                                    memmove(&r->branches[i], &r->branches[i+1], (r->nbranches - i - 1)*sizeof(r->branches[0]));
2688                                    r->nbranches--;
2689                                    i--;    /* We have one less now */
2690    
2691                                    /* Add to the fold-list */
2692                                    btag->folds = xrealloc(btag->folds, (btag->nfolds+1) * sizeof(btag->folds[0]));
2693                                    btag->folds[btag->nfolds] = b;
2694                                    btag->nfolds++;
2695                            }
2696                    }
2697                    else
2698                    {
2699                            if(!conf.branch_foldall)
2700                                    btag = NULL;    /* Start a new box */
2701                            /* Recursively fold sub-branches */
2702                            for(j = 0; j < b->nrevs; j++)
2703                                    fold_branch(rcs, b->revs[j]);
2704                    }
2705            }
2706    }
2707    
2708    static void mark_subtree(branch_t *b)
2709    {
2710            int i, j;
2711            b->subtree_draw = 1;
2712            for(i = 0; i < b->nrevs; i++)
2713            {
2714                    for(j = 0; j < b->revs[i]->nbranches; j++)
2715                            mark_subtree(b->revs[i]->branches[j]);
2716            }
2717    }
2718    
2719    static void make_layout(rcsfile_t *rcs)
2720  {  {
2721          int i, j;          int i, j;
2722          int x, y;          int x, y;
2723          int w, h;          int w, h;
2724          int w2;          int w2;
2725          int moved;  
2726            /* Remove all unwanted revisions */
2727            if(conf.strip_untagged)
2728            {
2729                    int fr = conf.strip_first_rev ? 0 : 1;
2730                    for(i = 0; i < rcs->nbranches; i++)
2731                    {
2732                            branch_t *bp = rcs->branches[i];
2733                            for(j = fr; j < bp->nrevs-1; j++)
2734                            {
2735                                    if(!bp->revs[j]->ntags && !bp->revs[j]->nbranches)
2736                                    {
2737                                            memmove(&bp->revs[j], &bp->revs[j+1], (bp->nrevs-j-1) * sizeof(bp->revs[0]));
2738                                            bp->nrevs--;
2739                                            bp->revs[j]->stripped = 1;
2740                                            j--;
2741                                    }
2742                            }
2743                    }
2744            }
2745    
2746            /* Find the sub-tree(s) we want to see */
2747            if(conf.branch_subtree && conf.branch_subtree[0])
2748            {
2749                    branch_t **b;
2750                    revision_t **r;
2751                    rev_t rev;
2752                    int k;
2753                    char *tag = conf.branch_subtree;
2754    
2755                    /* First translate any symbolic tag to a real branch/revision number */
2756                    if(rcs->tags)
2757                    {
2758                            for(k = 0; k < rcs->tags->ntags; k++)
2759                            {
2760                                    if(!strcmp(conf.branch_subtree, rcs->tags->tags[k]->tag))
2761                                    {
2762                                            if(rcs->tags->tags[k]->rev->isbranch)
2763                                                    tag = rcs->tags->tags[k]->rev->branch;
2764                                            else
2765                                                    tag = rcs->tags->tags[k]->rev->rev;
2766                                            break;
2767                                    }
2768                            }
2769                    }
2770    
2771                    /* Find the corresponding branch */
2772                    rev.branch = tag;
2773                    rev.rev = NULL;
2774                    rev.isbranch = 1;
2775                    b = bsearch(&rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
2776                    if(b)
2777                    {
2778                            if((*b)->branchpoint)
2779                            {
2780                                    subtree_branch = *b;
2781                                    for(k = 0; k < (*b)->branchpoint->nbranches; k++)
2782                                            mark_subtree((*b)->branchpoint->branches[k]);
2783                            }
2784                            /*
2785                             * else -> we want everything.
2786                             * This happens for the top level branch because it has no
2787                             * branchpoint. We do not set the subtree_branch, which then
2788                             * results in drawing the whole tree as if we did not select a
2789                             * particular branch.
2790                             */
2791                    }
2792                    else
2793                    {
2794                            /* Maybe it is a revision we want all subtrees from */
2795                            rev.rev = tag;
2796                            rev.branch = NULL;
2797                            rev.isbranch = 0;
2798                            r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
2799                            if(r)
2800                            {
2801                                    if((*r)->nbranches)
2802                                    {
2803                                            subtree_branch = (*r)->branches[0];
2804                                            subtree_rev = *r;
2805                                            for(k = 0; k < (*r)->nbranches; k++)
2806                                                    mark_subtree((*r)->branches[k]);
2807                                    }
2808                                    /*
2809                                     * else -> we select everything.
2810                                     * This happens for the any revision that has no branches.
2811                                     * We do not set the subtree_branch, which then results in
2812                                     * drawing the whole tree as if we did not select a
2813                                     * particular revision's branches.
2814                                     */
2815                            }
2816                    }
2817            }
2818    
2819            /* Fold all empty branches in one box on the same branchpoint */
2820            if(conf.branch_fold)
2821            {
2822                    for(i = 0; i < rcs->branches[0]->nrevs; i++)
2823                    {
2824                            if(rcs->branches[0]->revs[i]->nbranches > 0)
2825                                    fold_branch(rcs, rcs->branches[0]->revs[i]);
2826                    }
2827            }
2828    
2829            /* Remove all unwanted tags */
2830            for(i = 0; i < rcs->nbranches; i++)
2831            {
2832                    branch_t *bp = rcs->branches[i];
2833                    for(j = 0; j < bp->nrevs; j++)
2834                    {
2835                            revision_t *r = bp->revs[j];
2836                            int k;
2837                            for(k = 0; k < r->ntags; k++)
2838                            {
2839                                    if(r->tags[k]->ignore > 0)
2840                                    {
2841                                            memmove(&r->tags[k], &r->tags[k+1], (r->ntags-k-1) * sizeof(r->tags[0]));
2842                                            r->ntags--;
2843                                            k--;
2844                                    }
2845                            }
2846                    }
2847            }
2848    
2849          /* Calculate the box-sizes of the revisions */          /* Calculate the box-sizes of the revisions */
2850          for(i = 0; i < rcs->nsrev; i++)          for(i = 0; i < rcs->nsrev; i++)
# Line 1186  Line 2862 
2862                  for(j = 0; j < rp->ntags; j++)                  for(j = 0; j < rp->ntags; j++)
2863                  {                  {
2864                          int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);                          int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);
2865                            int th;
2866                          if(ww > w) w = ww;                          if(ww > w) w = ww;
2867                          h += get_sheight(rp->tags[j]->tag, &conf.tag_font) + conf.rev_separator;                          th = get_sheight(rp->tags[j]->tag, &conf.tag_font) + conf.rev_separator;
2868                            rp->tags[j]->yofs = h + th/2 + conf.rev_tspace;
2869                            h += th;
2870                  }                  }
2871                  rp->w = w + conf.rev_lspace + conf.rev_rspace;                  rp->w = w + conf.rev_lspace + conf.rev_rspace;
2872                  rp->h = h + conf.rev_tspace + conf.rev_bspace;                  rp->h = h + conf.rev_tspace + conf.rev_bspace;
# Line 1199  Line 2878 
2878                  branch_t *bp = rcs->branches[i];                  branch_t *bp = rcs->branches[i];
2879                  int w;                  int w;
2880                  int h;                  int h;
2881                  w = get_swidth(bp->branch->branch, &conf.branch_font);                  if(!bp->nfolds)
                 h = get_sheight(bp->branch->branch, &conf.branch_font);  
                 for(j = 0; j < bp->ntags; j++)  
2882                  {                  {
2883                          int ww = get_swidth(bp->tags[j]->tag, &conf.branch_font);                          w = get_swidth(bp->branch->branch, &conf.branch_font);
2884                          if(ww > w) w = ww;                          h = get_sheight(bp->branch->branch, &conf.branch_font);
2885                          h += get_sheight(bp->tags[j]->tag, &conf.branch_font);                          for(j = 0; j < bp->ntags; j++)
2886                            {
2887                                    int ww = get_swidth(bp->tags[j]->tag, &conf.branch_tag_font);
2888                                    if(ww > w) w = ww;
2889                                    h += get_sheight(bp->tags[j]->tag, &conf.branch_tag_font);
2890                            }
2891                    }
2892                    else
2893                    {
2894                            int h1, h2;
2895                            int w1, w2;
2896                            int fw;
2897                            w1 = get_swidth(bp->branch->branch, &conf.branch_font);
2898                            w1 += get_swidth(" ", &conf.branch_font);
2899                            w2 = get_swidth(bp->tags[0]->tag, &conf.branch_tag_font);
2900                            fw = w1;
2901                            w = w1 + w2;
2902                            h1 = get_sheight(bp->branch->branch, &conf.branch_font);
2903                            h2 = get_sheight(bp->tags[0]->tag, &conf.branch_tag_font);
2904                            h = MAX(h1, h2);
2905                            for(j = 0; j < bp->nfolds; j++)
2906                            {
2907                                    w1 = get_swidth(bp->folds[j]->branch->branch, &conf.branch_font);
2908                                    w1 += get_swidth(" ", &conf.branch_font);
2909                                    w2 = get_swidth(bp->folds[j]->tags[0]->tag, &conf.branch_tag_font);
2910                                    if(w1 > fw)
2911                                            fw = w1;
2912                                    if(w1 + w2 > w)
2913                                            w = w1 + w2;
2914                                    h1 = get_sheight(bp->folds[j]->branch->branch, &conf.branch_font);
2915                                    h2 = get_sheight(bp->folds[j]->tags[0]->tag, &conf.branch_tag_font);
2916                                    h += MAX(h1, h2);
2917                            }
2918                            bp->fw = fw;
2919                  }                  }
2920                  w += conf.branch_lspace + conf.branch_rspace;                  w += conf.branch_lspace + conf.branch_rspace;
2921                  h += conf.branch_tspace + conf.branch_bspace;                  h += conf.branch_tspace + conf.branch_bspace;
2922                  bp->w = w;                  bp->w = w;
2923                  bp->h = h;                  bp->h = h;
2924                  for(j = 0; j < bp->nrevs; j++)                  if(conf.left_right)
2925                    {
2926                            for(j = 0; j < bp->nrevs; j++)
2927                            {
2928                                    if(bp->revs[j]->h > h)
2929                                            h = bp->revs[j]->h;
2930                                    w += bp->revs[j]->w + conf.rev_minline;
2931                            }
2932                            if(conf.branch_dupbox && bp->nrevs)
2933                                    w += bp->w + conf.rev_minline;
2934                    }
2935                    else
2936                  {                  {
2937                          if(bp->revs[j]->w > w)                          for(j = 0; j < bp->nrevs; j++)
2938                                  w = bp->revs[j]->w;                          {
2939                          h += bp->revs[j]->h + conf.rev_minline;                                  if(bp->revs[j]->w > w)
2940                                            w = bp->revs[j]->w;
2941                                    h += bp->revs[j]->h + conf.rev_minline;
2942                            }
2943                            if(conf.branch_dupbox && bp->nrevs)
2944                                    h += bp->h + conf.rev_minline;
2945                  }                  }
2946                  bp->th = h;                  bp->th = h;
2947                  bp->tw = w;                  bp->tw = w;
2948          }          }
2949    
2950          /* Calculate the relative positions of revs in a branch */          /* Calculate the relative positions of revs in a branch */
2951          for(i = 0; i < rcs->nbranches; i++)          if(conf.left_right)
2952          {          {
2953                  branch_t *b = rcs->branches[i];                  for(i = 0; i < rcs->nbranches; i++)
                 x = b->tw/2;  
                 y = b->h;  
                 b->cx = x;  
                 b->y = 0;  
                 for(j = 0; j < b->nrevs; j++)  
2954                  {                  {
2955                          y += conf.rev_minline;                          branch_t *b = rcs->branches[i];
2956                          b->revs[j]->cx = x;                          y = b->th/2;
2957                          b->revs[j]->y = y;                          x = b->w;
2958                          y += b->revs[j]->h;                          b->y = y;
2959                            b->cx = 0;
2960                            for(j = 0; j < b->nrevs; j++)
2961                            {
2962                                    x += conf.rev_minline;
2963                                    b->revs[j]->y = y;
2964                                    b->revs[j]->cx = x;
2965                                    x += b->revs[j]->w;
2966                            }
2967                  }                  }
2968          }          }
2969            else
         /* Reposition the branches */  
         x = rcs->branches[0]->cx;  
         w2 = rcs->branches[0]->tw / 2;  
         for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)  
2970          {          {
2971                  reposition_branch(rcs->branches[0]->revs[i], &x, &w2);                  for(i = 0; i < rcs->nbranches; i++)
2972                    {
2973                            branch_t *b = rcs->branches[i];
2974                            x = b->tw/2;
2975                            y = b->h;
2976                            b->cx = x;
2977                            b->y = 0;
2978                            for(j = 0; j < b->nrevs; j++)
2979                            {
2980                                    y += conf.rev_minline;
2981                                    b->revs[j]->cx = x;
2982                                    b->revs[j]->y = y;
2983                                    y += b->revs[j]->h;
2984                            }
2985                    }
2986          }          }
2987    
2988          /* Try to move branches left if there is room (kerning) */          /* Initially reposition the branches from bottom to top progressively right */
2989          for(moved = 1; moved; )          if(conf.left_right)
2990          {          {
2991                  moved = 0;                  x = rcs->branches[0]->y;
2992                  for(i = 1; i < rcs->nbranches; i++)                  w2 = rcs->branches[0]->th / 2;
2993                    for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
2994                    {
2995                            initial_reposition_branch_lr(rcs->branches[0]->revs[i], &x, &w2);
2996                    }
2997            }
2998            else
2999            {
3000                    x = rcs->branches[0]->cx;
3001                    w2 = rcs->branches[0]->tw / 2;
3002                    for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
3003                  {                  {
3004                          moved += kern_branch(rcs, rcs->branches[i]);                          initial_reposition_branch(rcs->branches[0]->revs[i], &x, &w2);
3005                  }                  }
3006          }          }
3007    
3008          /* Move everything w.r.t. the top-left margin */          /* Initially move branches left if there is room */
3009          for(i = 0; i < rcs->nbranches; i++)          kern_tree(rcs);
3010                  move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);  
3011            /* Try to kern the branches more by expanding the inter-revision spacing */
3012            if(conf.auto_stretch && !conf.left_right)
3013                    auto_stretch(rcs);
3014    
3015          /* Calculate overall image size */          /* Calculate overall image size */
3016          x = rcs->branches[0]->cx - rcs->branches[0]->tw/2;          if(conf.left_right)
3017          y = rcs->branches[0]->y;          {
3018                    x = rcs->branches[0]->cx;
3019                    y = rcs->branches[0]->y - rcs->branches[0]->th/2;
3020            }
3021            else
3022            {
3023                    x = rcs->branches[0]->cx - rcs->branches[0]->tw/2;
3024                    y = rcs->branches[0]->y;
3025            }
3026          w = rcs->branches[0]->tw;          w = rcs->branches[0]->tw;
3027          h = rcs->branches[0]->th;          h = rcs->branches[0]->th;
3028          for(i = 1; i < rcs->nbranches; i++)          for(i = 1; i < rcs->nbranches; i++)
3029                  rect_union(&x, &y, &w, &h, rcs->branches[i]);                  rect_union(&x, &y, &w, &h, rcs->branches[i]);
3030          rcs->tw = w;          rcs->tw = w;
3031          rcs->th = h;          rcs->th = h;
3032    
3033            /* Flip the entire tree */
3034            if(conf.upside_down)
3035            {
3036                    if(conf.left_right)
3037                    {
3038                            x += rcs->tw;
3039                            for(i = 0; i < rcs->nbranches; i++)
3040                            {
3041                                    branch_t *b = rcs->branches[i];
3042                                    for(j = 0; j < b->nrevs; j++)
3043                                    {
3044                                            revision_t *r = b->revs[j];
3045                                            r->cx = x - r->cx - r->w;
3046                                    }
3047                                    b->cx = x - b->cx - b->w;
3048                            }
3049                    }
3050                    else
3051                    {
3052                            y += rcs->th;
3053                            for(i = 0; i < rcs->nbranches; i++)
3054                            {
3055                                    branch_t *b = rcs->branches[i];
3056                                    for(j = 0; j < b->nrevs; j++)
3057                                    {
3058                                            revision_t *r = b->revs[j];
3059                                            r->y = y - r->y - r->h;
3060                                    }
3061                                    b->y = y - b->y - b->h;
3062                            }
3063                    }
3064            }
3065    
3066            /* Relocate the lot if we only draw a sub-tree */
3067            if(subtree_branch)
3068            {
3069                    int xx, yy;
3070    
3071                    if(subtree_branch->folded)      /* Fix the reference if the branch got folded */
3072                            subtree_branch = subtree_branch->folded_to;
3073    
3074                    xx = conf.left_right ? subtree_branch->cx : subtree_branch->cx - subtree_branch->tw/2;
3075                    yy = conf.left_right ? subtree_branch->y - subtree_branch->th/2 : subtree_branch->y;
3076                    if(subtree_branch != rcs->branches[0])
3077                    {
3078                            if(conf.left_right)
3079                                    xx -= conf.branch_connect;
3080                            else
3081                                    yy -= conf.branch_connect;
3082                    }
3083                    for(i = 0; i < rcs->nbranches; i++)
3084                            move_branch(rcs->branches[i], -xx, -yy);
3085            }
3086    
3087            /* Move everything w.r.t. the top-left margin */
3088            for(i = 0; i < rcs->nbranches; i++)
3089                    move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
3090  }  }
3091    
3092  /*  /*
# Line 1276  Line 3094 
3094   * Imagemap functions   * Imagemap functions
3095   **************************************************************************   **************************************************************************
3096   */   */
3097  void make_imagemap(rcsfile_t *rcs, FILE *fp)  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)
3098    {
3099            char *href = expand_string(conf.map_merge_href, rcs, tr, tr->rev, fr->rev, NULL);
3100            char *alt = expand_string(conf.map_merge_alt, rcs, tr, tr->rev, fr->rev, NULL);
3101            const char *htp = conf.html_level == HTMLLEVEL_X ? " /" : "";
3102    
3103            if(x1 > 0 && x2 > 0 && y1 > 0 && y2 > 0)
3104                    fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3105                                            href, x1, y1, x2, y2, alt, htp);
3106            xfree(alt);
3107            xfree(href);
3108    
3109            if(im)
3110            {
3111                    gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3112                    gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3113                    gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3114            }
3115    }
3116    
3117    static void map_merges(rcsfile_t *rcs, FILE *fp, gdImagePtr im)
3118    {
3119            int i;
3120            int tagh2 = get_sheight("Hg", &conf.tag_font) / 2;
3121            int bm = conf.branch_margin / 2;
3122    
3123            for(i = 0; i < rcs->nmerges; i++)
3124            {
3125                    revision_t *fr = rcs->merges[i].from->logrev;
3126                    revision_t *tr = rcs->merges[i].to->logrev;
3127                    int x1, x2, y1, y2;
3128                    if(!fr || !tr || fr == tr)
3129                            continue;       /* This can happen with detached tags and self-references */
3130                    if(conf.left_right)
3131                    {
3132                            if(fr->branch == tr->branch)
3133                            {
3134                                    y1 = fr->y - fr->h/2;
3135                                    y2 = tr->y - tr->h/2;
3136                            }
3137                            else
3138                            {
3139                                    if(fr->y < tr->y)
3140                                    {
3141                                            y1 = fr->y + fr->h/2;
3142                                            y2 = tr->y - tr->h/2;
3143                                    }
3144                                    else
3145                                    {
3146                                            y1 = fr->y - fr->h/2;
3147                                            y2 = tr->y + tr->h/2;
3148                                    }
3149                            }
3150                            x1 = fr->cx + fr->w/2;
3151                            x2 = tr->cx + tr->w/2;
3152                    }
3153                    else
3154                    {
3155                            if(fr->branch == tr->branch)
3156                            {
3157                                    x1 = fr->cx - fr->w/2;
3158                                    x2 = tr->cx - tr->w/2;
3159                            }
3160                            else
3161                            {
3162                                    if(fr->cx < tr->cx)
3163                                    {
3164                                            x1 = fr->cx + fr->w/2;
3165                                            x2 = tr->cx - tr->w/2;
3166                                    }
3167                                    else
3168                                    {
3169                                            x1 = fr->cx - fr->w/2;
3170                                            x2 = tr->cx + tr->w/2;
3171                                    }
3172                            }
3173                            y1 = fr->y + rcs->merges[i].from->yofs;
3174                            y2 = tr->y + rcs->merges[i].to->yofs;
3175                    }
3176    
3177                    if(conf.left_right)
3178                    {
3179                            if(fr->branch == tr->branch)
3180                            {
3181                                    map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-bm, x1+bm, y1);
3182                                    map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-bm, x2+bm, y2);
3183                            }
3184                            else
3185                            {
3186                                    if(y1 > y2)
3187                                    {
3188                                            map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-bm, x1+bm, y1);
3189                                            map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2, x2+bm, y2+bm);
3190                                    }
3191                                    else
3192                                    {
3193                                            map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1, x1+bm, y1+bm);
3194                                            map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-bm, x2+bm, y2);
3195                                    }
3196                            }
3197                    }
3198                    else
3199                    {
3200                            if(fr->branch == tr->branch)
3201                            {
3202                                    map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-tagh2, x1, y1+tagh2);
3203                                    map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-tagh2, x2, y2+tagh2);
3204                            }
3205                            else
3206                            {
3207                                    if(x1 > x2)
3208                                    {
3209                                            map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-tagh2, x1, y1+tagh2);
3210                                            map_merge_box(rcs, fp, fr, tr, im, x2, y2-tagh2, x2+bm, y2+tagh2);
3211                                    }
3212                                    else
3213                                    {
3214                                            map_merge_box(rcs, fp, fr, tr, im, x1, y1-tagh2, x1+bm, y1+tagh2);
3215                                            map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-tagh2, x2, y2+tagh2);
3216                                    }
3217                            }
3218                    }
3219            }
3220    }
3221    
3222    static void make_imagemap(rcsfile_t *rcs, FILE *fp, gdImagePtr im)
3223  {  {
3224          int i, j;          int i, j;
3225          char *href;          const char *htp = conf.html_level == HTMLLEVEL_X ? " /" : "";
3226          char *alt;  
3227          fprintf(fp, "<map name=\"%s\">\n", conf.map_name);          switch(conf.html_level)
3228            {
3229            case HTMLLEVEL_4:
3230                    fprintf(fp, "<map name=\"%s\" id=\"%s\">\n", conf.map_name, conf.map_name);
3231                    break;
3232            case HTMLLEVEL_X:
3233                    fprintf(fp, "<map id=\"%s\">\n", conf.map_name);
3234                    break;
3235            default:
3236                    fprintf(fp, "<map name=\"%s\">\n", conf.map_name);
3237            }
3238    
3239          for(i = 0; i < rcs->nbranches; i++)          for(i = 0; i < rcs->nbranches; i++)
3240          {          {
3241                  branch_t *b = rcs->branches[i];                  branch_t *b = rcs->branches[i];
3242                  tag_t *tag = b->ntags ? b->tags[0] : NULL;                  tag_t *tag = b->ntags ? b->tags[0] : NULL;
3243                  href = expand_string(conf.map_branch_href, rcs, NULL, b->branch, NULL, tag);                  char *bhref;
3244                  alt = expand_string(conf.map_branch_alt, rcs, NULL, b->branch, NULL, tag);                  char *balt;
3245                  fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",                  int x1;
3246                                  href,                  int x2;
3247                                  b->cx - b->w/2, b->y, b->cx + b->w/2, b->y + b->h,                  int y1;
3248                                  alt);                  int y2;
3249                  xfree(href);  
3250                  xfree(alt);                  if(subtree_branch && !b->subtree_draw)
3251                            continue;
3252    
3253                    bhref = expand_string(conf.map_branch_href, rcs, NULL, b->branch, NULL, tag);
3254                    balt = expand_string(conf.map_branch_alt, rcs, NULL, b->branch, NULL, tag);
3255    
3256                    if(!b->nfolds)
3257                    {
3258                            if(conf.left_right)
3259                            {
3260                                    x1 = b->cx;
3261                                    y1 = b->y - b->h/2;
3262                                    x2 = b->cx + b->w;
3263                                    y2 = b->y + b->h/2;
3264                            }
3265                            else
3266                            {
3267                                    x1 = b->cx - b->w/2;
3268                                    y1 = b->y;
3269                                    x2 = b->cx + b->w/2;
3270                                    y2 = b->y + b->h;
3271                            }
3272                            fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3273                                            bhref, x1, y1, x2, y2, balt, htp);
3274                            if(im)
3275                            {
3276                                    gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3277                                    gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3278                                    gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3279                            }
3280                    }
3281                    else
3282                    {
3283                            int yy1, yy2, yy;
3284                            if(conf.left_right)
3285                            {
3286                                    x1 = b->cx + conf.branch_lspace;
3287                                    y1 = b->y - b->h/2 + conf.branch_tspace;
3288                            }
3289                            else
3290                            {
3291                                    x1 = b->cx - b->w/2 + conf.branch_lspace;
3292                                    y1 = b->y + conf.branch_tspace;
3293                            }
3294                            x2 = x1 + b->w - conf.branch_rspace;
3295    
3296                            yy1 = get_sheight(b->branch->branch, &conf.branch_font);
3297                            yy2 = get_sheight(b->tags[0]->tag, &conf.branch_tag_font);
3298                            yy = MAX(yy1, yy2);
3299                            y2 = y1 + yy;
3300                            fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3301                                            bhref, x1, y1, x2, y2, balt, htp);
3302    
3303                            y1 += yy;
3304                            y2 += yy;
3305                            for(j = 0; j < b->nfolds; j++)
3306                            {
3307                                    branch_t *fb = b->folds[j];
3308                                    tag_t *t = fb->tags[0];
3309                                    xfree(bhref);
3310                                    xfree(balt);
3311                                    bhref = expand_string(conf.map_branch_href, rcs, NULL, fb->branch, NULL, t);
3312                                    balt = expand_string(conf.map_branch_alt, rcs, NULL, fb->branch, NULL, t);
3313                                    fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3314                                                    bhref, x1, y1, x2, y2, balt, htp);
3315                                    yy1 = get_sheight(fb->branch->branch, &conf.branch_font);
3316                                    yy2 = get_sheight(fb->tags[0]->tag, &conf.branch_tag_font);
3317                                    yy = MAX(yy1, yy2);
3318                                    y1 += yy;
3319                                    y2 += yy;
3320                            }
3321                    }
3322    
3323                  for(j = 0; j < b->nrevs; j++)                  for(j = 0; j < b->nrevs; j++)
3324                  {                  {
3325                          revision_t *r = b->revs[j];                          revision_t *r = b->revs[j];
3326                          revision_t* r1;                          revision_t* r1;
3327                          int xoff;                          int xoff = 1;
3328                          int x1;                          int yoff = 1;
3329                          int x2;                          char *href;
3330                          int y1;                          char *alt;
3331    
3332                          tag = r->ntags ? r->tags[0] : NULL;                          tag = r->ntags ? r->tags[0] : NULL;
3333                          href = expand_string(conf.map_rev_href, rcs, r, r->rev, NULL, tag);                          href = expand_string(conf.map_rev_href, rcs, r, r->rev, NULL, tag);
3334                          alt = expand_string(conf.map_rev_alt, rcs, r, r->rev, NULL, tag);                          alt = expand_string(conf.map_rev_alt, rcs, r, r->rev, NULL, tag);
3335                          fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",                          if(conf.left_right)
3336                                  href,                          {
3337                                  r->cx - r->w/2, r->y, r->cx + r->w/2, r->y + r->h,                                  x1 = r->cx;
3338                                  alt);                                  y1 = r->y - r->h/2;
3339                                    x2 = r->cx + r->w;
3340                                    y2 = r->y + r->h/2;
3341                            }
3342                            else
3343                            {
3344                                    x1 = r->cx - r->w/2;
3345                                    y1 = r->y;
3346                                    x2 = r->cx + r->w/2;
3347                                    y2 = r->y + r->h;
3348                            }
3349                            fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3350                                    href, x1, y1, x2, y2, alt, htp);
3351                            if(im)
3352                            {
3353                                    gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3354                                    gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3355                                    gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3356                            }
3357                          xfree(href);                          xfree(href);
3358                          xfree(alt);                          xfree(alt);
3359                          if ( j > 0 || b->branchpoint )                          if(j > 0 || b->branchpoint)
3360                          {                          {
3361                              if ( j > 0 )                                  if(j > 0)
3362                              {                                  {
3363                                  r1 = b->revs[j-1];                                          r1 = b->revs[j-1];
3364                                  xoff = MIN(r->w, r1->w)/4;                                          if(conf.left_right)
3365                                  y1 = r1->y + r1->h;                                          {
3366                              }                                                  yoff = MIN(r->h, r1->h)/4;
3367                              else         &