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

Diff of /cvsgraph/cvsgraph.c

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

revision 1.13, Tue Nov 20 15:00:06 2001 UTC revision 1.57, Wed Jun 22 19:41:10 2005 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_cvsnt(rcsfile_t *rcs)
744    {
745            int i;
746    
747            if(!conf.merge_cvsnt)
748                    return;
749    
750            for(i = 0; i < rcs->nsrev; i++)
751            {
752                    revision_t **r;
753    
754                    if(!rcs->srev[i]->delta->mergepoint)
755                            continue;
756    
757                    r = bsearch(rcs->srev[i]->delta->mergepoint->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
758                    if(!r)
759                            continue;
760                    rcs->merges = xrealloc(rcs->merges, sizeof(rcs->merges[0]) * (rcs->nmerges+1));
761                    rcs->merges[rcs->nmerges].type = TR_REVISION;
762                    rcs->merges[rcs->nmerges].from.rev = *r;
763                    rcs->merges[rcs->nmerges].to.rev = rcs->srev[i];
764                    rcs->nmerges++;
765                    (*r)->stripped = -1;
766                    rcs->srev[i]->stripped = -1;
767            }
768    }
769    
770    static void find_merges(rcsfile_t *rcs)
771    {
772            int i;
773            int err;
774            int rcflags = REG_EXTENDED | (conf.merge_nocase ? REG_ICASE : 0);
775            regex_t *refrom = NULL;
776            regex_t *reto = NULL;
777            regmatch_t *matchfrom = NULL;
778    
779            if(!conf.merge_from || !conf.merge_from[0] || !conf.merge_to || !conf.merge_to[0])
780                    return;
781    
782            refrom = xmalloc(sizeof(*refrom));
783            reto = xmalloc(sizeof(*reto));
784    
785            /* Compile the 'from' regex match for merge identification */
786            err = regcomp(refrom, conf.merge_from, rcflags);
787            if(err)
788            {
789                    char *msg;
790                    i = regerror(err, refrom, NULL, 0);
791                    msg = xmalloc(i+1);
792                    regerror(err, refrom, msg, i+1);
793                    stack_msg(MSG_WARN, "%s", msg);
794                    xfree(msg);
795                    xfree(refrom);
796                    xfree(reto);
797                    return;
798            }
799            else
800                    matchfrom = xmalloc((refrom->re_nsub+1) * sizeof(*matchfrom));
801    
802            for(i = 0; i < rcs->tags->ntags; i++)
803            {
804                    tag_t *t = rcs->tags->tags[i];
805    
806                    /* Must be revision tags and not detached */
807                    if(t->rev->isbranch || !t->logrev)
808                            continue;
809    
810                    /* Try to find merge tag matches */
811                    if(!regexec(refrom, t->tag, refrom->re_nsub+1, matchfrom, 0))
812                    {
813                            int n;
814                            char *to;
815    
816                            to = build_regex(refrom->re_nsub+1, matchfrom, t->tag);
817                            if(to)
818                            {
819                                    err = regcomp(reto, to, rcflags);
820                                    if(err)
821                                    {
822                                            char *msg;
823                                            i = regerror(err, reto, NULL, 0);
824                                            msg = xmalloc(i+1);
825                                            regerror(err, reto, msg, i+1);
826                                            stack_msg(MSG_WARN, "%s", msg);
827                                            xfree(msg);
828                                    }
829                                    else if(!err)
830                                    {
831                                            for(n = 0; n < rcs->tags->ntags; n++)
832                                            {
833                                                    tag_t *nt = rcs->tags->tags[n];
834                                                    /* From and To never should match the same tag or belong to a branch */
835                                                    if(n == i || nt->rev->isbranch || !nt->logrev)
836                                                            continue;
837    
838                                                    if(!regexec(reto, nt->tag, 0, NULL, 0))
839                                                    {
840                                                            /* Tag matches */
841                                                            rcs->merges = xrealloc(rcs->merges,
842                                                                            sizeof(rcs->merges[0]) * (rcs->nmerges+1));
843                                                            rcs->merges[rcs->nmerges].type = TR_TAG;
844                                                            rcs->merges[rcs->nmerges].to.tag = nt;
845                                                            rcs->merges[rcs->nmerges].from.tag = t;
846                                                            rcs->nmerges++;
847                                                            if(!conf.tag_ignore_merge)
848                                                            {
849                                                                    nt->ignore = 0;
850                                                                    t->ignore = 0;
851                                                            }
852                                                            /* We cannot (should not) match multiple times */
853                                                            if(!conf.merge_findall)
854                                                                    break;
855                                                    }
856                                            }
857                                            regfree(reto);
858                                    }
859                                    xfree(to);
860                            }
861                    }
862            }
863            if(matchfrom)   xfree(matchfrom);
864            if(refrom)      { regfree(refrom); xfree(refrom); }
865            if(reto)        xfree(reto);
866    }
867    
868    static void assign_tags(rcsfile_t *rcs)
869  {  {
870          int i;          int i;
871          int nr;          int nr;
872            regex_t *regextag = NULL;
873    
874            if(conf.tag_ignore && conf.tag_ignore[0])
875            {
876                    int err;
877                    regextag = xmalloc(sizeof(*regextag));
878                    err = regcomp(regextag, conf.tag_ignore, REG_EXTENDED | REG_NOSUB | (conf.tag_nocase ? REG_ICASE : 0));
879                    if(err)
880                    {
881                            char *msg;
882                            i = regerror(err, regextag, NULL, 0);
883                            msg = xmalloc(i+1);
884                            regerror(err, regextag, msg, i+1);
885                            stack_msg(MSG_WARN, "%s", msg);
886                            xfree(msg);
887                            xfree(regextag);
888                            regextag = NULL;
889                    }
890            }
891    
892          for(i = nr = 0; i < rcs->nbranches; i++)          for(i = nr = 0; i < rcs->nbranches; i++)
893                  nr += rcs->branches[i]->nrevs;                  nr += rcs->branches[i]->nrevs;
# Line 627  Line 917 
917          }          }
918    
919          /* We should have at least two tags (HEAD and MAIN) */          /* We should have at least two tags (HEAD and MAIN) */
920          assert(rcs->tags != 0);          assert(rcs->tags != NULL);
921    
922          for(i = 0; i < rcs->tags->ntags; i++)          for(i = 0; i < rcs->tags->ntags; i++)
923          {          {
# Line 652  Line 942 
942                                  xfree(rev.rev);                                  xfree(rev.rev);
943                                  if(!r)                                  if(!r)
944                                  {                                  {
945                                          if(!quiet)                                          stack_msg(MSG_WARN, "No branch found for tag '%s:%s'", t->tag, t->rev->branch);
                                                 fprintf(stderr, "No branch found for tag '%s:%s'\n", t->tag, t->rev->branch);  
946                                  }                                  }
947                                  else                                  else
948                                  {                                  {
# Line 683  Line 972 
972                          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);
973                          if(!r)                          if(!r)
974                          {                          {
975                                  if(!quiet)                                  stack_msg(MSG_WARN, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);
                                         fprintf(stderr, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);  
976                          }                          }
977                          else                          else
978                          {                          {
979                                  revision_t *rr = *r;                                  revision_t *rr = *r;
980                                  rr->tags = xrealloc(rr->tags, (rr->ntags+1)*sizeof(rr->tags[0]));                                  t->logrev = rr;
981                                  rr->tags[rr->ntags] = t;                                  if(!conf.rev_maxtags || rr->ntags <= conf.rev_maxtags)
982                                  rr->ntags++;                                  {
983                                            rr->tags = xrealloc(rr->tags, (rr->ntags+1)*sizeof(rr->tags[0]));
984                                            if(conf.rev_maxtags && rr->ntags == conf.rev_maxtags)
985                                            {
986                                                    rr->tags[rr->ntags] = xmalloc(sizeof(tag_t));
987                                                    rr->tags[rr->ntags]->tag = xstrdup("...");
988                                                    rr->tags[rr->ntags]->rev = t->rev;
989                                            }
990                                            else
991                                                    rr->tags[rr->ntags] = t;
992                                            rr->ntags++;
993                                    }
994                            }
995    
996                            if(conf.tag_negate)
997                                    t->ignore++;
998                            /* Mark the tag ignored if it matches the configuration */
999                            if(regextag && !regexec(regextag, t->tag, 0, NULL, 0))
1000                            {
1001                                    if(conf.tag_negate)
1002                                            t->ignore--;
1003                                    else
1004                                            t->ignore++;
1005                          }                          }
1006                  }                  }
1007          }          }
# Line 708  Line 1018 
1018                  *b = rcs->branches[0];                  *b = rcs->branches[0];
1019                  rcs->branches[0] = t;                  rcs->branches[0] = t;
1020          }          }
1021          return 1;  
1022            if(regextag)
1023            {
1024                    regfree(regextag);
1025                    xfree(regextag);
1026            }
1027  }  }
1028    
1029  /*  /*
# Line 720  Line 1035 
1035  static int _nstring;  static int _nstring;
1036  static int _nastring;  static int _nastring;
1037    
1038  void add_string_str(const char *s)  static void zap_string(void)
1039    {
1040            _nstring = 0;
1041            if(_string)
1042                    _string[0] = '\0';
1043    }
1044    
1045    static char *dup_string(void)
1046    {
1047            if(_string)
1048                    return xstrdup(_string);
1049            else
1050                    return "";
1051    }
1052    
1053    static void add_string_str(const char *s)
1054  {  {
1055          int l = strlen(s) + 1;          int l = strlen(s) + 1;
1056          if(_nstring + l > _nastring)          if(_nstring + l > _nastring)
1057          {          {
1058                  _nastring += 128;                  _nastring += MAX(128, l);
1059                  _string = xrealloc(_string, _nastring * sizeof(_string[0]));                  _string = xrealloc(_string, _nastring * sizeof(_string[0]));
1060          }          }
1061          memcpy(_string+_nstring, s, l);          memcpy(_string+_nstring, s, l);
1062          _nstring += l-1;          _nstring += l-1;
1063  }  }
1064    
1065  void add_string_ch(int ch)  static void add_string_ch(int ch)
1066  {  {
1067          char buf[2];          char buf[2];
1068          buf[0] = ch;          buf[0] = ch;
# Line 740  Line 1070 
1070          add_string_str(buf);          add_string_str(buf);
1071  }  }
1072    
1073  void add_string_date(const char *d)  static void add_string_date(const char *d)
1074  {  {
1075          struct tm tm, *tmp;          struct tm tm, *tmp;
1076          int n;          int n;
1077          time_t t;          time_t t;
1078          char *buf;          char *buf;
1079          int nbuf;          int nbuf;
1080            char *env;
1081    
1082          memset(&tm, 0, sizeof(tm));          memset(&tm, 0, sizeof(tm));
1083          n = sscanf(d, "%d.%d.%d.%d.%d.%d",          n = sscanf(d, "%d.%d.%d.%d.%d.%d",
# Line 755  Line 1086 
1086          tm.tm_mon--;          tm.tm_mon--;
1087          if(tm.tm_year > 1900)          if(tm.tm_year > 1900)
1088                  tm.tm_year -= 1900;                  tm.tm_year -= 1900;
1089    
1090            env = getenv("TZ");
1091            putenv("TZ=UTC0");
1092          t = mktime(&tm);          t = mktime(&tm);
1093            if(env)
1094            {
1095                    char *c = xmalloc(strlen(env) + 3 + 1); /* Extra space for TZ and = */
1096                    sprintf(c, "TZ=%s", env);
1097                    putenv(c);
1098                    xfree(c);
1099            }
1100            else
1101                    putenv("TZ");
1102    
1103          if(n != 6 || t == (time_t)(-1))          if(n != 6 || t == (time_t)(-1))
1104          {          {
1105                  add_string_str("<invalid date>");                  add_string_str("<invalid date>");
# Line 763  Line 1107 
1107          }          }
1108    
1109          tmp = localtime(&t);          tmp = localtime(&t);
1110          nbuf = strlen(conf.date_format) * 16;   /* Should be enough to hold all types of expansions */          nbuf = (strlen(conf.date_format)+1) * 16;       /* Should be enough to hold all types of expansions */
1111          buf = xmalloc(nbuf);          buf = xmalloc(nbuf);
1112          strftime(buf, nbuf, conf.date_format, tmp);          strftime(buf, nbuf, conf.date_format, tmp);
1113          add_string_str(buf);          add_string_str(buf);
1114          xfree(buf);          xfree(buf);
1115  }  }
1116    
1117  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)
1118    {
1119            int l = 0;
1120            char *str = xmalloc(6 * strlen(s) + 1); /* Should hold all char entity-expand */
1121            char *cptr = str;
1122            for(; *s; s++)
1123            {
1124                    if(maxlen && l > abs(maxlen))
1125                    {
1126                            cptr += sprintf(cptr, "...");
1127                            break;
1128                    }
1129                    if(*s < 0x20)
1130                    {
1131                            if(*s == '\n')
1132                            {
1133                                    if(maxlen < 0)
1134                                            *cptr++ = ' ';
1135                                    else
1136                                            cptr += sprintf(cptr, "<br%s>", conf.html_level == HTMLLEVEL_X ? " /" : "");
1137                            }
1138                    }
1139                    else if(*s >= 0x7f || *s == '"')
1140                            cptr += sprintf(cptr, "&#%d;", (int)(unsigned char)*s);
1141                    else if(*s == '<')
1142                            cptr += sprintf(cptr, "&lt;");
1143                    else if(*s == '>')
1144                            cptr += sprintf(cptr, "&gt;");
1145                    else if(*s == '&')
1146                            cptr += sprintf(cptr, "&amp;");
1147                    else if(*s == '"')
1148                            cptr += sprintf(cptr, "&quot;");
1149                    else
1150                            *cptr++ = *s;
1151                    l++;
1152            }
1153            *cptr = '\0';
1154            add_string_str(str);
1155            xfree(str);
1156    }
1157    
1158    static void add_string_str_len(const char *s, int maxlen)
1159    {
1160            int l = strlen(s);
1161            char *str = xmalloc(l + 1 + 3);
1162            strcpy(str, s);
1163            if(maxlen < l)
1164                    sprintf(&str[maxlen], "...");
1165            add_string_str(str);
1166            xfree(str);
1167    }
1168    
1169    static char *expand_string(const char *s, rcsfile_t *rcs, revision_t *r, rev_t *rev, rev_t *prev, tag_t *tag)
1170  {  {
1171          char nb[32];          char nb[32];
1172          char nr[32];          char nr[32];
1173          char *base;          char *base;
1174            char *exp;
1175            int l;
1176            char ch;
1177    
1178          if(!s)          if(!s)
1179                  return xstrdup("");                  return xstrdup("");
1180    
1181          _nstring = 0;          zap_string();
         if(_string)  
                 _string[0] = '\0';  
1182    
1183          sprintf(nb, "%d", rcs->nbranches);          sprintf(nb, "%d", rcs->nbranches + rcs->nfolds);
1184          sprintf(nr, "%d", rcs->nsrev);          sprintf(nr, "%d", rcs->nsrev);
1185          for(; *s; s++)          for(; *s; s++)
1186          {          {
# Line 828  Line 1225 
1225                                  /*                                  /*
1226                                   * We should not add anything here because we can encounter                                   * We should not add anything here because we can encounter
1227                                   * a completely empty path, in which case we do not want                                   * a completely empty path, in which case we do not want
1228                                   * to add any slash. This prevents a inadvertent root redirect.                                   * to add any slash. This prevents an inadvertent root redirect.
1229                                   *                                   *
1230                                   * else                                   * else
1231                                   *      add_string_str("/");                                   *      add_string_str("/");
# Line 864  Line 1261 
1261                          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;
1262                          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;
1263                          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;
1264                            case 'L':
1265                            case 'l':
1266                                    ch = *s;
1267                                    l = 0;
1268                                    if(s[1] == '[')
1269                                    {
1270                                            char *cptr = strchr(s, ']');
1271                                            char *eptr;
1272                                            if(cptr)
1273                                            {
1274                                                    l = strtol(&s[2], &eptr, 10);
1275                                                    if(eptr != cptr)
1276                                                            l = 0;
1277                                                    else
1278                                                            s = cptr;
1279                                            }
1280                                    }
1281                                    if(!conf.parse_logs)
1282                                            add_string_str("N/A");
1283                                    else if(r && r->dtext && r->dtext->log)
1284                                    {
1285                                            if(ch == 'l')
1286                                                    add_string_str_html(r->dtext->log, l);
1287                                            else
1288                                                    add_string_str_len(r->dtext->log, abs(l));
1289                                    }
1290                                    break;
1291                            case '(':
1292                                    base = dup_string();
1293                                    exp = expand_string(s+1, rcs, r, rev, prev, tag);
1294                                    zap_string();
1295                                    add_string_str(base);
1296                                    add_string_str_html(exp, 0);
1297                                    xfree(base);
1298                                    xfree(exp);
1299                                    /* Find the %) in this recursion level */
1300                                    for(; *s; s++)
1301                                    {
1302                                            if(*s == '%' && s[1] == ')')
1303                                            {
1304                                                    s++;
1305                                                    break;
1306                                            }
1307                                    }
1308                                    if(!*s)
1309                                    {
1310                                            s--;    /* To end outer loop */
1311                                            stack_msg(MSG_WARN, "string expand: Missing %%) in expansion");
1312                                    }
1313                                    break;
1314                            case ')':
1315                                    return dup_string();
1316                          default:                          default:
1317                                  add_string_ch('%');                                  add_string_ch('%');
1318                                  add_string_ch(*s);                                  add_string_ch(*s);
# Line 873  Line 1322 
1322                  else                  else
1323                          add_string_ch(*s);                          add_string_ch(*s);
1324          }          }
1325          return xstrdup(_string);          return dup_string();
1326  }  }
1327    
1328  /*  /*
# Line 881  Line 1330 
1330   * Drawing routines   * Drawing routines
1331   **************************************************************************   **************************************************************************
1332   */   */
1333  int get_swidth(const char *s, font_t *f)  static color_t *clr_id = NULL;
1334    static int nclr_id = 0;
1335    
1336    static color_t *clr(gdImagePtr im, const char *s, revision_t *r, branch_t *b)
1337    {
1338            int i;
1339            color_t *c = get_colorref(s);
1340            if(!c)
1341                    c = &black_color;
1342            for(i = 0; i < nclr_id; i++)
1343            {
1344                    if(c->r == clr_id[i].r && c->g == clr_id[i].g && c->b == clr_id[i].b)
1345                            return &clr_id[i];
1346            }
1347            /* FIXME: Do color evaluation */
1348            clr_id = xrealloc(clr_id, (nclr_id+1) * sizeof(*clr_id));
1349            clr_id[nclr_id] = *c;
1350            clr_id[nclr_id].id = gdImageColorAllocate(im, c->r, c->g, c->b);
1351            return &clr_id[nclr_id++];
1352    }
1353    
1354    static int get_swidth(const char *s, font_t *f)
1355  {  {
1356          int n;          int n;
1357          int m;          int m;
1358          if(!s || !*s)          if(!s || !*s)
1359                  return 0;                  return 0;
1360    
1361    #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1362            if(conf.use_ttf && f->ttfont)
1363            {
1364                    int bb[8];
1365                    char *e;
1366    #ifdef HAVE_GDIMAGESTRINGFT
1367                    e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1368    #else
1369                    e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1370    #endif
1371                    if(!e)
1372                            return bb[2] - bb[6];
1373            }
1374    #endif
1375          for(n = m = 0; *s; n++, s++)          for(n = m = 0; *s; n++, s++)
1376          {          {
1377                  if(*s == '\n')                  if(*s == '\n')
# Line 898  Line 1383 
1383          }          }
1384          if(n > m)          if(n > m)
1385                  m = n;                  m = n;
1386          return m * (*f)->w;          return f->gdfont ? m * f->gdfont->w : m;
1387  }  }
1388    
1389  int get_sheight(const char *s, font_t *f)  static int get_sheight(const char *s, font_t *f)
1390  {  {
1391          int nl;          int nl;
1392          if(!s || !*s)          if(!s || !*s)
1393                  return 0;                  return 0;
1394    
1395    #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1396            if(conf.use_ttf && f->ttfont)
1397            {
1398                    int bb[8];
1399                    char *e;
1400    #ifdef HAVE_GDIMAGESTRINGFT
1401                    e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1402    #else
1403                    e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1404    #endif
1405                    if(!e)
1406                            return bb[3] - bb[7] + 4;
1407            }
1408    #endif
1409          for(nl = 1; *s; s++)          for(nl = 1; *s; s++)
1410          {          {
1411                  if(*s == '\n' && s[1])                  if(*s == '\n' && s[1])
1412                          nl++;                          nl++;
1413          }          }
1414          return nl * (*f)->h;          return nl * f->gdfont->h;
1415  }  }
1416    
1417  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)
1418  {  {
1419          int r2 = 2*r;          int r2 = 2*r;
1420            if(!r)
1421                    gdImageFilledRectangle(im, x1, y1, x2, y2, bgcolor->id);
1422    #ifdef HAVE_GDIMAGEFILLEDARC
1423            else
1424            {
1425                    gdImageFilledArc(im, x1+r, y1+r, r2, r2, 180, 270, bgcolor->id, gdArc);
1426                    gdImageFilledArc(im, x2-r, y1+r, r2, r2, 270, 360, bgcolor->id, gdArc);
1427                    gdImageFilledArc(im, x1+r, y2-r, r2, r2,  90, 180, bgcolor->id, gdArc);
1428                    gdImageFilledArc(im, x2-r, y2-r, r2, r2,   0,  90, bgcolor->id, gdArc);
1429                    gdImageFilledRectangle(im, x1+r, y1, x2-r, y1+r, bgcolor->id);
1430                    gdImageFilledRectangle(im, x1, y1+r, x2, y2-r, bgcolor->id);
1431                    gdImageFilledRectangle(im, x1+r, y2-r, x2-r, y2, bgcolor->id);
1432            }
1433    #endif
1434          gdImageLine(im, x1+r, y1, x2-r, y1, color->id);          gdImageLine(im, x1+r, y1, x2-r, y1, color->id);
1435          gdImageLine(im, x1+r, y2, x2-r, y2, color->id);          gdImageLine(im, x1+r, y2, x2-r, y2, color->id);
1436          gdImageLine(im, x1, y1+r, x1, y2-r, color->id);          gdImageLine(im, x1, y1+r, x1, y2-r, color->id);
# Line 928  Line 1442 
1442          }          }
1443          if(r)          if(r)
1444          {          {
1445                    /* FIXME: Pixelization is not perfect */
1446                  gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);                  gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);
1447                  gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);                  gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);
1448                  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);  
1449                  if(conf.box_shadow)                  if(conf.box_shadow)
1450                  {                  {
                         /* FIXME: Pixelization is not correct here */  
1451                          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);
1452                            gdImageArc(im, x2-r+1, y2-r, r2, r2,   0,  90, black_color.id);
1453                            gdImageArc(im, x2-r, y2-r+1, r2, r2,   0,  90, black_color.id);
1454                  }                  }
1455                    gdImageArc(im, x2-r, y2-r, r2, r2,   0,  90, color->id);
1456    #if !defined(NOGDFILL) && !defined(HAVE_GDIMAGEFILLEDARC)
1457                    /* BUG: We clip manually because libgd segfaults on out of bound values */
1458                    if((x1+x2)/2 >= 0 && (x1+x2)/2 < gdImageSX(im) && (y1+y2)/2 >= 0 && (y1+y2)/2 < gdImageSY(im))
1459                            gdImageFillToBorder(im, (x1+x2)/2, (y1+y2)/2, color->id, bgcolor->id);
1460    #endif
1461          }          }
         gdImageFillToBorder(im, (x1+x2)/2, (y1+y2)/2, color->id, bgcolor->id);  
