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