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

Diff of /cvsgraph/cvsgraph.c

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

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