1462  }  }
1463    
1464  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)
1465  {  {
1466            int h = get_sheight(s, f);
1467          int xx, yy;          int xx, yy;
1468          switch(align & ALIGN_HX)          switch(align & ALIGN_HX)
1469          {          {
# Line 955  Line 1476 
1476          {          {
1477          default:          default:
1478          case ALIGN_VT: yy = 0; break;          case ALIGN_VT: yy = 0; break;
1479          case ALIGN_VC: yy = -get_sheight(s, f)/2; break;          case ALIGN_VC: yy = h/2; break;
1480          case ALIGN_VB: yy = -get_sheight(s, f); break;          case ALIGN_VB: yy = h; break;
1481            }
1482    #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1483            if(conf.use_ttf && f->ttfont)
1484            {
1485                    int bb[8];
1486                    char *e;
1487                    int cid = conf.anti_alias ? c->id : -c->id;
1488    #ifdef HAVE_GDIMAGESTRINGFT
1489                    e = gdImageStringFT(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1490    #else
1491                    e = gdImageStringTTF(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1492    #endif
1493                    if(!e)
1494                            return;
1495          }          }
1496          gdImageString(im, *f, x+xx+1, y+yy, s, c->id);  #endif
1497            yy = -yy;
1498            gdImageString(im, f->gdfont, x+xx+1, y+yy, s, c->id);
1499  }  }
1500    
1501  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)
1502  {  {
1503          char *t;          char *t;
1504          char *d;          char *d;
# Line 978  Line 1515 
1515          xfree(d);          xfree(d);
1516  }  }
1517    
1518  void draw_rev(gdImagePtr im, int cx, int ty, revision_t *r)  static void draw_rev(gdImagePtr im, revision_t *r)
1519  {  {
1520          int lx = cx - r->w/2;          int lx;
1521          int rx = lx + r->w;          int rx;
1522            int x2;
1523          int i;          int i;
1524          draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color, &conf.rev_bgcolor);          int ty;
1525    
1526            if(conf.left_right)
1527            {
1528                    lx = r->cx;
1529                    rx = r->cx + r->w;
1530                    ty = r->y - r->h/2;
1531                    x2 = r->cx + r->w/2;
1532            }
1533            else
1534            {
1535                    lx = r->cx - r->w/2;
1536                    rx = lx + r->w;
1537                    ty = r->y;
1538                    x2 = r->cx;
1539            }
1540            draw_rbox(im, lx, ty, rx, ty+r->h, 0, clr(im, "rev_color", r, NULL), clr(im, "rev_bgcolor", r, NULL));
1541          ty += conf.rev_tspace;          ty += conf.rev_tspace;
1542          draw_string(im, r->rev->rev, &conf.rev_font, cx, ty, ALIGN_HC, &conf.rev_color);          if(!conf.rev_hidenumber)
1543          ty += get_sheight(r->rev->rev, &conf.rev_font);          {
1544          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, clr(im, "rev_color", r, NULL));
1545                    ty += get_sheight(r->rev->rev, &conf.rev_font);
1546            }
1547            draw_stringnl(im, r->revtext, &conf.rev_text_font, x2, ty, ALIGN_HC, clr(im, "rev_text_color", r, NULL));
1548          ty += get_sheight(r->revtext, &conf.rev_text_font);          ty += get_sheight(r->revtext, &conf.rev_text_font);
1549          for(i = 0; i < r->ntags; i++)          for(i = 0; i < r->ntags; i++)
1550          {          {
1551                  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, clr(im, "tag_color", r, NULL));
1552                  ty += get_sheight(r->tags[i]->tag, &conf.tag_font);                  ty += get_sheight(r->tags[i]->tag, &conf.tag_font) + conf.rev_separator;
1553          }          }
1554  }  }
1555    
1556  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)
1557  {  {
1558          int lx = cx - b->w/2;          int lx;
1559          int rx = lx + b->w;          int rx;
         int yy;  
1560          int i;          int i;
1561          /*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;
1562          draw_rbox(im, lx, ty, rx, ty+b->h, 5, &conf.branch_color, &conf.branch_bgcolor);          int x2;
1563    
1564            if(conf.left_right)
1565            {
1566                    lx = b->cx;
1567                    rx = lx + b->w;
1568                    x2 = b->cx + b->w/2;
1569            }
1570            else
1571            {
1572                    lx = b->cx - b->w/2;
1573                    rx = lx + b->w;
1574                    x2 = b->cx;
1575            }
1576            draw_rbox(im, lx+xp, yp, rx+xp, yp+b->h, 5, clr(im, "branch_color", NULL, b), clr(im, "branch_bgcolor", NULL, b));
1577          yy = conf.branch_tspace;          yy = conf.branch_tspace;
1578          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++)  
1579          {          {
1580                  draw_string(im, b->tags[i]->tag, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);                  if(!conf.rev_hidenumber)
1581                  yy += get_sheight(b->tags[i]->tag, &conf.branch_font);                  {
1582                            draw_string(im, b->branch->branch, &conf.branch_font, x2+xp, yp+yy, ALIGN_HC, clr(im, "branch_color", NULL, b));
1583                            yy += get_sheight(b->branch->branch, &conf.branch_font);
1584                    }
1585                    for(i = 0; i < b->ntags; i++)
1586                    {
1587                            draw_string(im, b->tags[i]->tag, &conf.branch_tag_font, x2+xp, yp+yy, ALIGN_HC, clr(im, "branch_tag_color", NULL, b));
1588                            yy += get_sheight(b->tags[i]->tag, &conf.branch_tag_font);
1589                    }
1590          }          }
1591            else
         ty += b->h;  
         for(i = 0; i < b->nrevs; i++)  
1592          {          {
1593                  gdImageLine(im, cx, ty, cx, ty+conf.rev_minline, conf.rev_color.id);                  int y1, y2;
1594                  ty += conf.rev_minline;                  int tx = lx + b->fw + conf.branch_lspace;
1595                  draw_rev(im, cx, ty, b->revs[i]);                  int nx = tx - get_swidth(" ", &conf.branch_font);
1596                  ty += b->revs[i]->h;                  draw_string(im, b->branch->branch, &conf.branch_font, nx+xp, yp+yy, ALIGN_HR, clr(im, "branch_color", NULL, b));
1597                    y1 = get_sheight(b->branch->branch, &conf.branch_font);
1598                    draw_string(im, b->tags[0]->tag, &conf.branch_tag_font, tx+xp, yp+yy, ALIGN_HL, clr(im, "branch_tag_color", NULL, b));
1599                    y2 = get_sheight(b->tags[0]->tag, &conf.branch_font);
1600                    yy += MAX(y1, y2);
1601                    for(i = 0; i < b->nfolds; i++)
1602                    {
1603                            draw_string(im, b->folds[i]->branch->branch, &conf.branch_font, nx+xp, yp+yy, ALIGN_HR, clr(im, "branch_color", NULL, b));
1604                            y1 = get_sheight(b->folds[i]->branch->branch, &conf.branch_font);
1605                            draw_string(im, b->folds[i]->tags[0]->tag, &conf.branch_tag_font, tx+xp, yp+yy, ALIGN_HL, clr(im, "branch_tag_color", NULL, b));
1606                            y2 = get_sheight(b->folds[i]->tags[0]->tag, &conf.branch_tag_font);
1607                            yy += MAX(y1, y2);
1608                    }
1609          }          }
1610  }  }
1611    
1612  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)  
1613  {  {
1614          gdImagePtr im;          int yy, xx;
1615          int i;          int i;
1616          char *cptr;          int line[4];
1617            int l;
1618            int sign;
1619    
1620          im = gdImageCreate(rcs->tw+conf.margin_left+conf.margin_right, rcs->th+conf.margin_top+conf.margin_bottom);          line[1] = line[2] = gdTransparent;
         alloc_color(im, &conf.color_bg);  
         alloc_color(im, &conf.tag_color);  
         alloc_color(im, &conf.rev_color);  
         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);  
1621    
1622          for(i = 0; i < rcs->nbranches; i++)          /* Trivial clip the branch */
1623                  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++)  
1624          {          {
1625                  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)
1626                          draw_connector(im, rcs->branches[i]);                          return;
1627            }
1628            else
1629            {
1630                    if(b->cx-b->tw/2 > gdImageSX(im) || b->cx+b->tw/2 < 0 || b->y > gdImageSY(im) || b->y+b->th < 0)
1631                            return;
1632            }
1633    
1634            draw_branch_box(im, b, 0, conf.left_right ? b->y - b->h/2 : b->y);
1635    
1636            if(conf.left_right)
1637            {
1638                    if(conf.upside_down)
1639                    {
1640                            xx = b->cx;
1641                            for(i = 0; i < b->nrevs; i++)
1642                            {
1643                                    revision_t *r = b->revs[i];
1644                                    line[0] = line[3] = clr(im, "rev_color", r, b)->id;
1645                                    gdImageSetStyle(im, line, r->stripped > 0 ? 4 : 1);
1646                                    gdImageLine(im, xx, r->y, r->cx+r->w, r->y, gdStyled);
1647                                    for(sign = l = 1; l < conf.thick_lines; l++)
1648                                    {
1649                                            int pp = (l+1)/2*sign;
1650                                            gdImageLine(im, xx, r->y+pp, r->cx+r->w, r->y+pp, gdStyled);
1651                                            sign *= -1;
1652                                    }
1653                                    draw_rev(im, r);
1654                                    xx = r->cx;
1655                            }
1656                            if(conf.branch_dupbox && b->nrevs)
1657                            {
1658                                    i = b->cx - b->tw + b->w;
1659                                    gdImageLine(im, xx, b->y, i+b->w, b->y, clr(im, "rev_color", NULL, b)->id);
1660                                    for(sign = l = 1; l < conf.thick_lines; l++)
1661                                    {
1662                                            int pp = (l+1)/2*sign;
1663                                            gdImageLine(im, xx, b->y+pp, i+b->w, b->y+pp, clr(im, "rev_color", NULL, b)->id);
1664                                            sign *= -1;
1665                                    }
1666                                    draw_branch_box(im, b, i - b->cx, b->y - b->h/2);
1667                            }
1668                    }
1669                    else
1670                    {
1671                            xx = b->cx + b->w;
1672                            for(i = 0; i < b->nrevs; i++)
1673                            {
1674                                    revision_t *r = b->revs[i];
1675                                    line[0] = line[3] = clr(im, "rev_color", r, b)->id;
1676                                    gdImageSetStyle(im, line, r->stripped > 0 ? 4 : 1);
1677                                    gdImageLine(im, xx, r->y, r->cx, r->y, gdStyled);
1678                                    for(sign = l = 1; l < conf.thick_lines; l++)
1679                                    {
1680                                            int pp = (l+1)/2*sign;
1681                                            gdImageLine(im, xx, r->y+pp, r->cx, r->y+pp, gdStyled);
1682                                            sign *= -1;
1683                                    }
1684                                    draw_rev(im, r);
1685                                    xx = r->cx + r->w;
1686                            }
1687                            if(conf.branch_dupbox && b->nrevs)
1688                            {
1689                                    i = b->cx + b->tw - b->w;
1690                                    gdImageLine(im, xx, b->y, i, b->y, clr(im, "rev_color", NULL, b)->id);
1691                                    for(sign = l = 1; l < conf.thick_lines; l++)
1692                                    {
1693                                            int pp = (l+1)/2*sign;
1694                                            gdImageLine(im, xx, b->y+pp, i, b->y+pp, clr(im, "rev_color", NULL, b)->id);
1695                                            sign *= -1;
1696                                    }
1697                                    draw_branch_box(im, b, i - b->cx, b->y - b->h/2);
1698                            }
1699                    }
1700            }
1701            else
1702            {
1703                    if(conf.upside_down)
1704                    {
1705                            yy = b->y;
1706                            for(i = 0; i < b->nrevs; i++)
1707                            {
1708                                    revision_t *r = b->revs[i];
1709                                    line[0] = line[3] = clr(im, "rev_color", r, b)->id;
1710                                    gdImageSetStyle(im, line, r->stripped > 0 ? 4 : 1);
1711                                    gdImageLine(im, r->cx, yy, r->cx, r->y+r->h, gdStyled);
1712                                    for(sign = l = 1; l < conf.thick_lines; l++)
1713                                    {
1714                                            int pp = (l+1)/2*sign;
1715                                            gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y+r->h, gdStyled);
1716                                            sign *= -1;
1717                                    }
1718                                    draw_rev(im, r);
1719                                    yy = r->y;
1720                            }
1721                            if(conf.branch_dupbox && b->nrevs)
1722                            {
1723                                    i = b->y - b->th + b->h;
1724                                    gdImageLine(im, b->cx, yy, b->cx, i, clr(im, "rev_color", NULL, b)->id);
1725                                    for(sign = l = 1; l < conf.thick_lines; l++)
1726                                    {
1727                                            int pp = (l+1)/2*sign;
1728                                            gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, clr(im, "rev_color", NULL, b)->id);
1729                                            sign *= -1;
1730                                    }
1731                                    draw_branch_box(im, b, 0, i);
1732                            }
1733                    }
1734                    else
1735                    {
1736                            yy = b->y + b->h;
1737                            for(i = 0; i < b->nrevs; i++)
1738                            {
1739                                    revision_t *r = b->revs[i];
1740                                    line[0] = line[3] = clr(im, "rev_color", r, b)->id;
1741                                    gdImageSetStyle(im, line, r->stripped > 0 ? 4 : 1);
1742                                    gdImageLine(im, r->cx, yy, r->cx, r->y, gdStyled);
1743                                    for(sign = l = 1; l < conf.thick_lines; l++)
1744                                    {
1745                                            int pp = (l+1)/2*sign;
1746                                            gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y, gdStyled);
1747                                            sign *= -1;
1748                                    }
1749                                    draw_rev(im, r);
1750                                    yy = r->y + r->h;
1751                            }
1752                            if(conf.branch_dupbox && b->nrevs)
1753                            {
1754                                    i = b->y + b->th - b->h;
1755                                    gdImageLine(im, b->cx, yy, b->cx, i, clr(im, "rev_color", NULL, b)->id);
1756                                    for(sign = l = 1; l < conf.thick_lines; l++)
1757                                    {
1758                                            int pp = (l+1)/2*sign;
1759                                            gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, clr(im, "rev_color", NULL, b)->id);
1760                                            sign *= -1;
1761                                    }
1762                                    draw_branch_box(im, b, 0, i);
1763                            }
1764                    }
1765            }
1766    }
1767    
1768    static void draw_connector(gdImagePtr im, branch_t *b)
1769    {
1770            int l;
1771            int sign;
1772            revision_t *r = b->branchpoint;
1773            int x1 = r->cx + r->w/2 + 2;
1774            int y1 = r->y + r->h/2;
1775            int x2 = b->cx;
1776            int y2 = b->y;
1777    
1778            if(conf.left_right)
1779            {
1780                    x2 = r->cx + r->w/2;
1781                    y2 = r->y + r->h/2 + 3;
1782                    x1 = b->cx;
1783                    y1 = b->y;
1784                    if(conf.upside_down)
1785                            x1 += b->w;
1786            }
1787            else
1788            {
1789                    x1 = r->cx + r->w/2 + 2;
1790                    y1 = r->y + r->h/2;
1791                    x2 = b->cx;
1792                    y2 = b->y;
1793                    if(conf.upside_down)
1794                            y2 += b->h;
1795            }
1796            gdImageLine(im, x1, y1, x2, y1, clr(im, "branch_color", NULL, b)->id);
1797            gdImageLine(im, x2, y1, x2, y2, clr(im, "branch_color", NULL, b)->id);
1798            for(sign = l = 1; l < conf.thick_lines; l++)
1799            {
1800                    int pp = (l+1)/2*sign;
1801                    gdImageLine(im, x1, y1+pp, x2, y1+pp, clr(im, "branch_color", NULL, b)->id);
1802                    gdImageLine(im, x2+pp, y1, x2+pp, y2, clr(im, "branch_color", NULL, b)->id);
1803                    sign *= -1;
1804            }
1805    }
1806    
1807    static void draw_merges(gdImagePtr im, rcsfile_t *rcs, int dot)
1808    {
1809            int i;
1810            for(i = 0; i < rcs->nmerges; i++)
1811            {
1812                    revision_t *fr;
1813                    revision_t *tr;
1814                    int colorid;
1815                    int x1, x2, y1, y2;
1816                    switch(rcs->merges[i].type)
1817                    {
1818                    case TR_TAG:
1819                            fr = rcs->merges[i].from.tag->logrev;
1820                            tr = rcs->merges[i].to.tag->logrev;
1821                            colorid = clr(im, "merge_color.id", NULL, NULL)->id;
1822                            break;
1823                    case TR_REVISION:
1824                            fr = rcs->merges[i].from.rev;
1825                            tr = rcs->merges[i].to.rev;
1826                            colorid = clr(im, "merge_cvsnt_color", NULL, NULL)->id;
1827                            break;
1828                    default:
1829                            continue;
1830                    }
1831                    if(!fr || !tr || fr == tr)
1832                            continue;       /* This can happen with detached tags and self-references */
1833                    if(conf.left_right)
1834                    {
1835                            if(fr->branch == tr->branch)
1836                            {
1837                                    y1 = fr->y - fr->h/2;
1838                                    y2 = tr->y - tr->h/2;
1839                            }
1840                            else
1841                            {
1842                                    if(fr->y < tr->y)
1843                                    {
1844                                            y1 = fr->y + fr->h/2;
1845                                            y2 = tr->y - tr->h/2;
1846                                    }
1847                                    else
1848                                    {
1849                                            y1 = fr->y - fr->h/2;
1850                                            y2 = tr->y + tr->h/2;
1851                                    }
1852                            }
1853                            x1 = fr->cx + fr->w/2;
1854                            x2 = tr->cx + tr->w/2;
1855                    }
1856                    else
1857                    {
1858                            if(fr->branch == tr->branch)
1859                            {
1860                                    x1 = fr->cx - fr->w/2;
1861                                    x2 = tr->cx - tr->w/2;
1862                            }
1863                            else
1864                            {
1865                                    if(fr->cx < tr->cx)
1866                                    {
1867                                            x1 = fr->cx + fr->w/2;
1868                                            x2 = tr->cx - tr->w/2;
1869                                    }
1870                                    else
1871                                    {
1872                                            x1 = fr->cx - fr->w/2;
1873                                            x2 = tr->cx + tr->w/2;
1874                                    }
1875                            }
1876                            if(rcs->merges[i].type == TR_TAG)
1877                            {
1878                                    y1 = fr->y + rcs->merges[i].from.tag->yofs;
1879                                    y2 = tr->y + rcs->merges[i].to.tag->yofs;
1880                            }
1881                            else
1882                            {
1883                                    y1 = fr->y + fr->h/2;
1884                                    y2 = tr->y + tr->h/2;
1885                            }
1886                    }
1887                    if(dot && !conf.merge_arrows)
1888                    {
1889                            int o = conf.left_right ? 1 : 0;
1890                            gdImageArc(im, x2, y2+o, 8, 8, 0, 360, colorid);
1891                            /* BUG: We clip manually because libgd segfaults on out of bound values */
1892                            if(x2+1 >= 0 && x2+1 < gdImageSX(im) && y2+o+1 >= 0 && y2+o+1 < gdImageSY(im))
1893                                    gdImageFillToBorder(im, x2+1, y2+o+1, colorid, colorid);
1894                    }
1895                    else if(dot && conf.merge_arrows)
1896                    {
1897                            /*
1898                             * Arrow patch from Haroon Rafique <haroon.rafique@utoronto.ca>
1899                             * Slightly adapted to be more configurable.
1900                             */
1901                            int sx, sy;     /* start point coordinates */
1902                            int ex, ey;     /* end point coordinates */
1903                            double theta;
1904                            double u1, v1, u2, v2;
1905                            gdPoint p[3];
1906    
1907                            sx = x1; sy = y1;
1908                            ex = x2; ey = y2;
1909                            if(conf.left_right)
1910                            {
1911                                    if(fr->branch == tr->branch)
1912                                    {
1913                                            int yy = (y1 < y2 ? y1 : y2) - 5;
1914                                            /* line from (x1,yy) to (x2,yy) */
1915                                            sy = ey = yy;
1916                                    }
1917                                    else
1918                                    {
1919                                            if(y1 > y2)
1920                                            {
1921                                                    /* line from (x1,y1-3) to (x2,y2+3+1) */
1922                                                    sy = y1-3;
1923                                                    ey = y2+3+1;
1924                                            }
1925                                            else
1926                                            {
1927                                                    /* line from (x1,y1+3+1) to (x2,y2-3) */
1928                                                    sy = y1+3+1;
1929                                                    ey = y2-3;
1930                                            }
1931                                    }
1932                            }
1933                            else
1934                            {
1935                                    if(fr->branch == tr->branch)
1936                                    {
1937                                            int xx = (x1 < x2 ? x1 : x2) - 5;
1938                                            /* line from (xx,y1) to (xx,y2) */
1939                                            sx = ex = xx;
1940                                    }
1941                                    else
1942                                    {
1943                                            if(x1 > x2)
1944                                            {
1945                                                    /* line from (x1-3,y1) to (x2+3,y2) */
1946                                                    sx = x1-3;
1947                                                    ex = x2+3;
1948                                            }
1949                                            else
1950                                            {
1951                                                    /* line from (x1+3,y1) to (x2-3,y2) */
1952                                                    sx = x1+3;
1953                                                    ex = x2-3;
1954                                            }
1955                                    }
1956                            }
1957                            /*
1958                             * inspiration for arrow code comes from arrows.c in the
1959                             * graphviz package. Thank you, AT&T
1960                             */
1961                            /* theta in radians */
1962                            theta = atan2((double)(sy-ey), (double)(sx-ex));
1963                            u1 = (double)conf.arrow_length * cos(theta);
1964                            v1 = (double)conf.arrow_length * sin(theta);
1965                            u2 = (double)conf.arrow_width  * cos(theta + M_PI/2.0);
1966                            v2 = (double)conf.arrow_width  * sin(theta + M_PI/2.0);
1967                            /* points of polygon (triangle) */
1968                            p[0].x = ROUND(ex + u1 - u2);
1969                            p[0].y = ROUND(ey + v1 - v2);
1970                            p[1].x = ex;
1971                            p[1].y = ey;
1972                            p[2].x = ROUND(ex + u1 + u2);
1973                            p[2].y = ROUND(ey + v1 + v2);
1974                            /* draw the polygon (triangle) */
1975                            gdImageFilledPolygon(im, p, 3, colorid);
1976                    }
1977                    else
1978                    {
1979                            if(conf.left_right)
1980                            {
1981                                    if(fr->branch == tr->branch)
1982                                    {
1983                                            int yy = (y1 < y2 ? y1 : y2) - 5;
1984                                            gdImageLine(im, x1, y1, x1, yy, colorid);
1985                                            gdImageLine(im, x2, y2, x2, yy, colorid);
1986                                            gdImageLine(im, x1, yy, x2, yy, colorid);
1987                                    }
1988                                    else
1989                                    {
1990                                            if(y1 > y2)
1991                                            {
1992                                                    gdImageLine(im, x1, y1, x1, y1-3, colorid);
1993                                                    gdImageLine(im, x2, y2+1, x2, y2+3+1, colorid);
1994                                                    gdImageLine(im, x1, y1-3, x2, y2+3+1, colorid);
1995                                            }
1996                                            else
1997                                            {
1998                                                    gdImageLine(im, x1, y1+1, x1, y1+3+1, colorid);
1999                                                    gdImageLine(im, x2, y2, x2, y2-3, colorid);
2000                                                    gdImageLine(im, x1, y1+3+1, x2, y2-3, colorid);
2001                                            }
2002                                    }
2003                            }
2004                            else
2005                            {
2006                                    if(fr->branch == tr->branch)
2007                                    {
2008                                            int xx = (x1 < x2 ? x1 : x2) - 5;
2009                                            gdImageLine(im, xx, y1, x1, y1, colorid);
2010                                            gdImageLine(im, xx, y2, x2, y2, colorid);
2011                                            gdImageLine(im, xx, y1, xx, y2, colorid);
2012                                    }
2013                                    else
2014                                    {
2015                                            if(x1 > x2)
2016                                            {
2017                                                    gdImageLine(im, x1, y1, x1-3, y1, colorid);
2018                                                    gdImageLine(im, x2, y2, x2+3, y2, colorid);
2019                                                    gdImageLine(im, x1-3, y1, x2+3, y2, colorid);
2020                                            }
2021                                            else
2022                                            {
2023                                                    gdImageLine(im, x1, y1, x1+3, y1, colorid);
2024                                                    gdImageLine(im, x2, y2, x2-3, y2, colorid);
2025                                                    gdImageLine(im, x1+3, y1, x2-3, y2, colorid);
2026                                            }
2027                                    }
2028                            }
2029                    }
2030            }
2031    }
2032    
2033    static void draw_messages(gdImagePtr im, int offset)
2034    {
2035            int i;
2036    
2037            for(i = 0; i < nmsg_stack; i++)
2038            {
2039                    draw_stringnl(im, msg_stack[i].msg, &conf.msg_font, conf.margin_left, offset, ALIGN_HL|ALIGN_VT, clr(im, "msg_color", NULL, NULL));
2040                    offset += msg_stack[i].h;
2041            }
2042    }
2043    
2044    static void alloc_color(gdImagePtr im, color_t *c)
2045    {
2046            c->id = gdImageColorAllocate(im, c->r, c->g, c->b);
2047    }
2048    
2049    static gdImagePtr make_image(rcsfile_t *rcs)
2050    {
2051            gdImagePtr im;
2052            int i;
2053            char *cptr;
2054            int w, h;
2055            int subx = 0, suby = 0;
2056            int subw, subh;
2057            int msgh = 0;
2058    
2059            if(subtree_branch)
2060            {
2061                    subw = 0;
2062                    subh = 0;
2063                    if(subtree_rev)
2064                    {
2065                            for(i = 0; i < subtree_rev->nbranches; i++)
2066                                    calc_subtree_size(subtree_rev->branches[i], &subx, &suby, &subw, &subh);
2067                    }
2068                    else
2069                            calc_subtree_size(subtree_branch, &subx, &suby, &subw, &subh);
2070            }
2071            else
2072            {
2073                    subw = rcs->tw;
2074                    subh = rcs->th;
2075            }
2076    
2077            cptr = expand_string(conf.title, rcs, NULL, NULL, NULL, NULL);
2078            w = subw + conf.margin_left + conf.margin_right;
2079            h = subh + conf.margin_top + conf.margin_bottom;
2080            i = get_swidth(cptr, &conf.title_font);
2081            if(i > w)
2082                    w = i;
2083    
2084            if(!quiet && nmsg_stack)
2085            {
2086                    int msgw = 0;
2087                    for(i = 0; i < nmsg_stack; i++)
2088                    {
2089                            int ww = msg_stack[i].w = get_swidth(msg_stack[i].msg, &conf.msg_font);
2090                            int hh = msg_stack[i].h = get_sheight(msg_stack[i].msg, &conf.msg_font);
2091                            msgh += hh;
2092                            h += hh;
2093                            if(ww > msgw)
2094                                    msgw = ww;
2095                    }
2096                    if(msgw > w)
2097                            w = msgw;
2098            }
2099    
2100            im = gdImageCreate(w, h);
2101            alloc_color(im, &conf.color_bg);        /* The background is always a unique color */
2102            clr(im, "__black_color__", NULL, NULL);
2103    
2104            if(conf.transparent_bg)
2105                    gdImageColorTransparent(im, conf.color_bg.id);
2106    
2107            if(!conf.merge_front)
2108                    draw_merges(im, rcs, 0);
2109    
2110            for(i = 0; i < rcs->nbranches; i++)
2111            {
2112                    if(!rcs->branches[i]->folded && !(subtree_branch && !rcs->branches[i]->subtree_draw))
2113                            draw_branch(im, rcs->branches[i]);
2114            }
2115    
2116            draw_merges(im, rcs, 1);        /* The dots of the merge dest */
2117    
2118            for(i = 0; i < rcs->nbranches; i++)
2119            {
2120                    if(rcs->branches[i]->branchpoint)
2121                            draw_connector(im, rcs->branches[i]);
2122          }          }
2123          cptr = expand_string(conf.title, rcs, NULL, NULL, NULL, NULL);  
2124          draw_stringnl(im, cptr, &conf.title_font, conf.title_x, conf.title_y, conf.title_align, &conf.title_color);          /* Clear the margins if we have a partial tree */
2125          xfree(cptr);          if(subtree_branch)
2126            {
2127          return im;                  gdImageFilledRectangle(im, 0, 0, w-1, conf.margin_top-1, conf.color_bg.id);
2128  }                  gdImageFilledRectangle(im, 0, 0, conf.margin_left-1, h-1, conf.color_bg.id);
2129                    gdImageFilledRectangle(im, 0, h-conf.margin_bottom, w-1, h-1, conf.color_bg.id);
2130                    gdImageFilledRectangle(im, w-conf.margin_right, 0, w-1, h-1, conf.color_bg.id);
2131            }
2132    
2133            draw_stringnl(im, cptr, &conf.title_font, conf.title_x, conf.title_y, conf.title_align, clr(im, "title_color", NULL, NULL));
2134            xfree(cptr);
2135    
2136            if(conf.merge_front)
2137                    draw_merges(im, rcs, 0);
2138    
2139            if(!quiet)
2140                    draw_messages(im, h - conf.margin_bottom/2 - msgh);
2141    
2142            return im;
2143    }
2144    
2145  /*  /*
2146   **************************************************************************   **************************************************************************
2147   * Layout routines   * Layout routines
2148     *
2149     * Branch BBox:
2150     *      left   = center_x - total_width / 2     (cx-tw)/2
2151     *      right  = center_x + total_width / 2     (cx+tw)/2
2152     *      top    = y_pos                          (y)
2153     *      bottom = y_pos + total_height           (y+th)
2154     *
2155     * Margins of branches:
2156     *
2157     *         .              .
2158     *         .              .
2159     *         +--------------+
2160     *            ^
2161     *            | branch_margin           .
2162     *            v                         .
2163     * ----------------+                    .
2164     *                 | ^                  |
2165     *                 | | branch_connect   |
2166     *                 | v                  |
2167     *..-+      +t-----+------+      +------+------+
2168     *   |      l             |      |             |
2169     *   | <--> | branch bbox | <--> | branch bbox |
2170     *   |   |  |             r   |  |             |
2171     *..-+   |  +------------b+   |  +-------------+
2172     *       |    ^               branch_margin
2173     *       |    | branch_margin
2174     *       |    v
2175     *       |  +-------------+
2176     *       |  .             .
2177     *       |  .             .
2178     *       |
2179     *       branch_margin
2180     *
2181     * FIXME: There are probable som +/-1 errors in the code...
2182     *        (notably shadows are not calculated in the margins)
2183   **************************************************************************   **************************************************************************
2184   */   */
2185  void move_branch(branch_t *b, int x, int y)  static void move_branch(branch_t *b, int x, int y)
2186  {  {
2187          int i;          int i;
2188          b->cx += x;          b->cx += x;
# Line 1088  Line 2194 
2194          }          }
2195  }  }
2196    
2197  void reposition_branch(revision_t *r, int *x, int *w)  static void initial_reposition_branch(revision_t *r, int *x, int *w)
2198  {  {
2199          int i, j;          int i, j;
2200          for(j = 0; j < r->nbranches; j++)          for(j = 0; j < r->nbranches; j++)
# Line 1101  Line 2207 
2207                  /* Recurse to move branches of branched revisions */                  /* Recurse to move branches of branched revisions */
2208                  for(i = b->nrevs-1; i >= 0; i--)                  for(i = b->nrevs-1; i >= 0; i--)
2209                  {                  {
2210                          reposition_branch(b->revs[i], x, w);                          initial_reposition_branch(b->revs[i], x, w);
2211                    }
2212            }
2213    }
2214    
2215    static void initial_reposition_branch_lr(revision_t *r, int *y, int *h)
2216    {
2217            int i, j;
2218            for(j = 0; j < r->nbranches; j++)
2219            {
2220                    branch_t *b = r->branches[j];
2221                    *y += *h + conf.rev_minline + b->th/2 - b->y;
2222                    *h = b->th/2;
2223                    move_branch(b, r->cx + r->w/2 + conf.branch_connect, *y);
2224                    *y = b->y;
2225                    /* Recurse to move branches of branched revisions */
2226                    for(i = b->nrevs-1; i >= 0; i--)
2227                    {
2228                            initial_reposition_branch_lr(b->revs[i], y, h);
2229                  }                  }
2230          }          }
2231  }  }
2232    
2233  void rect_union(int *x, int *y, int *w, int *h, branch_t *b)  static void rect_union(int *x, int *y, int *w, int *h, branch_t *b)
2234  {  {
2235          int x1 = *x;          int x1 = *x;
2236          int x2 = x1 + *w;          int x2 = x1 + *w;
2237          int y1 = *y;          int y1 = *y;
2238          int y2 = y1 + *h;          int y2 = y1 + *h;
2239          int xx1 = b->cx - b->tw/2;          int xx1;
2240          int xx2 = xx1 + b->tw;          int xx2;
2241          int yy1 = b->y;          int yy1;
2242          int yy2 = yy1 + b->th;          int yy2;
2243    
2244            if(conf.left_right)
2245            {
2246                    xx1 = b->cx;
2247                    yy1 = b->y - b->th/2;
2248            }
2249            else
2250            {
2251                    xx1 = b->cx - b->tw/2;
2252                    yy1 = b->y;
2253            }
2254            xx2 = xx1 + b->tw;
2255            yy2 = yy1 + b->th;
2256    
2257          x1 = MIN(x1, xx1);          x1 = MIN(x1, xx1);
2258          x2 = MAX(x2, xx2);          x2 = MAX(x2, xx2);
2259          y1 = MIN(y1, yy1);          y1 = MIN(y1, yy1);
# Line 1126  Line 2264 
2264          *h = y2 - y1;          *h = y2 - y1;
2265  }  }
2266    
2267  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)
2268    {
2269            int i, j;
2270    
2271            rect_union(x, y, w, h, b);
2272    
2273            for(i = 0; i < b->nrevs; i++)
2274            {
2275                    for(j = 0; j < b->revs[i]->nbranches; j++)
2276                            calc_subtree_size(b->revs[i]->branches[j], x, y, w, h);
2277            }
2278    }
2279    
2280    static int branch_intersects(int top, int bottom, int left, branch_t *b)
2281  {  {
2282          int br = b->cx + b->tw/2;          int br = b->cx + b->tw/2;
2283          int bt = b->y - conf.branch_connect - conf.branch_margin/2;          int bt = b->y - conf.branch_connect - conf.branch_margin/2;
# Line 1134  Line 2285 
2285          return !(bt > bottom || bb < top || br >= left);          return !(bt > bottom || bb < top || br >= left);
2286  }  }
2287    
2288  int kern_branch(rcsfile_t *rcs, branch_t *b)  static int branch_intersects_lr(int left, int right, int top, branch_t *b)
2289    {
2290            int bt = b->y + b->th/2;
2291            int bl = b->cx - conf.branch_connect - conf.branch_margin/2;
2292            int br = b->cx + b->tw + conf.branch_margin/2;
2293            return !(bl > right || br < left || bt >= top);
2294    }
2295    
2296    static int kern_branch(rcsfile_t *rcs, branch_t *b)
2297  {  {
2298          int left = b->cx - b->tw/2;          int left = b->cx - b->tw/2;
2299          int top = b->y - conf.branch_connect - conf.branch_margin/2;          int top = b->y - conf.branch_connect - conf.branch_margin/2;
# Line 1162  Line 2321 
2321          return 0;          return 0;
2322  }  }
2323    
2324  void make_layout(rcsfile_t *rcs)  static int kern_branch_lr(rcsfile_t *rcs, branch_t *b)
2325  {  {
2326          int i, j;          int top = b->y - b->th/2;
2327          int x, y;          int left = b->cx - conf.branch_connect - conf.branch_margin/2;
2328          int w, h;          int right = b->cx + b->tw + conf.branch_margin/2;
2329          int w2;          int i;
2330          int moved;          int ypos = 0;
2331    
2332          /* Calculate the box-sizes of the revisions */          for(i = 0; i < rcs->nbranches; i++)
         for(i = 0; i < rcs->nsrev; i++)  
2333          {          {
2334                  revision_t *rp;                  branch_t *bp = rcs->branches[i];
2335                  int w;                  if(bp == b)
2336                  int h;                          continue;
2337                  rp = rcs->srev[i];                  if(branch_intersects_lr(left, right, top, bp))
2338                  rp->revtext = expand_string(conf.rev_text, rcs, rp, rp->rev, NULL, rp->ntags ? rp->tags[0] : NULL);                  {
2339                  w = get_swidth(rp->revtext, &conf.rev_text_font);                          int m = bp->y + bp->th/2 + conf.branch_margin;
2340                  j = get_swidth(rp->rev->rev, &conf.rev_font);                          if(m > ypos)
2341                  if(j > w)                                  ypos = m;
2342                          w = j;                  }
2343                  h = get_sheight(rp->revtext, &conf.rev_text_font) + get_sheight(rp->rev->rev, &conf.rev_font);          }
2344                  for(j = 0; j < rp->ntags; j++)          if(ypos && (b->y - b->th/2) - ypos > 0)
2345            {
2346                    move_branch(b, 0, ypos - (b->y - b->th/2));
2347                    return 1;
2348            }
2349            return 0;
2350    }
2351    
2352    static int kern_tree(rcsfile_t *rcs)
2353    {
2354            int i;
2355            int moved;
2356            int safeguard;
2357            int totalmoved = 0;
2358            for(moved = 1, safeguard = LOOPSAFEGUARD; moved && safeguard; safeguard--)
2359            {
2360                    moved = 0;
2361                    for(i = 1; i < rcs->nbranches; i++)
2362                    {
2363                            if(conf.left_right)
2364                                    moved += kern_branch_lr(rcs, rcs->branches[i]);
2365                            else
2366                                    moved += kern_branch(rcs, rcs->branches[i]);
2367                    }
2368                    totalmoved += moved;
2369    #ifdef DEBUG
2370                    fprintf(stderr, "kern_tree: moved=%d\n", moved);
2371    #endif
2372            }
2373            if(!safeguard)
2374                    stack_msg(MSG_WARN, "kern_tree: safeguard terminated possible infinite loop; please report.");
2375            return totalmoved;
2376    }
2377    
2378    static int index_of_revision(revision_t *r)
2379    {
2380            branch_t *b = r->branch;
2381            int i;
2382            for(i = 0; i < b->nrevs; i++)
2383            {
2384                    if(r == b->revs[i])
2385                            return i;
2386            }
2387            stack_msg(MSG_ERR, "index_of_revision: Cannot find revision in branch\n");
2388            return 0;
2389    }
2390    
2391    static void branch_bbox(branch_t *br, int *l, int *r, int *t, int *b)
2392    {
2393            if(l)   *l = br->cx - br->tw/2;
2394            if(r)   *r = br->cx + br->tw/2;
2395            if(t)   *t = br->y;
2396            if(b)   *b = br->y + br->th + ((conf.branch_dupbox && br->nrevs) ? conf.rev_minline + br->h : 0);
2397    }
2398    
2399    static void branch_ext_bbox(branch_t *br, int *l, int *r, int *t, int *b)
2400    {
2401            int extra = conf.branch_margin & 1;     /* Correct +/-1 error on div 2 */
2402            branch_bbox(br, l, r, t, b);
2403            if(l)   *l -= conf.branch_margin/2;
2404            if(r)   *r += conf.branch_margin/2 + extra;
2405            if(t)   *t -= conf.branch_connect + conf.branch_margin/2;
2406            if(b)   *b += conf.branch_margin/2 + extra;
2407    }
2408    
2409    static int branch_distance(branch_t *br1, branch_t *br2)
2410    {
2411            int l1, r1, t1, b1;
2412            int l2, r2, t2, b2;
2413            assert(br1 != NULL);
2414            assert(br2 != NULL);
2415            branch_bbox(br1, &l1, &r1, NULL, NULL);
2416            branch_bbox(br2, &l2, &r2, NULL, NULL);
2417            branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
2418            branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
2419            /* Return:
2420             * - 0 if branches have no horizontal overlap
2421             * - positive if b1 is left of b2
2422             * - negative if b2 is left of b1
2423             */
2424            if((t1 > t2 && t1 < b2) || (b1 > t2 && b1 < b2))
2425                    return l1 < l2 ? l2 - r1 : -(l1 - r2);
2426            else
2427                    return 0;
2428    }
2429    
2430    static int space_needed(branch_t *br1, branch_t *br2)
2431    {
2432            int t1, b1;
2433            int t2, b2;
2434            assert(br1 != NULL);
2435            assert(br2 != NULL);
2436            assert(br1->cx < br2->cx);      /* br1 must be left of br2 */
2437            branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
2438            branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
2439            /* Return:
2440             * - positive if top br1 is located lower than br2
2441             * - negatve is top br2 is located lower than br1
2442             */
2443            if(t1 > t2)
2444                    return -(t1 - b2);
2445            else
2446                    return t2 - b1;
2447    }
2448    
2449    static void move_yr_branch(branch_t *b, int dy)
2450    {
2451            int i, j;
2452    #ifdef DEBUG
2453    /*      fprintf(stderr, "move_yr_branch: b=%s, dy=%d\n", b->branch->branch, dy);*/
2454    #endif
2455            b->y += dy;
2456            for(i = 0; i < b->nrevs; i++)
2457            {
2458                    b->revs[i]->y += dy;
2459                    for(j = 0; j < b->revs[i]->nbranches; j++)
2460                    {
2461    #ifdef DEBUG
2462    /*                      fprintf(stderr, ".");*/
2463    #endif
2464                            move_yr_branch(b->revs[i]->branches[j], dy);
2465                    }
2466            }
2467    }
2468    
2469    static void move_trunk(revision_t *r, int dy)
2470    {
2471            int i, j;
2472            branch_t *b = r->branch;
2473            b->th += dy;
2474            for(i = index_of_revision(r); i < b->nrevs; i++)
2475            {
2476    #ifdef DEBUG
2477                    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);
2478    #endif
2479                    b->revs[i]->y += dy;
2480                    for(j = 0; j < b->revs[i]->nbranches; j++)
2481                    {
2482                            move_yr_branch(b->revs[i]->branches[j], dy);
2483                    }
2484            }
2485    }
2486    
2487    static int space_below(rcsfile_t *rcs, revision_t *r)
2488    {
2489            int i, j;
2490            int bl, br, bb;
2491            int space = INT_MAX;
2492            branch_t *b = r->branch;
2493            branch_t *minb = NULL;
2494    
2495            branch_ext_bbox(b, &bl, &br, NULL, &bb);
2496            for(i = 0; i < rcs->nbranches; i++)
2497            {
2498                    int tbl, tbr, tbt;
2499                    branch_t *tb = rcs->branches[i];
2500                    branch_ext_bbox(tb, &tbl, &tbr, &tbt, NULL);
2501                    if(tb == b)
2502                            continue;
2503                    if(tbt > bb)    /* Must be below our branch */
2504                    {
2505                            if(tb->branchpoint)     /* Take account for the horiz connector */
2506                                    tbl = tb->branchpoint->cx + tb->branchpoint->branch->tw/2;
2507                            if((bl >= tbl && bl <= tbr) || (br <= tbr && br >= tbl))
2508                            {
2509                                    int s = tbt - bb - conf.branch_connect;
2510                                    if(s < space)
2511                                    {
2512                                            space = s;
2513                                            minb = tb;
2514                                    }
2515                            }
2516                    }
2517            }
2518            if(b->branchpoint)
2519            {
2520                    for(i = index_of_revision(r); i < b->nrevs; i++)
2521                    {
2522                            for(j = 0; j < b->revs[i]->nbranches; j++)
2523                            {
2524                                    int s = space_below(rcs, b->revs[i]->branches[j]->revs[0]);
2525                                    if(s < space)
2526                                            space = s;
2527                            }
2528                    }
2529            }
2530    #ifdef DEBUG
2531            fprintf(stderr, "space_below: from %s have %d to %s\n", b->branch->branch, space, minb ? minb->branch->branch : "<recursed>");
2532    #endif
2533            return space;
2534    }
2535    
2536    static int space_available(rcsfile_t *rcs, branch_t *colbr, branch_t *tagbr, int *nl, revision_t **bpcommon)
2537    {
2538            int i;
2539            int space = 0;
2540            int nlinks = 0;
2541            revision_t *r;
2542            branch_t *b;
2543            branch_t *ancestor;
2544            revision_t *branchpoint;
2545    
2546            if(!tagbr->branchpoint || !colbr->branchpoint)
2547            {
2548                    stack_msg(MSG_WARN, "space_available: Trying to stretch the top?");
2549                    return 0;
2550            }
2551    
2552            r = colbr->branchpoint;
2553            b = r->branch;
2554            branchpoint = tagbr->branchpoint;
2555            ancestor = branchpoint->branch;
2556            assert(b != NULL);
2557            assert(ancestor != NULL);
2558    
2559            while(1)
2560            {
2561                    int s;
2562                    int rtag = b == ancestor ? index_of_revision(branchpoint)+1 : 0;
2563                    for(i = index_of_revision(r); i >= rtag; i--)
2564                    {
2565                            if(i > 0)
2566                                    s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
2567                            else
2568                                    s = b->revs[i]->y - (b->y + b->h);
2569                            if(s < conf.rev_maxline)
2570                            {
2571                                    space += conf.rev_maxline - s;
2572                                    nlinks++;
2573                            }
2574                    }
2575                    s = space_below(rcs, r);
2576                    if(s < space)
2577                            space = s;
2578    #ifdef DEBUG
2579                    if(space < 0)
2580                            return -1;
2581    #endif
2582                    if(b == ancestor)
2583                            break;
2584                    r = b->branchpoint;
2585                    if(!r)
2586                    {
2587                            /* Not a common ancestor */
2588                            r = colbr->branchpoint;
2589                            b = r->branch;
2590                            branchpoint = ancestor->branchpoint;
2591                            if(!branchpoint)
2592                            {
2593                                    stack_msg(MSG_WARN, "space_available: No common ancestor?");
2594                                    return 0;
2595                            }
2596                            ancestor = branchpoint->branch;
2597                            assert(ancestor != NULL);
2598                            nlinks = 0;
2599                            space = 0;
2600                            continue;       /* Restart with a new ancestor */
2601                    }
2602                    b = r->branch;
2603            }
2604            if(nl)
2605                    *nl = nlinks;           /* Return the number of links that can stretch */
2606            if(bpcommon)
2607                    *bpcommon = branchpoint;        /* Return the ancestral branchpoint on the common branch */
2608            return space;
2609    }
2610    
2611    static int stretch_branches(rcsfile_t *rcs, branch_t *br1, branch_t *br2, int totalstretch)
2612    {
2613            revision_t *r;
2614            revision_t *bpcommon = NULL;
2615            branch_t *ancestor = NULL;
2616            branch_t *b;
2617            int i;
2618            int space;
2619            int nlinks;
2620            int dy;
2621            int rest;
2622    
2623            space = space_available(rcs, br1, br2, &nlinks, &bpcommon);
2624            if(bpcommon)
2625                    ancestor = bpcommon->branch;
2626    
2627    #ifdef DEBUG
2628            if(space == -1)
2629                    return 0;
2630            fprintf(stderr, "stretch_branches: space available %d over %d links common %s\n", space, nlinks, ancestor->branch->branch);
2631    #endif
2632            if(space < totalstretch)
2633                    return 0;
2634    
2635            dy = totalstretch / nlinks;
2636            rest = totalstretch - dy * nlinks;
2637    
2638            r = br1->branchpoint;
2639            b = r->branch;
2640            while(1)
2641            {
2642                    int rtag = b == ancestor ? index_of_revision(bpcommon)+1 : 0;
2643                    for(i = index_of_revision(r); i >= rtag; i--)
2644                    {
2645                            int s, q;
2646                            if(i > 0)
2647                                    s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
2648                            else
2649                                    s = b->revs[i]->y - (b->y + b->h);
2650                            q = conf.rev_maxline - s;
2651                            if(q > 0)
2652                            {
2653                                    int d = rest ? rest/nlinks+1 : 0;
2654                                    if(q >= dy+d)
2655                                    {
2656                                            move_trunk(b->revs[i], dy+d);
2657                                    }
2658                                    else
2659                                    {
2660                                            move_trunk(b->revs[i], q);
2661                                            rest += dy+d - q;
2662                                    }
2663                                    rest -= d;
2664                                    nlinks--;
2665                            }
2666                    }
2667                    if(b == ancestor)
2668                            break;
2669                    r = b->branchpoint;
2670                    assert(r != NULL);      /* else 'space_available' wouldn't have returned positively */
2671                    b = r->branch;
2672            }
2673            return 1;
2674    }
2675    
2676    static branch_t *find_collision_branch(rcsfile_t *rcs, branch_t *b)
2677    {
2678            int i;
2679            int dist = INT_MAX;
2680            branch_t *col = NULL;
2681    
2682            for(i = 0; i < rcs->nbranches; i++)
2683            {
2684                    int t = branch_distance(rcs->branches[i], b);
2685                    if(t > 0 && t < dist)
2686                    {
2687                            dist = t;
2688                            col = rcs->branches[i];
2689                    }
2690            }
2691            return col;
2692    }
2693    
2694    static void auto_stretch(rcsfile_t *rcs)
2695    {
2696            int i;
2697            int safeguard;
2698    
2699            for(i = 0, safeguard = LOOPSAFEGUARD; i < rcs->nbranches && safeguard; i++)
2700            {
2701                    int bl, pr;
2702                    branch_t *b = rcs->branches[i];
2703                    if(!b->branchpoint)
2704                            continue;
2705                    branch_bbox(b, &bl, NULL, NULL, NULL);
2706                    branch_bbox(b->branchpoint->branch, NULL, &pr, NULL, NULL);
2707                    if(bl - conf.branch_margin - pr > 0)
2708                    {
2709                            branch_t *col;
2710                            int spaceneeded;
2711                            /* There is a potential to move branch b further left.
2712                             * All branches obstructing this one from moving further
2713                             * left must be originating from revisions below
2714                             * b->branchpoint until a common ancester.
2715                             * So, we search all branches for a branch that lies left
2716                             * of b and is closest to b. This is then the collission
2717                             * branch that needs to be moved.
2718                             */
2719                            col = find_collision_branch(rcs, b);
2720                            if(!col)
2721                                    continue;
2722                            spaceneeded = space_needed(col, b);
2723                            if(spaceneeded < 0)
2724                                    continue;
2725    #ifdef DEBUG
2726                            fprintf(stderr, "auto_stretch: %s collides %s need %d\n", b->branch->branch, col->branch->branch, spaceneeded);
2727    #endif
2728                            /* Trace the collision branch back to find the common ancester
2729                             * of both col and b. All revisions encountered while traversing
2730                             * backwards must be stretched, including all revisions on the
2731                             * common ancester from where the branches sprout.
2732                             */
2733                            if(stretch_branches(rcs, col, b, spaceneeded))
2734                            {
2735                                    if(kern_tree(rcs))
2736                                    {
2737                                            /* Restart the process because movement can
2738                                             * cause more movement.
2739                                             */
2740                                            i = 0 - 1;      /* -1 for the i++ of the loop */
2741                                            safeguard--;    /* Prevent infinite loop, just in case */
2742                                    }
2743                                    /*return;*/
2744                            }
2745                    }
2746            }
2747            if(!safeguard)
2748                    stack_msg(MSG_ERR, "auto_stretch: safeguard terminated possible infinite loop; please report.");
2749    }
2750    
2751    static void fold_branch(rcsfile_t *rcs, revision_t *r)
2752    {
2753            int i, j;
2754            branch_t *btag = NULL;
2755    
2756            for(i = 0; i < r->nbranches; i++)
2757            {
2758                    branch_t *b = r->branches[i];
2759                    if(!b->nrevs && b->ntags < 2)
2760                    {
2761                            /* No commits in this branch and no duplicate tags */
2762                            if(!btag)
2763                                    btag = b;
2764                            else
2765                            {
2766                                    /* We have consecutive empty branches, fold */
2767                                    b->folded = 1;
2768                                    b->folded_to = btag;
2769                                    for(j = 0; j < rcs->nbranches; j++)
2770                                    {
2771                                            if(b == rcs->branches[j])
2772                                            {
2773                                                    /* Zap the branch from the admin */
2774                                                    memmove(&rcs->branches[j],
2775                                                            &rcs->branches[j+1],
2776                                                            (rcs->nbranches - j - 1)*sizeof(rcs->branches[0]));
2777                                                    rcs->nbranches--;
2778                                                    rcs->nfolds++;
2779                                                    break;
2780                                            }
2781    
2782                                    }
2783                                    memmove(&r->branches[i], &r->branches[i+1], (r->nbranches - i - 1)*sizeof(r->branches[0]));
2784                                    r->nbranches--;
2785                                    i--;    /* We have one less now */
2786    
2787                                    /* Add to the fold-list */
2788                                    btag->folds = xrealloc(btag->folds, (btag->nfolds+1) * sizeof(btag->folds[0]));
2789                                    btag->folds[btag->nfolds] = b;
2790                                    btag->nfolds++;
2791                            }
2792                    }
2793                    else
2794                    {
2795                            if(!conf.branch_foldall)
2796                                    btag = NULL;    /* Start a new box */
2797                            /* Recursively fold sub-branches */
2798                            for(j = 0; j < b->nrevs; j++)
2799                                    fold_branch(rcs, b->revs[j]);
2800                    }
2801            }
2802    }
2803    
2804    static void mark_subtree(branch_t *b)
2805    {
2806            int i, j;
2807            b->subtree_draw = 1;
2808            for(i = 0; i < b->nrevs; i++)
2809            {
2810                    for(j = 0; j < b->revs[i]->nbranches; j++)
2811                            mark_subtree(b->revs[i]->branches[j]);
2812            }
2813    }
2814    
2815    static void make_layout(rcsfile_t *rcs)
2816    {
2817            int i, j;
2818            int x, y;
2819            int w, h;
2820            int w2;
2821    
2822            /* Remove all unwanted revisions */
2823            if(conf.strip_untagged)
2824            {
2825                    int fr = conf.strip_first_rev ? 0 : 1;
2826                    for(i = 0; i < rcs->nbranches; i++)
2827                    {
2828                            branch_t *bp = rcs->branches[i];
2829                            for(j = fr; j < bp->nrevs-1; j++)
2830                            {
2831                                    if(!bp->revs[j]->ntags && bp->revs[j]->stripped >= 0 && !bp->revs[j]->nbranches)
2832                                    {
2833                                            bp->revs[j]->stripped = 1;
2834                                            memmove(&bp->revs[j], &bp->revs[j+1], (bp->nrevs-j-1) * sizeof(bp->revs[0]));
2835                                            bp->nrevs--;
2836                                            j--;
2837                                    }
2838                            }
2839                    }
2840            }
2841    
2842            /* Find the sub-tree(s) we want to see */
2843            if(conf.branch_subtree && conf.branch_subtree[0])
2844            {
2845                    branch_t **b;
2846                    revision_t **r;
2847                    rev_t rev;
2848                    int k;
2849                    char *tag = conf.branch_subtree;
2850    
2851                    /* First translate any symbolic tag to a real branch/revision number */
2852                    if(rcs->tags)
2853                    {
2854                            for(k = 0; k < rcs->tags->ntags; k++)
2855                            {
2856                                    if(!strcmp(conf.branch_subtree, rcs->tags->tags[k]->tag))
2857                                    {
2858                                            if(rcs->tags->tags[k]->rev->isbranch)
2859                                                    tag = rcs->tags->tags[k]->rev->branch;
2860                                            else
2861                                                    tag = rcs->tags->tags[k]->rev->rev;
2862                                            break;
2863                                    }
2864                            }
2865                    }
2866    
2867                    /* Find the corresponding branch */
2868                    rev.branch = tag;
2869                    rev.rev = NULL;
2870                    rev.isbranch = 1;
2871                    b = bsearch(&rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
2872                    if(b)
2873                    {
2874                            if((*b)->branchpoint)
2875                            {
2876                                    subtree_branch = *b;
2877                                    for(k = 0; k < (*b)->branchpoint->nbranches; k++)
2878                                            mark_subtree((*b)->branchpoint->branches[k]);
2879                            }
2880                            /*
2881                             * else -> we want everything.
2882                             * This happens for the top level branch because it has no
2883                             * branchpoint. We do not set the subtree_branch, which then
2884                             * results in drawing the whole tree as if we did not select a
2885                             * particular branch.
2886                             */
2887                    }
2888                    else
2889                    {
2890                            /* Maybe it is a revision we want all subtrees from */
2891                            rev.rev = tag;
2892                            rev.branch = NULL;
2893                            rev.isbranch = 0;
2894                            r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
2895                            if(r)
2896                            {
2897                                    if((*r)->nbranches)
2898                                    {
2899                                            subtree_branch = (*r)->branches[0];
2900                                            subtree_rev = *r;
2901                                            for(k = 0; k < (*r)->nbranches; k++)
2902                                                    mark_subtree((*r)->branches[k]);
2903                                    }
2904                                    /*
2905                                     * else -> we select everything.
2906                                     * This happens for the any revision that has no branches.
2907                                     * We do not set the subtree_branch, which then results in
2908                                     * drawing the whole tree as if we did not select a
2909                                     * particular revision's branches.
2910                                     */
2911                            }
2912                    }
2913            }
2914    
2915            /* Fold all empty branches in one box on the same branchpoint */
2916            if(conf.branch_fold)
2917            {
2918                    for(i = 0; i < rcs->branches[0]->nrevs; i++)
2919                    {
2920                            if(rcs->branches[0]->revs[i]->nbranches > 0)
2921                                    fold_branch(rcs, rcs->branches[0]->revs[i]);
2922                    }
2923            }
2924    
2925            /* Remove all unwanted tags */
2926            for(i = 0; i < rcs->nbranches; i++)
2927            {
2928                    branch_t *bp = rcs->branches[i];
2929                    for(j = 0; j < bp->nrevs; j++)
2930                    {
2931                            revision_t *r = bp->revs[j];
2932                            int k;
2933                            for(k = 0; k < r->ntags; k++)
2934                            {
2935                                    if(r->tags[k]->ignore > 0)
2936                                    {
2937                                            memmove(&r->tags[k], &r->tags[k+1], (r->ntags-k-1) * sizeof(r->tags[0]));
2938                                            r->ntags--;
2939                                            k--;
2940                                    }
2941                            }
2942                    }
2943            }
2944    
2945            /* Calculate the box-sizes of the revisions */
2946            for(i = 0; i < rcs->nsrev; i++)
2947            {
2948                    revision_t *rp;
2949                    int w;
2950                    int h;
2951                    rp = rcs->srev[i];
2952                    rp->revtext = expand_string(conf.rev_text, rcs, rp, rp->rev, NULL, rp->ntags ? rp->tags[0] : NULL);
2953                    w = get_swidth(rp->revtext, &conf.rev_text_font);
2954                    j = get_swidth(rp->rev->rev, &conf.rev_font);
2955                    if(j > w)
2956                            w = j;
2957                    h = get_sheight(rp->revtext, &conf.rev_text_font);
2958                    if(!conf.rev_hidenumber)
2959                            h += get_sheight(rp->rev->rev, &conf.rev_font);
2960                    for(j = 0; j < rp->ntags; j++)
2961                  {                  {
2962                          int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);                          int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);
2963                            int th;
2964                          if(ww > w) w = ww;                          if(ww > w) w = ww;
2965                          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;
2966                            rp->tags[j]->yofs = h + th/2 + conf.rev_tspace;
2967                            h += th;
2968                  }                  }
2969                  rp->w = w + conf.rev_lspace + conf.rev_rspace;                  rp->w = w + conf.rev_lspace + conf.rev_rspace;
2970                  rp->h = h + conf.rev_tspace + conf.rev_bspace;                  rp->h = h + conf.rev_tspace + conf.rev_bspace;
# Line 1199  Line 2976 
2976                  branch_t *bp = rcs->branches[i];                  branch_t *bp = rcs->branches[i];
2977                  int w;                  int w;
2978                  int h;                  int h;
2979                  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++)  
2980                  {                  {
2981                          int ww = get_swidth(bp->tags[j]->tag, &conf.branch_font);                          w = get_swidth(bp->branch->branch, &conf.branch_font);
2982                          if(ww > w) w = ww;                          if(conf.rev_hidenumber)
2983                          h += get_sheight(bp->tags[j]->tag, &conf.branch_font);                                  h = 0;
2984                            else
2985                                    h = get_sheight(bp->branch->branch, &conf.branch_font);
2986                            for(j = 0; j < bp->ntags; j++)
2987                            {
2988                                    int ww = get_swidth(bp->tags[j]->tag, &conf.branch_tag_font);
2989                                    if(ww > w) w = ww;
2990                                    h += get_sheight(bp->tags[j]->tag, &conf.branch_tag_font);
2991                            }
2992                    }
2993                    else
2994                    {
2995                            int h1, h2;
2996                            int w1, w2;
2997                            int fw;
2998                            w1 = get_swidth(bp->branch->branch, &conf.branch_font);
2999                            w1 += get_swidth(" ", &conf.branch_font);
3000                            w2 = get_swidth(bp->tags[0]->tag, &conf.branch_tag_font);
3001                            fw = w1;
3002                            w = w1 + w2;
3003                            h1 = get_sheight(bp->branch->branch, &conf.branch_font);
3004                            h2 = get_sheight(bp->tags[0]->tag, &conf.branch_tag_font);
3005                            h = MAX(h1, h2);
3006                            for(j = 0; j < bp->nfolds; j++)
3007                            {
3008                                    w1 = get_swidth(bp->folds[j]->branch->branch, &conf.branch_font);
3009                                    w1 += get_swidth(" ", &conf.branch_font);
3010                                    w2 = get_swidth(bp->folds[j]->tags[0]->tag, &conf.branch_tag_font);
3011                                    if(w1 > fw)
3012                                            fw = w1;
3013                                    if(w1 + w2 > w)
3014                                            w = w1 + w2;
3015                                    h1 = get_sheight(bp->folds[j]->branch->branch, &conf.branch_font);
3016                                    h2 = get_sheight(bp->folds[j]->tags[0]->tag, &conf.branch_tag_font);
3017                                    h += MAX(h1, h2);
3018                            }
3019                            bp->fw = fw;
3020                  }                  }
3021                  w += conf.branch_lspace + conf.branch_rspace;                  w += conf.branch_lspace + conf.branch_rspace;
3022                  h += conf.branch_tspace + conf.branch_bspace;                  h += conf.branch_tspace + conf.branch_bspace;
3023                  bp->w = w;                  bp->w = w;
3024                  bp->h = h;                  bp->h = h;
3025                  for(j = 0; j < bp->nrevs; j++)                  if(conf.left_right)
3026                    {
3027                            for(j = 0; j < bp->nrevs; j++)
3028                            {
3029                                    if(bp->revs[j]->h > h)
3030                                            h = bp->revs[j]->h;
3031                                    w += bp->revs[j]->w + conf.rev_minline;
3032                            }
3033                            if(conf.branch_dupbox && bp->nrevs)
3034                                    w += bp->w + conf.rev_minline;
3035                    }
3036                    else
3037                  {                  {
3038                          if(bp->revs[j]->w > w)                          for(j = 0; j < bp->nrevs; j++)
3039                                  w = bp->revs[j]->w;                          {
3040                          h += bp->revs[j]->h + conf.rev_minline;                                  if(bp->revs[j]->w > w)
3041                                            w = bp->revs[j]->w;
3042                                    h += bp->revs[j]->h + conf.rev_minline;
3043                            }
3044                            if(conf.branch_dupbox && bp->nrevs)
3045                                    h += bp->h + conf.rev_minline;
3046                  }                  }
3047                  bp->th = h;                  bp->th = h;
3048                  bp->tw = w;                  bp->tw = w;
3049          }          }
3050    
3051          /* Calculate the relative positions of revs in a branch */          /* Calculate the relative positions of revs in a branch */
3052          for(i = 0; i < rcs->nbranches; i++)          if(conf.left_right)
3053            {
3054                    for(i = 0; i < rcs->nbranches; i++)
3055                    {
3056                            branch_t *b = rcs->branches[i];
3057                            y = b->th/2;
3058                            x = b->w;
3059                            b->y = y;
3060                            b->cx = 0;
3061                            for(j = 0; j < b->nrevs; j++)
3062                            {
3063                                    x += conf.rev_minline;
3064                                    b->revs[j]->y = y;
3065                                    b->revs[j]->cx = x;
3066                                    x += b->revs[j]->w;
3067                            }
3068                    }
3069            }
3070            else
3071            {
3072                    for(i = 0; i < rcs->nbranches; i++)
3073                    {
3074                            branch_t *b = rcs->branches[i];
3075                            x = b->tw/2;
3076                            y = b->h;
3077                            b->cx = x;
3078                            b->y = 0;
3079                            for(j = 0; j < b->nrevs; j++)
3080                            {
3081                                    y += conf.rev_minline;
3082                                    b->revs[j]->cx = x;
3083                                    b->revs[j]->y = y;
3084                                    y += b->revs[j]->h;
3085                            }
3086                    }
3087            }
3088    
3089            /* Initially reposition the branches from bottom to top progressively right */
3090            if(conf.left_right)
3091            {
3092                    x = rcs->branches[0]->y;
3093                    w2 = rcs->branches[0]->th / 2;
3094                    for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
3095                    {
3096                            initial_reposition_branch_lr(rcs->branches[0]->revs[i], &x, &w2);
3097                    }
3098            }
3099            else
3100            {
3101                    x = rcs->branches[0]->cx;
3102                    w2 = rcs->branches[0]->tw / 2;
3103                    for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
3104                    {
3105                            initial_reposition_branch(rcs->branches[0]->revs[i], &x, &w2);
3106                    }
3107            }
3108    
3109            /* Initially move branches left if there is room */
3110            kern_tree(rcs);
3111    
3112            /* Try to kern the branches more by expanding the inter-revision spacing */
3113            if(conf.auto_stretch && !conf.left_right)
3114                    auto_stretch(rcs);
3115    
3116            /* Calculate overall image size */
3117            if(conf.left_right)
3118            {
3119                    x = rcs->branches[0]->cx;
3120                    y = rcs->branches[0]->y - rcs->branches[0]->th/2;
3121            }
3122            else
3123            {
3124                    x = rcs->branches[0]->cx - rcs->branches[0]->tw/2;
3125                    y = rcs->branches[0]->y;
3126            }
3127            w = rcs->branches[0]->tw;
3128            h = rcs->branches[0]->th;
3129            for(i = 1; i < rcs->nbranches; i++)
3130                    rect_union(&x, &y, &w, &h, rcs->branches[i]);
3131            rcs->tw = w;
3132            rcs->th = h;
3133    
3134            /* Flip the entire tree */
3135            if(conf.upside_down)
3136          {          {
3137                  branch_t *b = rcs->branches[i];                  if(conf.left_right)
3138                  x = b->tw/2;                  {
3139                  y = b->h;                          x += rcs->tw;
3140                  b->cx = x;                          for(i = 0; i < rcs->nbranches; i++)
3141                  b->y = 0;                          {
3142                  for(j = 0; j < b->nrevs; j++)                                  branch_t *b = rcs->branches[i];
3143                                    for(j = 0; j < b->nrevs; j++)
3144                                    {
3145                                            revision_t *r = b->revs[j];
3146                                            r->cx = x - r->cx - r->w;
3147                                    }
3148                                    b->cx = x - b->cx - b->w;
3149                            }
3150                    }
3151                    else
3152                  {                  {
3153                          y += conf.rev_minline;                          y += rcs->th;
3154                          b->revs[j]->cx = x;                          for(i = 0; i < rcs->nbranches; i++)
3155                          b->revs[j]->y = y;                          {
3156                          y += b->revs[j]->h;                                  branch_t *b = rcs->branches[i];
3157                                    for(j = 0; j < b->nrevs; j++)
3158                                    {
3159                                            revision_t *r = b->revs[j];
3160                                            r->y = y - r->y - r->h;
3161                                    }
3162                                    b->y = y - b->y - b->h;
3163                            }
3164                  }                  }
3165          }          }
3166    
3167          /* Reposition the branches */          /* Relocate the lot if we only draw a sub-tree */
3168          x = rcs->branches[0]->cx;          if(subtree_branch)
         w2 = rcs->branches[0]->tw / 2;  
         for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)  
