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