3169          {          {
3170                  reposition_branch(rcs->branches[0]->revs[i], &x, &w2);                  int xx, yy;
         }  
3171    
3172          /* Try to move branches left if there is room (kerning) */                  if(subtree_branch->folded)      /* Fix the reference if the branch got folded */
3173          for(moved = 1; moved; )                          subtree_branch = subtree_branch->folded_to;
3174          {  
3175                  moved = 0;                  xx = conf.left_right ? subtree_branch->cx : subtree_branch->cx - subtree_branch->tw/2;
3176                  for(i = 1; i < rcs->nbranches; i++)                  yy = conf.left_right ? subtree_branch->y - subtree_branch->th/2 : subtree_branch->y;
3177                    if(subtree_branch != rcs->branches[0])
3178                  {                  {
3179                          moved += kern_branch(rcs, rcs->branches[i]);                          if(conf.left_right)
3180                                    xx -= conf.branch_connect;
3181                            else
3182                                    yy -= conf.branch_connect;
3183                  }                  }
3184                    for(i = 0; i < rcs->nbranches; i++)
3185                            move_branch(rcs->branches[i], -xx, -yy);
3186          }          }
3187    
3188          /* Move everything w.r.t. the top-left margin */          /* Move everything w.r.t. the top-left margin */
3189          for(i = 0; i < rcs->nbranches; i++)          for(i = 0; i < rcs->nbranches; i++)
3190                  move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);                  move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
   
         /* Calculate overall image size */  
         x = rcs->branches[0]->cx - rcs->branches[0]->tw/2;  
         y = rcs->branches[0]->y;  
         w = rcs->branches[0]->tw;  
         h = rcs->branches[0]->th;  
         for(i = 1; i < rcs->nbranches; i++)  
                 rect_union(&x, &y, &w, &h, rcs->branches[i]);  
         rcs->tw = w;  
         rcs->th = h;  
3191  }  }
3192    
3193  /*  /*
# Line 1276  Line 3195 
3195   * Imagemap functions   * Imagemap functions
3196   **************************************************************************   **************************************************************************
3197   */   */
3198  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)
3199    {
3200            char *href = expand_string(conf.map_merge_href, rcs, tr, tr->rev, fr->rev, NULL);
3201            char *alt = expand_string(conf.map_merge_alt, rcs, tr, tr->rev, fr->rev, NULL);
3202            const char *htp = conf.html_level == HTMLLEVEL_X ? " /" : "";
3203    
3204            if(x1 > 0 && x2 > 0 && y1 > 0 && y2 > 0)
3205                    fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3206                                            href, x1, y1, x2, y2, alt, htp);
3207            xfree(alt);
3208            xfree(href);
3209    
3210            if(im)
3211            {
3212                    gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, clr(im, "title_color", NULL, NULL)->id);
3213                    gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, clr(im, "tag_color.id", NULL, NULL)->id);
3214                    gdImageLine(im, x1, y1, x2, y2, clr(im, "title_color", NULL, NULL)->id);
3215            }
3216    }
3217    
3218    static void map_merges(rcsfile_t *rcs, FILE *fp, gdImagePtr im)
3219    {
3220            int i;
3221            int tagh2 = get_sheight("Hg", &conf.tag_font) / 2;
3222            int bm = conf.branch_margin / 2;
3223    
3224            for(i = 0; i < rcs->nmerges; i++)
3225            {
3226                    revision_t *fr;
3227                    revision_t *tr;
3228                    int x1, x2, y1, y2;
3229                    switch(rcs->merges[i].type)
3230                    {
3231                    case TR_TAG:
3232                            fr = rcs->merges[i].from.tag->logrev;
3233                            tr = rcs->merges[i].to.tag->logrev;
3234                            break;
3235                    case TR_REVISION:
3236                            fr = rcs->merges[i].from.rev;
3237                            tr = rcs->merges[i].to.rev;
3238                            break;
3239                    default:
3240                            continue;
3241                    }
3242                    if(!fr || !tr || fr == tr)
3243                            continue;       /* This can happen with detached tags and self-references */
3244                    if(conf.left_right)
3245                    {
3246                            if(fr->branch == tr->branch)
3247                            {
3248                                    y1 = fr->y - fr->h/2;
3249                                    y2 = tr->y - tr->h/2;
3250                            }
3251                            else
3252                            {
3253                                    if(fr->y < tr->y)
3254                                    {
3255                                            y1 = fr->y + fr->h/2;
3256                                            y2 = tr->y - tr->h/2;
3257                                    }
3258                                    else
3259                                    {
3260                                            y1 = fr->y - fr->h/2;
3261                                            y2 = tr->y + tr->h/2;
3262                                    }
3263                            }
3264                            x1 = fr->cx + fr->w/2;
3265                            x2 = tr->cx + tr->w/2;
3266                    }
3267                    else
3268                    {
3269                            if(fr->branch == tr->branch)
3270                            {
3271                                    x1 = fr->cx - fr->w/2;
3272                                    x2 = tr->cx - tr->w/2;
3273                            }
3274                            else
3275                            {
3276                                    if(fr->cx < tr->cx)
3277                                    {
3278                                            x1 = fr->cx + fr->w/2;
3279                                            x2 = tr->cx - tr->w/2;
3280                                    }
3281                                    else
3282                                    {
3283                                            x1 = fr->cx - fr->w/2;
3284                                            x2 = tr->cx + tr->w/2;
3285                                    }
3286                            }
3287                            if(rcs->merges[i].type == TR_TAG)
3288                            {
3289                                    y1 = fr->y + rcs->merges[i].from.tag->yofs;
3290                                    y2 = tr->y + rcs->merges[i].to.tag->yofs;
3291                            }
3292                            else
3293                            {
3294                                    y1 = fr->y + fr->h/2;
3295                                    y2 = tr->y + tr->h/2;
3296                            }
3297                    }
3298    
3299                    if(conf.left_right)
3300                    {
3301                            if(fr->branch == tr->branch)
3302                            {
3303                                    map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-bm, x1+bm, y1);
3304                                    map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-bm, x2+bm, y2);
3305                            }
3306                            else
3307                            {
3308                                    if(y1 > y2)
3309                                    {
3310                                            map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-bm, x1+bm, y1);
3311                                            map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2, x2+bm, y2+bm);
3312                                    }
3313                                    else
3314                                    {
3315                                            map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1, x1+bm, y1+bm);
3316                                            map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-bm, x2+bm, y2);
3317                                    }
3318                            }
3319                    }
3320                    else
3321                    {
3322                            if(fr->branch == tr->branch)
3323                            {
3324                                    map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-tagh2, x1, y1+tagh2);
3325                                    map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-tagh2, x2, y2+tagh2);
3326                            }
3327                            else
3328                            {
3329                                    if(x1 > x2)
3330                                    {
3331                                            map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-tagh2, x1, y1+tagh2);
3332                                            map_merge_box(rcs, fp, fr, tr, im, x2, y2-tagh2, x2+bm, y2+tagh2);
3333                                    }
3334                                    else
3335                                    {
3336                                            map_merge_box(rcs, fp, fr, tr, im, x1, y1-tagh2, x1+bm, y1+tagh2);
3337                                            map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-tagh2, x2, y2+tagh2);
3338                                    }
3339                            }
3340                    }
3341            }
3342    }
3343    
3344    static void make_imagemap(rcsfile_t *rcs, FILE *fp, gdImagePtr im)
3345  {  {
3346          int i, j;          int i, j;
3347          char *href;          const char *htp = conf.html_level == HTMLLEVEL_X ? " /" : "";
3348          char *alt;  
3349          fprintf(fp, "<map name=\"%s\">\n", conf.map_name);          switch(conf.html_level)
3350            {
3351            case HTMLLEVEL_4:
3352                    fprintf(fp, "<map name=\"%s\" id=\"%s\">\n", conf.map_name, conf.map_name);
3353                    break;
3354            case HTMLLEVEL_X:
3355                    fprintf(fp, "<map id=\"%s\">\n", conf.map_name);
3356                    break;
3357            default:
3358                    fprintf(fp, "<map name=\"%s\">\n", conf.map_name);
3359            }
3360