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

Annotate of /cvsgraph/cvsgraph.c

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


Revision 1.30 - (hide annotations)
Sun Mar 9 22:36:50 2003 UTC (14 years, 7 months ago) by bertho
Branch: MAIN
Changes since 1.29: +256 -5 lines
File MIME type: text/plain
- add merge visualization
- Provide solution for possible XSS bug in tag expansions
1 bertho 1.1 /*
2     * CvsGraph graphical representation generator of brances and revisions
3     * of a file in cvs/rcs.
4     *
5 bertho 1.29 * Copyright (C) 2001,2002,2003 B. Stultiens
6 bertho 1.1 *
7     * 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
9     * the Free Software Foundation; either version 2 of the License, or
10     * (at your option) any later version.
11     *
12     * This program is distributed in the hope that it will be useful,
13     * but WITHOUT ANY WARRANTY; without even the implied warranty of
14     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15     * GNU General Public License for more details.
16     *
17     * You should have received a copy of the GNU General Public License
18     * along with this program; if not, write to the Free Software
19     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20     */
21    
22 bertho 1.14 #include "config.h"
23    
24 bertho 1.1 #include <stdio.h>
25     #include <stdlib.h>
26     #include <unistd.h>
27     #include <string.h>
28     #include <assert.h>
29     #include <sys/types.h>
30     #include <sys/stat.h>
31     #include <sys/wait.h>
32     #include <fcntl.h>
33     #include <regex.h>
34     #include <errno.h>
35 bertho 1.8 #include <ctype.h>
36 bertho 1.9 #include <time.h>
37 bertho 1.18 #include <limits.h>
38 bertho 1.30 #include <regex.h>
39 bertho 1.9
40 bertho 1.14 #ifdef HAVE_GETOPT_H
41     # include <getopt.h>
42     #endif
43    
44 bertho 1.1 #include <gd.h>
45     #include <gdfontt.h>
46    
47     #include "cvsgraph.h"
48     #include "utils.h"
49     #include "readconf.h"
50 bertho 1.7 #include "rcs.h"
51 bertho 1.1
52 bertho 1.5 #if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG) && !defined(HAVE_IMAGE_JPEG)
53     # error No image output format available. Check libgd
54     #endif
55    
56    
57 bertho 1.21 /*#define DEBUG 1*/
58 bertho 1.19 /*#define NOGDFILL 1*/
59 bertho 1.26 /*#define DEBUG_IMAGEMAP 1*/
60 bertho 1.19
61     #define LOOPSAFEGUARD 100 /* Max itterations in possible infinite loops */
62 bertho 1.1
63     #ifndef MAX
64     # define MAX(a,b) ((a) > (b) ? (a) : (b))
65     #endif
66    
67     #ifndef MIN
68     # define MIN(a,b) ((a) < (b) ? (a) : (b))
69     #endif
70    
71     #define ALIGN_HL 0x00
72     #define ALIGN_HC 0x01
73     #define ALIGN_HR 0x02
74 bertho 1.9 #define ALIGN_HX 0x0f
75 bertho 1.1 #define ALIGN_VT 0x00
76     #define ALIGN_VC 0x10
77     #define ALIGN_VB 0x20
78     #define ALIGN_VX 0xf0
79 bertho 1.9
80 bertho 1.1 /*
81     **************************************************************************
82     * Globals
83     **************************************************************************
84     */
85    
86     config_t conf;
87 bertho 1.7 int debuglevel;
88 bertho 1.9 color_t white_color = {255, 255, 255, 0};
89     color_t black_color = {0, 0, 0, 0};
90    
91 bertho 1.30 /*
92     **************************************************************************
93     * Forwards
94     **************************************************************************
95     */
96     static void zap_string(void);
97     static char *dup_string(void);
98     static void add_string_str(const char *s);
99     static void add_string_ch(int ch);
100     static void add_string_date(const char *d);
101     static void add_string_str_html(const char *s, int maxlen);
102     static void add_string_str_len(const char *s, int maxlen);
103 bertho 1.1
104     /*
105     **************************************************************************
106 bertho 1.17 * Debug routines
107 bertho 1.1 **************************************************************************
108     */
109 bertho 1.19 static void dump_rev(char *p, rev_t *r)
110 bertho 1.1 {
111 bertho 1.7 printf("%s", p);
112     if(r)
113     printf("'%s', '%s', %d\n", r->rev, r->branch, r->isbranch);
114     else
115     printf("<null>\n");
116 bertho 1.1 }
117    
118 bertho 1.19 static void dump_id(char *p, char *d)
119 bertho 1.1 {
120 bertho 1.7 printf("%s", p);
121     if(d)
122     printf("'%s'\n", d);
123     else
124     printf("<null>\n");
125 bertho 1.1 }
126    
127 bertho 1.19 static void dump_idrev(char *p, idrev_t *t)
128 bertho 1.1 {
129 bertho 1.7 printf("%s", p);
130     if(t)
131 bertho 1.1 {
132 bertho 1.7 printf("'%s' -> ", t->id);
133     dump_rev("", t->rev);
134 bertho 1.1 }
135     else
136 bertho 1.7 printf("<null>\n");
137 bertho 1.1 }
138    
139 bertho 1.19 static void dump_tag(char *p, tag_t *t)
140 bertho 1.1 {
141 bertho 1.7 printf("%s", p);
142     if(t)
143 bertho 1.1 {
144 bertho 1.7 printf("'%s' -> ", t->tag);
145     dump_rev("", t->rev);
146 bertho 1.1 }
147 bertho 1.7 else
148     printf("<null>\n");
149 bertho 1.1 }
150    
151 bertho 1.19 static void dump_delta(char *p, delta_t *d)
152 bertho 1.1 {
153 bertho 1.7 int i;
154     printf("%sdelta.rev : ", p);
155     dump_rev("", d->rev);
156     printf("%sdelta.date : %s\n", p, d->date);
157     printf("%sdelta.author: %s\n", p, d->author);
158     printf("%sdelta.state : %s\n", p, d->state);
159     for(i = 0; d->branches && i < d->branches->nrevs; i++)
160 bertho 1.1 {
161 bertho 1.7 printf("%sdelta.branch: ", p);
162     dump_rev("", d->branches->revs[i]);
163 bertho 1.1 }
164 bertho 1.7 printf("%sdelta.next : ", p);
165     dump_rev("", d->next);
166     printf("\n");
167 bertho 1.1 }
168    
169 bertho 1.19 static void dump_dtext(char *p, dtext_t *d)
170 bertho 1.1 {
171 bertho 1.7 printf("%sdtext.rev : ", p);
172     dump_rev("", d->rev);
173     printf("%sdtext.log : %d bytes\n", p, d->log ? strlen(d->log) : -1);
174     printf("%sdtext.text : %d bytes\n", p, d->text ? strlen(d->text) : -1);
175     printf("\n");
176 bertho 1.1 }
177    
178 bertho 1.19 static void dump_rcsfile(rcsfile_t *rcs)
179 bertho 1.1 {
180 bertho 1.7 int i;
181     printf("root : '%s'\n", rcs->root);
182     printf("module : '%s'\n", rcs->module);
183     printf("file : '%s'\n", rcs->file);
184     dump_rev("head : ", rcs->head);
185     dump_rev("branch : ", rcs->branch);
186     printf("access :\n");
187     for(i = 0; rcs->access && i < rcs->access->nids; i++)
188     dump_id("\t", rcs->access->ids[i]);
189     printf("tags :\n");
190     for(i = 0; rcs->tags && i < rcs->tags->ntags; i++)
191     dump_tag("\t", rcs->tags->tags[i]);
192     printf("locks :%s\n", rcs->strict ? " (strict)" : "");
193     for(i = 0; rcs->locks && i < rcs->locks->nidrevs; i++)
194     dump_idrev("\t", rcs->locks->idrevs[i]);
195     printf("comment: '%s'\n", rcs->comment);
196     printf("expand : '%s'\n", rcs->expand ? rcs->expand : "(default -kv)");
197     printf("deltas :\n");
198     for(i = 0; rcs->deltas && i < rcs->deltas->ndeltas; i++)
199     dump_delta("\t", rcs->deltas->deltas[i]);
200     printf("desc : '%s'\n", rcs->desc);
201     printf("dtexts :\n");
202     for(i = 0; rcs->dtexts && i < rcs->dtexts->ndtexts; i++)
203     dump_dtext("\t", rcs->dtexts->dtexts[i]);
204    
205     fflush(stdout);
206 bertho 1.1 }
207    
208 bertho 1.7 /*
209     **************************************************************************
210     * Read the rcs file
211     **************************************************************************
212     */
213     rcsfile_t *get_rcsfile(const char *cvsroot, const char *module, const char *file)
214 bertho 1.1 {
215 bertho 1.7 char *cmd = NULL;
216     int rv;
217 bertho 1.1
218 bertho 1.18 if(file)
219 bertho 1.9 {
220 bertho 1.18 cmd = xmalloc(strlen(cvsroot) + strlen(module) + strlen(file) + 1);
221     sprintf(cmd, "%s%s%s", cvsroot, module, file);
222     if(!(rcsin = fopen(cmd, "rb")))
223     {
224     perror(cmd);
225     return NULL;
226     }
227     input_file = cmd;
228     }
229     else
230     {
231     rcsin = stdin;
232     input_file = "<stdin>";
233 bertho 1.9 }
234 bertho 1.7 line_number = 1;
235     rv = rcsparse();
236 bertho 1.18 if(file)
237     {
238     fclose(rcsin);
239     xfree(cmd);
240     }
241 bertho 1.7 if(rv)
242 bertho 1.1 return NULL;
243 bertho 1.7 input_file = NULL;
244 bertho 1.18 if(file)
245     {
246     rcsfile->root = xstrdup(cvsroot);
247     rcsfile->module = xstrdup(module);
248     rcsfile->file = xstrdup(file);
249     }
250     else
251     {
252     rcsfile->root = xstrdup("");
253     rcsfile->module = xstrdup("");
254     rcsfile->file = xstrdup("<stdin>");
255     }
256 bertho 1.7 return rcsfile;
257 bertho 1.1 }
258    
259     /*
260     **************************************************************************
261     * Sort and find helpers
262     **************************************************************************
263     */
264 bertho 1.5 int count_dots(const char *s)
265     {
266     int i;
267     for(i = 0; *s; s++)
268     {
269     if(*s == '.')
270     i++;
271     }
272     return i;
273     }
274    
275 bertho 1.7 int compare_rev(int bcmp, const rev_t *r1, const rev_t *r2)
276 bertho 1.5 {
277     int d1, d2;
278 bertho 1.7 char *c1, *c2;
279 bertho 1.5 char *v1, *v2;
280     char *s1, *s2;
281     int retval = 0;
282     assert(r1 != NULL);
283     assert(r2 != NULL);
284 bertho 1.7 if(bcmp)
285     {
286     assert(r1->branch != NULL);
287     assert(r2->branch != NULL);
288     c1 = r1->branch;
289     c2 = r2->branch;
290     }
291     else
292     {
293     assert(r1->rev != NULL);
294     assert(r2->rev != NULL);
295     c1 = r1->rev;
296     c2 = r2->rev;
297     }
298 bertho 1.5
299 bertho 1.7 d1 = count_dots(c1);
300     d2 = count_dots(c2);
301 bertho 1.5 if(d1 != d2)
302     {
303     return d1 - d2;
304     }
305    
306 bertho 1.7 s1 = v1 = xstrdup(c1);
307     s2 = v2 = xstrdup(c2);
308 bertho 1.5 while(1)
309     {
310     char *vc1 = strchr(s1, '.');
311     char *vc2 = strchr(s2, '.');
312     if(vc1 && vc2)
313     *vc1 = *vc2 = '\0';
314     if(*s1 && *s2)
315     {
316     d1 = atoi(s1);
317     d2 = atoi(s2);
318     if(d1 != d2)
319     {
320     retval = d1 - d2;
321     break;
322     }
323     }
324     if(!vc1 || !vc2)
325     break;
326     s1 = vc1 + 1;
327     s2 = vc2 + 1;
328     }
329     xfree(v1);
330     xfree(v2);
331     return retval;
332     }
333    
334 bertho 1.7 /*
335     **************************************************************************
336     * Reorganise the rcsfile for the branches
337     *
338     * Basically, we have a list of deltas (i.e. administrative info on
339     * revisions) and a list of delta text (the actual logs and diffs).
340     * The deltas are linked through the 'next' and the 'branches' fields
341     * which describe the tree-structure of revisions.
342     * The reorganisation means that we put each delta and corresponding
343     * delta text in a revision structure and assign it to a specific
344     * branch. This is required because we want to be able to calculate
345     * the bounding boxes of each branch. The revisions expand vertically
346     * and the branches expand horizontally.
347     * The reorganisation is performed in these steps:
348 bertho 1.17 * 1 - sort deltas and delta text on revision number for quick lookup
349 bertho 1.7 * 2 - start at the denoted head revision:
350     * * create a branch structure and add this revision
351     * * for each 'branches' in the delta do:
352     * - walk all 'branches' of the delta and recursively goto 2
353     * with the denoted branch delta as new head
354     * - backlink the newly create sub-branch to the head revision
355     * so that we can draw them recursively
356     * * set head to the 'next' field and goto 2 until no next is
357     * available
358     * 3 - update the administration
359     **************************************************************************
360     */
361 bertho 1.19 static int sort_delta(const void *d1, const void *d2)
362 bertho 1.7 {
363     return compare_rev(0, (*(delta_t **)d1)->rev, (*(delta_t **)d2)->rev);
364     }
365    
366 bertho 1.19 static int search_delta(const void *r, const void *d)
367 bertho 1.1 {
368 bertho 1.7 return compare_rev(0, (rev_t *)r, (*(delta_t **)d)->rev);
369 bertho 1.1 }
370    
371 bertho 1.19 static delta_t *find_delta(delta_t **dl, int n, rev_t *r)
372 bertho 1.1 {
373 bertho 1.7 delta_t **d;
374 bertho 1.23 if(!n)
375     return NULL;
376 bertho 1.7 d = bsearch(r, dl, n, sizeof(*dl), search_delta);
377     if(!d)
378     return NULL;
379     return *d;
380 bertho 1.1 }
381    
382 bertho 1.19 static int sort_dtext(const void *d1, const void *d2)
383 bertho 1.1 {
384 bertho 1.7 return compare_rev(0, (*(dtext_t **)d1)->rev, (*(dtext_t **)d2)->rev);
385 bertho 1.1 }
386    
387 bertho 1.19 static int search_dtext(const void *r, const void *d)
388 bertho 1.1 {
389 bertho 1.7 return compare_rev(0, (rev_t *)r, (*(dtext_t **)d)->rev);
390 bertho 1.1 }
391    
392 bertho 1.19 static dtext_t *find_dtext(dtext_t **dl, int n, rev_t *r)
393 bertho 1.1 {
394 bertho 1.7 dtext_t **d;
395 bertho 1.23 if(!n)
396     return NULL;
397 bertho 1.7 d = bsearch(r, dl, n, sizeof(*dl), search_dtext);
398     if(!d)
399 bertho 1.1 return NULL;
400 bertho 1.7 return *d;
401     }
402    
403 bertho 1.19 static rev_t *dup_rev(const rev_t *r)
404 bertho 1.7 {
405     rev_t *t = xmalloc(sizeof(*t));
406     t->rev = xstrdup(r->rev);
407     t->branch = xstrdup(r->branch);
408     t->isbranch = r->isbranch;
409     return t;
410     }
411    
412 bertho 1.19 static branch_t *new_branch(delta_t *d, dtext_t *t)
413 bertho 1.7 {
414     branch_t *b = xmalloc(sizeof(*b));
415     revision_t *r = xmalloc(sizeof(*r));
416     r->delta = d;
417     r->dtext = t;
418     r->rev = d->rev;
419     r->branch = b;
420     b->branch = dup_rev(d->rev);
421     b->branch->isbranch = 1;
422     b->nrevs = 1;
423     b->revs = xmalloc(sizeof(b->revs[0]));
424     b->revs[0] = r;
425     return b;
426     }
427    
428 bertho 1.19 static revision_t *add_to_branch(branch_t *b, delta_t *d, dtext_t *t)
429 bertho 1.7 {
430     revision_t *r = xmalloc(sizeof(*r));
431     r->delta = d;
432     r->dtext = t;
433     r->rev = d->rev;
434     r->branch = b;
435     b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));
436     b->revs[b->nrevs] = r;
437     b->nrevs++;
438     return r;
439     }
440    
441     void build_branch(branch_t ***bl, int *nbl, delta_t **sdl, int nsdl, dtext_t **sdt, int nsdt, delta_t *head)
442     {
443     branch_t *b;
444     dtext_t *text;
445     revision_t *currev;
446    
447     assert(head != NULL);
448    
449     if(head->flag)
450     {
451     fprintf(stderr, "Circular reference on '%s' in branchpoint\n", head->rev->rev);
452     return;
453     }
454     head->flag++;
455     text = find_dtext(sdt, nsdt, head->rev);
456    
457     /* Create a new branch for this head */
458     b = new_branch(head, text);
459     *bl = xrealloc(*bl, (*nbl+1)*sizeof((*bl)[0]));
460     (*bl)[*nbl] = b;
461     (*nbl)++;
462     currev = b->revs[0];
463     while(1)
464     {
465     /* Process all sub-branches */
466     if(head->branches)
467     {
468     int i;
469     for(i = 0; i < head->branches->nrevs; i++)
470     {
471     delta_t *d = find_delta(sdl, nsdl, head->branches->revs[i]);
472     int btag = *nbl;
473     if(!d)
474     continue;
475     build_branch(bl, nbl, sdl, nsdl, sdt, nsdt, d);
476    
477     /* Set the new branch's origin */
478     (*bl)[btag]->branchpoint = currev;
479    
480     /* Add branch to this revision */
481     currev->branches = xrealloc(currev->branches, (currev->nbranches+1)*sizeof(currev->branches[0]));
482     currev->branches[currev->nbranches] = (*bl)[btag];
483     currev->nbranches++;
484     }
485     }
486    
487     /* Walk through the next list */
488     if(!head->next)
489     return;
490 bertho 1.9
491 bertho 1.7 head = find_delta(sdl, nsdl, head->next);
492     if(!head)
493     {
494     fprintf(stderr, "Next revision (%s) not found in deltalist\n", head->next->rev);
495     return;
496     }
497     if(head->flag)
498     {
499     fprintf(stderr, "Circular reference on '%s'\n", head->rev->rev);
500     return;
501     }
502     head->flag++;
503     text = find_dtext(sdt, nsdt, head->rev);
504     currev = add_to_branch(b, head, text);
505     }
506     }
507    
508     int reorganise_branches(rcsfile_t *rcs)
509     {
510     delta_t **sdelta;
511     int nsdelta;
512     dtext_t **sdtext;
513     int nsdtext;
514     delta_t *head;
515     branch_t **bl;
516     int nbl;
517     int i;
518    
519     assert(rcs->deltas != NULL);
520     assert(rcs->head != NULL);
521    
522     /* Make a new list for quick lookup */
523     nsdelta = rcs->deltas->ndeltas;
524     sdelta = xmalloc(nsdelta * sizeof(sdelta[0]));
525     memcpy(sdelta, rcs->deltas->deltas, nsdelta * sizeof(sdelta[0]));
526     qsort(sdelta, nsdelta, sizeof(sdelta[0]), sort_delta);
527    
528     /* Do the same for the delta text */
529 bertho 1.22 if(rcs->dtexts)
530     {
531     nsdtext = rcs->dtexts->ndtexts;
532     sdtext = xmalloc(nsdtext * sizeof(sdtext[0]));
533     memcpy(sdtext, rcs->dtexts->dtexts, nsdtext * sizeof(sdtext[0]));
534     qsort(sdtext, nsdtext, sizeof(sdtext[0]), sort_dtext);
535     }
536     else
537     {
538     nsdtext = 0;
539     sdtext = NULL;
540     }
541 bertho 1.7
542     /* Start from the head of the trunk */
543     head = find_delta(sdelta, nsdelta, rcs->head);
544     if(!head)
545     {
546     fprintf(stderr, "Head revision (%s) not found in deltalist\n", rcs->head->rev);
547     return 0;
548     }
549     bl = NULL;
550     nbl = 0;
551     build_branch(&bl, &nbl, sdelta, nsdelta, sdtext, nsdtext, head);
552    
553     /* Reverse the head branch */
554     for(i = 0; i < bl[0]->nrevs/2; i++)
555     {
556     revision_t *r;
557     r = bl[0]->revs[i];
558     bl[0]->revs[i] = bl[0]->revs[bl[0]->nrevs-i-1];
559     bl[0]->revs[bl[0]->nrevs-i-1] = r;
560     }
561    
562     /* Update the branch-number of the head because it was reversed */
563     xfree(bl[0]->branch->branch);
564     bl[0]->branch->branch = xstrdup(bl[0]->revs[0]->rev->branch);
565    
566     /* Keep the admin */
567     rcs->branches = bl;
568     rcs->nbranches = nbl;
569     rcs->sdelta = sdelta;
570     rcs->nsdelta = nsdelta;
571     rcs->sdtext = sdtext;
572     rcs->nsdtext = nsdtext;
573     rcs->active = bl[0];
574     return 1;
575     }
576    
577     /*
578     **************************************************************************
579     * Assign the symbolic tags to the revisions and branches
580     *
581     * The tags point to revision numbers somewhere in the tree structure
582     * of branches and revisions. First we make a sorted list of all
583     * revisions and then we assign each tag to the proper revision.
584     **************************************************************************
585     */
586 bertho 1.19 static int sort_revision(const void *r1, const void *r2)
587 bertho 1.7 {
588     return compare_rev(0, (*(revision_t **)r1)->delta->rev, (*(revision_t **)r2)->delta->rev);
589     }
590    
591 bertho 1.19 static int search_revision(const void *t, const void *r)
592 bertho 1.7 {
593     return compare_rev(0, (rev_t *)t, (*(revision_t **)r)->delta->rev);
594     }
595    
596 bertho 1.19 static int sort_branch(const void *b1, const void *b2)
597 bertho 1.7 {
598     return compare_rev(1, (*(branch_t **)b1)->branch, (*(branch_t **)b2)->branch);
599 bertho 1.1 }
600    
601 bertho 1.19 static int search_branch(const void *t, const void *b)
602 bertho 1.1 {
603 bertho 1.7 return compare_rev(1, (rev_t *)t, (*(branch_t **)b)->branch);
604 bertho 1.1 }
605    
606 bertho 1.19 static char *previous_rev(const char *c)
607 bertho 1.1 {
608 bertho 1.7 int dots = count_dots(c);
609     char *cptr;
610     char *r;
611     if(!dots)
612 bertho 1.9 {
613     fprintf(stderr, "FIXME: previous_rev(\"%s\"): Cannot determine parent branch revision\n", c);
614 bertho 1.7 return xstrdup("1.0"); /* FIXME: don't know what the parent is */
615 bertho 1.9 }
616 bertho 1.7 if(dots & 1)
617     {
618     /* Is is a revision we want the parent of */
619     r = xstrdup(c);
620     cptr = strrchr(r, '.');
621     assert(cptr != NULL);
622     if(dots == 1)
623     {
624 bertho 1.9 fprintf(stderr, "FIXME: previous_rev(\"%s\"): Going beyond top-level?\n", c);
625 bertho 1.7 /* FIXME: What is the parent of 1.1? */
626     cptr[1] = '\0';
627     strcat(r, "0");
628     return r;
629     }
630     /* Here we have a "x.x[.x.x]+" case */
631     *cptr = '\0';
632     cptr = strrchr(r, '.');
633     assert(cptr != NULL);
634     *cptr = '\0';
635     return r;
636     }
637     /* It is a branch we want the parent of */
638     r = xstrdup(c);
639     cptr = strrchr(r, '.');
640     assert(cptr != NULL);
641     *cptr = '\0';
642     return r;
643 bertho 1.1 }
644    
645 bertho 1.30 static char *build_regex(size_t n, regmatch_t *m, const char *ms)
646     {
647     char *cptr;
648     int i;
649    
650     if(!conf.merge_to || !conf.merge_to[0])
651     return NULL;
652    
653     zap_string();
654     for(cptr = conf.merge_to; *cptr; cptr++)
655     {
656     if(*cptr == '%')
657     {
658     if(cptr[1] >= '1' && cptr[1] <= '9')
659     {
660     int idx = cptr[1] - '0';
661     regmatch_t *p = &m[idx];
662     if(idx < n && !(p->rm_so == -1 || p->rm_so >= p->rm_eo))
663     {
664     for(i = p->rm_so; i < p->rm_eo; i++)
665     {
666     if(strchr("^$.*+\\[{()", ms[i]))
667     add_string_ch('\\');
668     add_string_ch(ms[i]);
669     }
670     }
671     cptr++;
672     }
673     else
674     add_string_ch('%');
675     }
676     else
677     add_string_ch(*cptr);
678     }
679     return dup_string();
680     }
681    
682 bertho 1.7 int assign_tags(rcsfile_t *rcs)
683 bertho 1.1 {
684     int i;
685 bertho 1.7 int nr;
686 bertho 1.30 int err;
687     regex_t *refrom = NULL;
688     regex_t *reto = NULL;
689     regmatch_t *matchfrom = NULL;
690    
691     if(conf.merge_from && conf.merge_from[0] && conf.merge_to && conf.merge_to[0])
692     {
693     refrom = xmalloc(sizeof(*refrom));
694     reto = xmalloc(sizeof(*reto));
695    
696     /* Compile the 'from' regex match for merge identification */
697     err = regcomp(refrom, conf.merge_from, REG_EXTENDED | (conf.merge_nocase ? REG_ICASE : 0));
698     if(err)
699     {
700     if(!quiet)
701     {
702     char *msg;
703     i = regerror(err, refrom, NULL, 0);
704     msg = xmalloc(i+1);
705     regerror(err, refrom, msg, i+1);
706     fprintf(stderr, "%s\n", msg);
707     xfree(msg);
708     }
709     xfree(refrom);
710     xfree(reto);
711     refrom = NULL;
712     reto = NULL;
713     }
714     else
715     matchfrom = xmalloc((refrom->re_nsub+1) * sizeof(*matchfrom));
716     }
717 bertho 1.7
718     for(i = nr = 0; i < rcs->nbranches; i++)
719     nr += rcs->branches[i]->nrevs;
720    
721     rcs->srev = xmalloc(nr * sizeof(rcs->srev[0]));
722     rcs->nsrev = nr;
723     for(i = nr = 0; i < rcs->nbranches; i++)
724     {
725     memcpy(&rcs->srev[nr], rcs->branches[i]->revs, rcs->branches[i]->nrevs * sizeof(rcs->branches[i]->revs[0]));
726     nr += rcs->branches[i]->nrevs;
727     }
728    
729     qsort(rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), sort_revision);
730     qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
731    
732     if(!rcs->branch)
733     {
734     /* The main trunk is the active trunk */
735     rcs->tags->tags = xrealloc(rcs->tags->tags, (rcs->tags->ntags+1)*sizeof(rcs->tags->tags[0]));
736     rcs->tags->tags[rcs->tags->ntags] = xmalloc(sizeof(tag_t));
737     rcs->tags->tags[rcs->tags->ntags]->tag = xstrdup("MAIN");
738     rcs->tags->tags[rcs->tags->ntags]->rev = xmalloc(sizeof(rev_t));
739     rcs->tags->tags[rcs->tags->ntags]->rev->rev = xstrdup(rcs->active->branch->rev);
740     rcs->tags->tags[rcs->tags->ntags]->rev->branch = xstrdup(rcs->active->branch->branch);
741     rcs->tags->tags[rcs->tags->ntags]->rev->isbranch = 1;
742     rcs->tags->ntags++;
743     }
744    
745     /* We should have at least two tags (HEAD and MAIN) */
746     assert(rcs->tags != 0);
747    
748     for(i = 0; i < rcs->tags->ntags; i++)
749     {
750     tag_t *t = rcs->tags->tags[i];
751     if(t->rev->isbranch)
752     {
753     branch_t **b;
754     add_btag:
755     b = bsearch(t->rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
756     if(!b)
757     {
758     rev_t rev;
759     revision_t **r;
760     /* This happens for the magic branch numbers if there are
761 bertho 1.12 * no commits within the new branch yet. So, we add the
762     * branch and try to continue.
763 bertho 1.7 */
764     rev.rev = previous_rev(t->rev->branch);
765     rev.branch = NULL;
766     rev.isbranch = 0;
767     r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
768     xfree(rev.rev);
769     if(!r)
770     {
771 bertho 1.12 if(!quiet)
772     fprintf(stderr, "No branch found for tag '%s:%s'\n", t->tag, t->rev->branch);
773 bertho 1.7 }
774     else
775     {
776     rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1)*sizeof(rcs->branches[0]));
777     rcs->branches[rcs->nbranches] = xmalloc(sizeof(branch_t));
778     rcs->branches[rcs->nbranches]->branch = dup_rev(t->rev);
779     rcs->branches[rcs->nbranches]->branchpoint = *r;
780     (*r)->branches = xrealloc((*r)->branches, ((*r)->nbranches+1)*sizeof((*r)->branches[0]));
781     (*r)->branches[(*r)->nbranches] = rcs->branches[rcs->nbranches];
782     (*r)->nbranches++;
783     rcs->nbranches++;
784     /* Resort the branches */
785     qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
786     goto add_btag;
787     }
788     }
789     else
790     {
791     branch_t *bb = *b;
792     bb->tags = xrealloc(bb->tags, (bb->ntags+1)*sizeof(bb->tags[0]));
793     bb->tags[bb->ntags] = t;
794     bb->ntags++;
795     }
796     }
797     else
798     {
799     revision_t **r = bsearch(t->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
800     if(!r)
801 bertho 1.12 {
802     if(!quiet)
803     fprintf(stderr, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);
804     }
805 bertho 1.7 else
806     {
807     revision_t *rr = *r;
808 bertho 1.30 t->logrev = rr;
809 bertho 1.28 if(!conf.rev_maxtags || rr->ntags <= conf.rev_maxtags)
810     {
811     rr->tags = xrealloc(rr->tags, (rr->ntags+1)*sizeof(rr->tags[0]));
812     if(conf.rev_maxtags && rr->ntags == conf.rev_maxtags)
813     {
814     rr->tags[rr->ntags] = xmalloc(sizeof(tag_t));
815     rr->tags[rr->ntags]->tag = xstrdup("...");
816     rr->tags[rr->ntags]->rev = t->rev;
817     }
818     else
819     rr->tags[rr->ntags] = t;
820     rr->ntags++;
821     }
822 bertho 1.30
823     /* Try to find merge tag matches */
824     if(refrom && !regexec(refrom, t->tag, refrom->re_nsub+1, matchfrom, 0))
825     {
826     int n;
827     char *to;
828    
829     to = build_regex(refrom->re_nsub+1, matchfrom, t->tag);
830     if(to)
831     {
832     err = regcomp(reto, to, REG_EXTENDED | (conf.merge_nocase ? REG_ICASE : 0));
833     if(err && !quiet)
834     {
835     char *msg;
836     i = regerror(err, reto, NULL, 0);
837     msg = xmalloc(i+1);
838     regerror(err, reto, msg, i+1);
839     fprintf(stderr, "%s\n", msg);
840     }
841     else if(!err)
842     {
843     for(n = 0; n < rcs->tags->ntags; n++)
844     {
845     /* From and To never should match the same tag */
846     if(n == i)
847     continue;
848    
849     if(!regexec(reto, rcs->tags->tags[n]->tag, 0, NULL, REG_NOSUB))
850     {
851     /* Tag matches */
852     rcs->merges = xrealloc(rcs->merges,
853     sizeof(rcs->merges[0]) * (rcs->nmerges+1));
854     rcs->merges[rcs->nmerges].to = rcs->tags->tags[n];
855     rcs->merges[rcs->nmerges].from = t;
856     rcs->nmerges++;
857     /* We cannot (should not) match multiple times */
858     n = rcs->tags->ntags;
859     }
860     }
861     regfree(reto);
862     }
863     xfree(to);
864     }
865     }
866 bertho 1.7 }
867     }
868     }
869    
870     /* We need to reset the first in the list of branches to the
871     * active branch to ensure the drawing of all
872     */
873     if(rcs->active != rcs->branches[0])
874 bertho 1.1 {
875 bertho 1.7 branch_t **b = bsearch(rcs->active->branch, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
876     branch_t *t;
877     assert(b != NULL);
878     t = *b;
879     *b = rcs->branches[0];
880     rcs->branches[0] = t;
881 bertho 1.1 }
882 bertho 1.30 if(matchfrom) xfree(matchfrom);
883     if(refrom) { regfree(refrom); xfree(refrom); }
884     if(reto) xfree(reto);
885 bertho 1.7 return 1;
886 bertho 1.1 }
887    
888     /*
889     **************************************************************************
890 bertho 1.8 * String expansion
891     **************************************************************************
892     */
893     static char *_string;
894     static int _nstring;
895     static int _nastring;
896    
897 bertho 1.30 static void zap_string(void)
898     {
899     _nstring = 0;
900     if(_string)
901     _string[0] = '\0';
902     }
903    
904     static char *dup_string(void)
905     {
906     if(_string)
907     return xstrdup(_string);
908     else
909     return "";
910     }
911    
912 bertho 1.19 static void add_string_str(const char *s)
913 bertho 1.8 {
914     int l = strlen(s) + 1;
915     if(_nstring + l > _nastring)
916     {
917 bertho 1.24 _nastring += MAX(128, l);
918 bertho 1.8 _string = xrealloc(_string, _nastring * sizeof(_string[0]));
919     }
920     memcpy(_string+_nstring, s, l);
921     _nstring += l-1;
922     }
923    
924 bertho 1.19 static void add_string_ch(int ch)
925 bertho 1.8 {
926     char buf[2];
927     buf[0] = ch;
928     buf[1] = '\0';
929     add_string_str(buf);
930     }
931    
932 bertho 1.19 static void add_string_date(const char *d)
933 bertho 1.9 {
934     struct tm tm, *tmp;
935     int n;
936     time_t t;
937     char *buf;
938     int nbuf;
939    
940     memset(&tm, 0, sizeof(tm));
941     n = sscanf(d, "%d.%d.%d.%d.%d.%d",
942     &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
943     &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
944     tm.tm_mon--;
945     if(tm.tm_year > 1900)
946     tm.tm_year -= 1900;
947     t = mktime(&tm);
948     if(n != 6 || t == (time_t)(-1))
949     {
950     add_string_str("<invalid date>");
951     return;
952     }
953    
954     tmp = localtime(&t);
955     nbuf = strlen(conf.date_format) * 16; /* Should be enough to hold all types of expansions */
956     buf = xmalloc(nbuf);
957     strftime(buf, nbuf, conf.date_format, tmp);
958     add_string_str(buf);
959     xfree(buf);
960     }
961    
962 bertho 1.22 static void add_string_str_html(const char *s, int maxlen)
963     {
964     int l = 0;
965     char *str = xmalloc(6 * strlen(s) + 1); /* Should hold all char entity-expand */
966     char *cptr = str;
967     for(; *s; s++)
968     {
969     if(maxlen && l > abs(maxlen))
970     {
971     cptr += sprintf(cptr, "...");
972     break;
973     }
974     if(*s < 0x20)
975     {
976     if(*s == '\n')
977     {
978     if(maxlen < 0)
979     *cptr++ = ' ';
980     else
981     cptr += sprintf(cptr, "<br>");
982     }
983     }
984 bertho 1.30 else if(*s >= 0x7f || *s == '"')
985 bertho 1.22 cptr += sprintf(cptr, "&#%d;", (int)(unsigned char)*s);
986     else if(*s == '<')
987     cptr += sprintf(cptr, "&lt;");
988     else if(*s == '>')
989     cptr += sprintf(cptr, "&gt;");
990     else
991     *cptr++ = *s;
992     l++;
993     }
994     *cptr = '\0';
995     add_string_str(str);
996     xfree(str);
997     }
998    
999     static void add_string_str_len(const char *s, int maxlen)
1000     {
1001     int l = strlen(s);
1002     char *str = xmalloc(l + 1 + 3);
1003     strcpy(str, s);
1004     if(maxlen < l)
1005     sprintf(&str[maxlen], "...");
1006     add_string_str(str);
1007     xfree(str);
1008     }
1009    
1010 bertho 1.9 char *expand_string(const char *s, rcsfile_t *rcs, revision_t *r, rev_t *rev, rev_t *prev, tag_t *tag)
1011 bertho 1.8 {
1012     char nb[32];
1013     char nr[32];
1014     char *base;
1015 bertho 1.30 char *exp;
1016 bertho 1.22 int l;
1017     char ch;
1018 bertho 1.8
1019     if(!s)
1020     return xstrdup("");
1021    
1022 bertho 1.30 zap_string();
1023 bertho 1.8
1024     sprintf(nb, "%d", rcs->nbranches);
1025     sprintf(nr, "%d", rcs->nsrev);
1026     for(; *s; s++)
1027     {
1028     if(*s == '%')
1029     {
1030     switch(*++s)
1031     {
1032 bertho 1.12 case 'c':
1033     case 'C':
1034     add_string_str(conf.cvsroot);
1035     if(*s == 'C' && conf.cvsroot[0] && conf.cvsroot[strlen(conf.cvsroot)-1] == '/')
1036     {
1037     /* Strip the trailing '/' */
1038     _nstring--;
1039     _string[_nstring] = '\0';
1040     }
1041     break;
1042 bertho 1.8 case 'f':
1043     case 'F':
1044     base = strrchr(rcs->file, '/');
1045     if(!base)
1046     add_string_str(rcs->file);
1047     else
1048     add_string_str(base+1);
1049     if(*s == 'F' && _string[_nstring-1] == 'v' && _string[_nstring-2] == ',')
1050     {
1051     _nstring -= 2;
1052     _string[_nstring] = '\0';
1053     }
1054     break;
1055     case 'p':
1056     base = strrchr(rcs->file, '/');
1057     if(base)
1058     {
1059     char *c = xstrdup(rcs->file);
1060     base = strrchr(c, '/');
1061     assert(base != NULL);
1062     base[1] = '\0';
1063     add_string_str(c);
1064     xfree(c);
1065     }
1066 bertho 1.9 /*
1067     * We should not add anything here because we can encounter
1068     * a completely empty path, in which case we do not want
1069 bertho 1.17 * to add any slash. This prevents an inadvertent root redirect.
1070 bertho 1.9 *
1071     * else
1072     * add_string_str("/");
1073     */
1074 bertho 1.8 break;
1075 bertho 1.12 case 'm':
1076     case 'M':
1077     add_string_str(conf.cvsmodule);
1078     if(*s == 'M' && conf.cvsmodule[0] && conf.cvsmodule[strlen(conf.cvsmodule)-1] == '/')
1079     {
1080     /* Strip the trailing '/' */
1081     _nstring--;
1082     _string[_nstring] = '\0';
1083     }
1084     break;
1085 bertho 1.8 case 'r': add_string_str(nr); break;
1086     case 'b': add_string_str(nb); break;
1087     case '%': add_string_ch('%'); break;
1088     case '0': if(conf.expand[0]) add_string_str(conf.expand[0]); break;
1089     case '1': if(conf.expand[1]) add_string_str(conf.expand[1]); break;
1090     case '2': if(conf.expand[2]) add_string_str(conf.expand[2]); break;
1091     case '3': if(conf.expand[3]) add_string_str(conf.expand[3]); break;
1092     case '4': if(conf.expand[4]) add_string_str(conf.expand[4]); break;
1093     case '5': if(conf.expand[5]) add_string_str(conf.expand[5]); break;
1094     case '6': if(conf.expand[6]) add_string_str(conf.expand[6]); break;
1095     case '7': if(conf.expand[7]) add_string_str(conf.expand[7]); break;
1096     case '8': if(conf.expand[8]) add_string_str(conf.expand[8]); break;
1097     case '9': if(conf.expand[9]) add_string_str(conf.expand[9]); break;
1098     case 'R': if(rev && rev->rev) add_string_str(rev->rev); break;
1099 bertho 1.9 case 'P': if(prev && prev->rev) add_string_str(prev->rev); break;
1100 bertho 1.8 case 'B': if(rev && rev->branch) add_string_str(rev->branch); break;
1101     case 't': if(tag && tag->tag) add_string_str(tag->tag); break;
1102 bertho 1.9 case 'd': if(r && r->delta && r->delta->date) add_string_date(r->delta->date); break;
1103     case 's': if(r && r->delta && r->delta->state) add_string_str(r->delta->state); break;
1104     case 'a': if(r && r->delta && r->delta->author) add_string_str(r->delta->author); break;
1105 bertho 1.22 case 'L':
1106     case 'l':
1107     ch = *s;
1108     l = 0;
1109     if(s[1] == '[')
1110     {
1111     char *cptr = strchr(s, ']');
1112     char *eptr;
1113     if(cptr)
1114     {
1115     l = strtol(&s[2], &eptr, 10);
1116     if(eptr != cptr)
1117     l = 0;
1118     else
1119     s = cptr;
1120     }
1121     }
1122     if(!conf.parse_logs)
1123     add_string_str("N/A");
1124     else if(r && r->dtext && r->dtext->log)
1125     {
1126     if(ch == 'l')
1127     add_string_str_html(r->dtext->log, l);
1128     else
1129     add_string_str_len(r->dtext->log, abs(l));
1130     }
1131     break;
1132 bertho 1.30 case '(':
1133     base = dup_string();
1134     exp = expand_string(s+1, rcs, r, rev, prev, tag);
1135     zap_string();
1136     add_string_str(base);
1137     add_string_str_html(exp, 0);
1138     xfree(base);
1139     xfree(exp);
1140     /* Find the %) in this recursion level */
1141     for(; *s; s++)
1142     {
1143     if(*s == '%' && s[1] == ')')
1144     break;
1145     }
1146     if(!*s)
1147     {
1148     s--; /* To end outer loop */
1149     if(!quiet)
1150     fprintf(stderr, "string expand: Missing %%) in expansion\n");
1151     }
1152     break;
1153     case ')':
1154     return dup_string();
1155 bertho 1.8 default:
1156     add_string_ch('%');
1157     add_string_ch(*s);
1158     break;
1159     }
1160     }
1161     else
1162     add_string_ch(*s);
1163     }
1164 bertho 1.30 return dup_string();
1165 bertho 1.8 }
1166    
1167     /*
1168     **************************************************************************
1169 bertho 1.1 * Drawing routines
1170     **************************************************************************
1171     */
1172 bertho 1.19 static int get_swidth(const char *s, font_t *f)
1173 bertho 1.1 {
1174 bertho 1.9 int n;
1175     int m;
1176     if(!s || !*s)
1177 bertho 1.1 return 0;
1178 bertho 1.19
1179     #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1180     if(conf.use_ttf && f->ttfont)
1181     {
1182     int bb[8];
1183     char *e;
1184     #ifdef HAVE_GDIMAGESTRINGFT
1185     e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1186     #else
1187     e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1188     #endif
1189     if(!e)
1190     return bb[2] - bb[6];
1191     }
1192     #endif
1193 bertho 1.9 for(n = m = 0; *s; n++, s++)
1194     {
1195     if(*s == '\n')
1196     {
1197     if(n > m)
1198     m = n;
1199     n = 0;
1200     }
1201     }
1202     if(n > m)
1203     m = n;
1204 bertho 1.22 return f->gdfont ? m * f->gdfont->w : m;
1205 bertho 1.1 }
1206    
1207 bertho 1.19 static int get_sheight(const char *s, font_t *f)
1208 bertho 1.1 {
1209     int nl;
1210 bertho 1.9 if(!s || !*s)
1211 bertho 1.1 return 0;
1212 bertho 1.19
1213     #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1214     if(conf.use_ttf && f->ttfont)
1215     {
1216     int bb[8];
1217     char *e;
1218     #ifdef HAVE_GDIMAGESTRINGFT
1219     e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1220     #else
1221     e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1222     #endif
1223     if(!e)
1224     return bb[3] - bb[7] + 4;
1225     }
1226     #endif
1227 bertho 1.1 for(nl = 1; *s; s++)
1228     {
1229     if(*s == '\n' && s[1])
1230     nl++;
1231     }
1232 bertho 1.19 return nl * f->gdfont->h;
1233 bertho 1.1 }
1234    
1235 bertho 1.19 static void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color, color_t *bgcolor)
1236 bertho 1.1 {
1237     int r2 = 2*r;
1238     gdImageLine(im, x1+r, y1, x2-r, y1, color->id);
1239     gdImageLine(im, x1+r, y2, x2-r, y2, color->id);
1240     gdImageLine(im, x1, y1+r, x1, y2-r, color->id);
1241     gdImageLine(im, x2, y1+r, x2, y2-r, color->id);
1242 bertho 1.9 if(conf.box_shadow)
1243     {
1244     gdImageLine(im, x1+r+1, y2+1, x2-r, y2+1, black_color.id);
1245     gdImageLine(im, x2+1, y1+r+1, x2+1, y2-r, black_color.id);
1246     }
1247 bertho 1.1 if(r)
1248     {
1249 bertho 1.18 /* FIXME: Pixelization is not perfect */
1250 bertho 1.1 gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);
1251     gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);
1252     gdImageArc(im, x1+r, y2-r, r2, r2, 90, 180, color->id);
1253 bertho 1.9 if(conf.box_shadow)
1254     {
1255     gdImageArc(im, x2-r+1, y2-r+1, r2, r2, 0, 90, black_color.id);
1256 bertho 1.18 gdImageArc(im, x2-r+1, y2-r, r2, r2, 0, 90, black_color.id);
1257     gdImageArc(im, x2-r, y2-r+1, r2, r2, 0, 90, black_color.id);
1258 bertho 1.9 }
1259 bertho 1.18 gdImageArc(im, x2-r, y2-r, r2, r2, 0, 90, color->id);
1260 bertho 1.1 }
1261 bertho 1.19 #ifndef NOGDFILL
1262 bertho 1.9 gdImageFillToBorder(im, (x1+x2)/2, (y1+y2)/2, color->id, bgcolor->id);
1263 bertho 1.19 #endif
1264 bertho 1.1 }
1265    
1266 bertho 1.19 static void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
1267 bertho 1.1 {
1268 bertho 1.19 int h = get_sheight(s, f);
1269 bertho 1.1 int xx, yy;
1270     switch(align & ALIGN_HX)
1271     {
1272     default:
1273     case ALIGN_HL: xx = 0; break;
1274     case ALIGN_HC: xx = -get_swidth(s, f)/2; break;
1275     case ALIGN_HR: xx = -get_swidth(s, f); break;
1276     }
1277     switch(align & ALIGN_VX)
1278     {
1279     default:
1280     case ALIGN_VT: yy = 0; break;
1281 bertho 1.19 case ALIGN_VC: yy = h/2; break;
1282     case ALIGN_VB: yy = h; break;
1283 bertho 1.1 }
1284 bertho 1.19 #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1285     if(conf.use_ttf && f->ttfont)
1286     {
1287     int bb[8];
1288     char *e;
1289     int cid = conf.anti_alias ? c->id : -c->id;
1290     #ifdef HAVE_GDIMAGESTRINGFT
1291     e = gdImageStringFT(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1292     #else
1293     e = gdImageStringTTF(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1294     #endif
1295     if(!e)
1296     return;
1297     }
1298     #endif
1299     yy = -yy;
1300     gdImageString(im, f->gdfont, x+xx+1, y+yy, s, c->id);
1301 bertho 1.1 }
1302    
1303 bertho 1.19 static void draw_stringnl(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
1304 bertho 1.9 {
1305     char *t;
1306     char *d;
1307     d = s = xstrdup(s);
1308     do
1309     {
1310     t = strchr(s, '\n');
1311     if(t)
1312     *t = '\0';
1313     draw_string(im, s, f, x, y, align, c);
1314     y += get_sheight(s, f);
1315     s = t+1;
1316     } while(t);
1317     xfree(d);
1318     }
1319    
1320 bertho 1.19 static void draw_rev(gdImagePtr im, revision_t *r)
1321 bertho 1.1 {
1322 bertho 1.25 int lx;
1323     int rx;
1324     int x2;
1325 bertho 1.1 int i;
1326 bertho 1.25 int ty;
1327    
1328     if(conf.left_right)
1329     {
1330     lx = r->cx;
1331     rx = r->cx + r->w;
1332     ty = r->y - r->h/2;
1333     x2 = r->cx + r->w/2;
1334     }
1335     else
1336     {
1337     lx = r->cx - r->w/2;
1338     rx = lx + r->w;
1339     ty = r->y;
1340     x2 = r->cx;
1341     }
1342 bertho 1.9 draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color, &conf.rev_bgcolor);
1343 bertho 1.1 ty += conf.rev_tspace;
1344 bertho 1.25 draw_string(im, r->rev->rev, &conf.rev_font, x2, ty, ALIGN_HC, &conf.rev_color);
1345 bertho 1.1 ty += get_sheight(r->rev->rev, &conf.rev_font);
1346 bertho 1.25 draw_stringnl(im, r->revtext, &conf.rev_text_font, x2, ty, ALIGN_HC, &conf.rev_text_color);
1347 bertho 1.9 ty += get_sheight(r->revtext, &conf.rev_text_font);
1348 bertho 1.1 for(i = 0; i < r->ntags; i++)
1349     {
1350 bertho 1.25 draw_string(im, r->tags[i]->tag, &conf.tag_font, x2, ty, ALIGN_HC, &conf.tag_color);
1351 bertho 1.19 ty += get_sheight(r->tags[i]->tag, &conf.tag_font) + conf.rev_separator;
1352 bertho 1.1 }
1353     }
1354    
1355 bertho 1.26 static void draw_branch_box(gdImagePtr im, branch_t *b, int xp, int yp)
1356 bertho 1.1 {
1357 bertho 1.25 int lx;
1358     int rx;
1359 bertho 1.16 int i;
1360 bertho 1.1 int yy;
1361 bertho 1.25 int x2;
1362 bertho 1.16
1363 bertho 1.25 if(conf.left_right)
1364     {
1365     lx = b->cx;
1366     rx = lx + b->w;
1367     x2 = b->cx + b->w/2;
1368     }
1369     else
1370     {
1371     lx = b->cx - b->w/2;
1372     rx = lx + b->w;
1373     x2 = b->cx;
1374     }
1375 bertho 1.26 draw_rbox(im, lx+xp, yp, rx+xp, yp+b->h, 5, &conf.branch_color, &conf.branch_bgcolor);
1376 bertho 1.1 yy = conf.branch_tspace;
1377 bertho 1.26 draw_string(im, b->branch->branch, &conf.branch_font, x2+xp, yp+yy, ALIGN_HC, &conf.branch_color);
1378 bertho 1.7 yy += get_sheight(b->branch->branch, &conf.branch_font);
1379     for(i = 0; i < b->ntags; i++)
1380 bertho 1.1 {
1381 bertho 1.26 draw_string(im, b->tags[i]->tag, &conf.branch_tag_font, x2+xp, yp+yy, ALIGN_HC, &conf.branch_tag_color);
1382 bertho 1.7 yy += get_sheight(b->tags[i]->tag, &conf.branch_font);
1383 bertho 1.1 }
1384 bertho 1.16 }
1385    
1386 bertho 1.19 static void draw_branch(gdImagePtr im, branch_t *b)
1387 bertho 1.16 {
1388 bertho 1.25 int yy, xx;
1389 bertho 1.16 int i;
1390 bertho 1.17 int line[4];
1391 bertho 1.20 int l;
1392     int sign;
1393 bertho 1.17
1394     line[0] = conf.rev_color.id;
1395     line[1] = gdTransparent;
1396     line[1] = gdTransparent;
1397     line[3] = conf.rev_color.id;
1398    
1399 bertho 1.26 draw_branch_box(im, b, 0, conf.left_right ? b->y - b->h/2 : b->y);
1400 bertho 1.1
1401 bertho 1.25 if(conf.left_right)
1402 bertho 1.16 {
1403 bertho 1.25 if(conf.upside_down)
1404 bertho 1.16 {
1405 bertho 1.25 xx = b->cx;
1406     for(i = 0; i < b->nrevs; i++)
1407 bertho 1.20 {
1408 bertho 1.25 revision_t *r = b->revs[i];
1409     gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1410     gdImageLine(im, xx, r->y, r->cx+r->w, r->y, gdStyled);
1411     for(sign = l = 1; l < conf.thick_lines; l++)
1412     {
1413     int pp = (l+1)/2*sign;
1414     gdImageLine(im, xx, r->y+pp, r->cx+r->w, r->y+pp, gdStyled);
1415     sign *= -1;
1416     }
1417     draw_rev(im, r);
1418     xx = r->cx;
1419     }
1420     if(conf.branch_dupbox)
1421     {
1422     i = b->cx - b->tw + b->w;
1423 bertho 1.26 gdImageLine(im, xx, b->y, i+b->w, b->y, conf.rev_color.id);
1424 bertho 1.25 for(sign = l = 1; l < conf.thick_lines; l++)
1425     {
1426     int pp = (l+1)/2*sign;
1427 bertho 1.26 gdImageLine(im, xx, b->y+pp, i+b->w, b->y+pp, conf.rev_color.id);
1428 bertho 1.25 sign *= -1;
1429     }
1430 bertho 1.26 draw_branch_box(im, b, i - b->cx, b->y - b->h/2);
1431 bertho 1.20 }
1432 bertho 1.16 }
1433 bertho 1.25 else
1434 bertho 1.16 {
1435 bertho 1.25 xx = b->cx + b->w;
1436     for(i = 0; i < b->nrevs; i++)
1437     {
1438     revision_t *r = b->revs[i];
1439     gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1440     gdImageLine(im, xx, r->y, r->cx, r->y, gdStyled);
1441     for(sign = l = 1; l < conf.thick_lines; l++)
1442     {
1443     int pp = (l+1)/2*sign;
1444     gdImageLine(im, xx, r->y+pp, r->cx, r->y+pp, gdStyled);
1445     sign *= -1;
1446     }
1447     draw_rev(im, r);
1448     xx = r->cx + r->w;
1449     }
1450     if(conf.branch_dupbox)
1451 bertho 1.20 {
1452 bertho 1.25 i = b->cx + b->tw - b->w;
1453     gdImageLine(im, xx, b->y, i, b->y, conf.rev_color.id);
1454     for(sign = l = 1; l < conf.thick_lines; l++)
1455     {
1456     int pp = (l+1)/2*sign;
1457     gdImageLine(im, xx, b->y+pp, i, b->y+pp, conf.rev_color.id);
1458     sign *= -1;
1459     }
1460 bertho 1.26 draw_branch_box(im, b, i - b->cx, b->y - b->h/2);
1461 bertho 1.20 }
1462 bertho 1.16 }
1463     }
1464     else
1465 bertho 1.1 {
1466 bertho 1.25 if(conf.upside_down)
1467 bertho 1.16 {
1468 bertho 1.25 yy = b->y;
1469     for(i = 0; i < b->nrevs; i++)
1470 bertho 1.20 {
1471 bertho 1.25 revision_t *r = b->revs[i];
1472     gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1473     gdImageLine(im, r->cx, yy, r->cx, r->y+r->h, gdStyled);
1474     for(sign = l = 1; l < conf.thick_lines; l++)
1475     {
1476     int pp = (l+1)/2*sign;
1477     gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y+r->h, gdStyled);
1478     sign *= -1;
1479     }
1480     draw_rev(im, r);
1481     yy = r->y;
1482     }
1483     if(conf.branch_dupbox)
1484     {
1485     i = b->y - b->th + b->h;
1486     gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);
1487     for(sign = l = 1; l < conf.thick_lines; l++)
1488     {
1489     int pp = (l+1)/2*sign;
1490     gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, conf.rev_color.id);
1491     sign *= -1;
1492     }
1493 bertho 1.26 draw_branch_box(im, b, 0, i);
1494 bertho 1.20 }
1495 bertho 1.16 }
1496 bertho 1.25 else
1497 bertho 1.16 {
1498 bertho 1.25 yy = b->y + b->h;
1499     for(i = 0; i < b->nrevs; i++)
1500     {
1501     revision_t *r = b->revs[i];
1502     gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1503     gdImageLine(im, r->cx, yy, r->cx, r->y, gdStyled);
1504     for(sign = l = 1; l < conf.thick_lines; l++)
1505     {
1506     int pp = (l+1)/2*sign;
1507     gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y, gdStyled);
1508     sign *= -1;
1509     }
1510     draw_rev(im, r);
1511     yy = r->y + r->h;
1512     }
1513     if(conf.branch_dupbox)
1514 bertho 1.20 {
1515 bertho 1.25 i = b->y + b->th - b->h;
1516     gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);
1517     for(sign = l = 1; l < conf.thick_lines; l++)
1518     {
1519     int pp = (l+1)/2*sign;
1520     gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, conf.rev_color.id);
1521     sign *= -1;
1522     }
1523 bertho 1.26 draw_branch_box(im, b, 0, i);
1524 bertho 1.20 }
1525 bertho 1.16 }
1526 bertho 1.1 }
1527     }
1528    
1529 bertho 1.19 static void draw_connector(gdImagePtr im, branch_t *b)
1530 bertho 1.1 {
1531 bertho 1.20 int l;
1532     int sign;
1533 bertho 1.1 revision_t *r = b->branchpoint;
1534 bertho 1.7 int x1 = r->cx + r->w/2 + 2;
1535 bertho 1.1 int y1 = r->y + r->h/2;
1536 bertho 1.7 int x2 = b->cx;
1537 bertho 1.1 int y2 = b->y;
1538 bertho 1.25
1539     if(conf.left_right)
1540     {
1541     x2 = r->cx + r->w/2;
1542 bertho 1.26 y2 = r->y + r->h/2 + 3;
1543 bertho 1.25 x1 = b->cx;
1544     y1 = b->y;
1545     if(conf.upside_down)
1546     x1 += b->w;
1547     }
1548     else
1549     {
1550     x1 = r->cx + r->w/2 + 2;
1551     y1 = r->y + r->h/2;
1552     x2 = b->cx;
1553     y2 = b->y;
1554     if(conf.upside_down)
1555     y2 += b->h;
1556     }
1557 bertho 1.1 gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);
1558     gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);
1559 bertho 1.20 for(sign = l = 1; l < conf.thick_lines; l++)
1560     {
1561     int pp = (l+1)/2*sign;
1562 bertho 1.26 gdImageLine(im, x1, y1+pp, x2, y1+pp, conf.branch_color.id);
1563     gdImageLine(im, x2+pp, y1, x2+pp, y2, conf.branch_color.id);
1564 bertho 1.20 sign *= -1;
1565     }
1566 bertho 1.1 }
1567    
1568 bertho 1.30 static void draw_merges(gdImagePtr im, rcsfile_t *rcs)
1569     {
1570     int i;
1571     for(i = 0; i < rcs->nmerges; i++)
1572     {
1573     revision_t *fr = rcs->merges[i].from->logrev;
1574     revision_t *tr = rcs->merges[i].to->logrev;
1575     int x1, x2, y1, y2;
1576     if(!fr || !tr)
1577     continue; /* This can happen with detached tags */
1578     if(conf.left_right)
1579     {
1580     if(fr->y < tr->y)
1581     {
1582     y1 = fr->y + fr->h/2;
1583     y2 = tr->y - tr->h/2;
1584     }
1585     else
1586     {
1587     y1 = fr->y - fr->h/2;
1588     y2 = tr->y + tr->h/2;
1589     }
1590     x1 = fr->cx + fr->w/2;
1591     x2 = tr->cx + tr->w/2;
1592     }
1593     else
1594     {
1595     if(fr->cx < tr->cx)
1596     {
1597     x1 = fr->cx + fr->w/2;
1598     x2 = tr->cx - tr->w/2;
1599     }
1600     else
1601     {
1602     x1 = fr->cx - fr->w/2;
1603     x2 = tr->cx + tr->w/2;
1604     }
1605     y1 = fr->y + fr->h/2;
1606     y2 = tr->y + tr->h/2;
1607     }
1608     gdImageArc(im, x2, y2, 8, 8, 0, 360, conf.merge_color.id);
1609     gdImageFillToBorder(im, x2, y2, conf.merge_color.id, conf.merge_color.id);
1610     if(conf.left_right)
1611     {
1612     if(y1 > y2)
1613     {
1614     gdImageLine(im, x1, y1, x1, y1-3, conf.merge_color.id);
1615     gdImageLine(im, x2, y2+1, x2, y2+3+1, conf.merge_color.id);
1616     gdImageLine(im, x1, y1-3, x2, y2+3+1, conf.merge_color.id);
1617     }
1618     else
1619     {
1620     gdImageLine(im, x1, y1+1, x1, y1+3+1, conf.merge_color.id);
1621     gdImageLine(im, x2, y2, x2, y2-3, conf.merge_color.id);
1622     gdImageLine(im, x1, y1+3+1, x2, y2-3, conf.merge_color.id);
1623     }
1624     }
1625     else
1626     {
1627     if(x1 > x2)
1628     {
1629     gdImageLine(im, x1, y1, x1-3, y1, conf.merge_color.id);
1630     gdImageLine(im, x2, y2, x2+3, y2, conf.merge_color.id);
1631     gdImageLine(im, x1-3, y1, x2+3, y2, conf.merge_color.id);
1632     }
1633     else
1634     {
1635     gdImageLine(im, x1, y1, x1+3, y1, conf.merge_color.id);
1636     gdImageLine(im, x2, y2, x2-3, y2, conf.merge_color.id);
1637     gdImageLine(im, x1+3, y1, x2-3, y2, conf.merge_color.id);
1638     }
1639     }
1640     }
1641     }
1642    
1643 bertho 1.19 static void alloc_color(gdImagePtr im, color_t *c)
1644 bertho 1.9 {
1645     c->id = gdImageColorAllocate(im, c->r, c->g, c->b);
1646     }
1647    
1648 bertho 1.7 gdImagePtr make_image(rcsfile_t *rcs)
1649 bertho 1.1 {
1650     gdImagePtr im;
1651     int i;
1652 bertho 1.8 char *cptr;
1653 bertho 1.1
1654 bertho 1.15 cptr = expand_string(conf.title, rcs, NULL, NULL, NULL, NULL);
1655     i = get_swidth(cptr, &conf.title_font);
1656     if(rcs->tw+conf.margin_left+conf.margin_right > i)
1657     i = rcs->tw+conf.margin_left+conf.margin_right;
1658     im = gdImageCreate(i, rcs->th+conf.margin_top+conf.margin_bottom);
1659 bertho 1.9 alloc_color(im, &conf.color_bg);
1660     alloc_color(im, &conf.tag_color);
1661     alloc_color(im, &conf.rev_color);
1662     alloc_color(im, &conf.rev_bgcolor);
1663     alloc_color(im, &conf.rev_text_color);
1664     alloc_color(im, &conf.branch_color);
1665 bertho 1.19 alloc_color(im, &conf.branch_tag_color);
1666 bertho 1.9 alloc_color(im, &conf.branch_bgcolor);
1667     alloc_color(im, &conf.title_color);
1668 bertho 1.30 alloc_color(im, &conf.merge_color);
1669 bertho 1.9 alloc_color(im, &black_color);
1670     alloc_color(im, &white_color);
1671 bertho 1.1
1672 bertho 1.19 if(conf.transparent_bg)
1673     gdImageColorTransparent(im, conf.color_bg.id);
1674    
1675 bertho 1.30 if(!conf.merge_front)
1676     draw_merges(im, rcs);
1677    
1678 bertho 1.1 for(i = 0; i < rcs->nbranches; i++)
1679 bertho 1.16 draw_branch(im, rcs->branches[i]);
1680 bertho 1.1 for(i = 0; i < rcs->nbranches; i++)
1681     {
1682     if(rcs->branches[i]->branchpoint)
1683     draw_connector(im, rcs->branches[i]);
1684     }
1685 bertho 1.9 draw_stringnl(im, cptr, &conf.title_font, conf.title_x, conf.title_y, conf.title_align, &conf.title_color);
1686 bertho 1.8 xfree(cptr);
1687 bertho 1.1
1688 bertho 1.30 if(conf.merge_front)
1689     draw_merges(im, rcs);
1690    
1691 bertho 1.1 return im;
1692     }
1693    
1694 bertho 1.7 /*
1695     **************************************************************************
1696     * Layout routines
1697 bertho 1.19 *
1698     * Branch BBox:
1699     * left = center_x - total_width / 2 (cx-tw)/2
1700     * right = center_x + total_width / 2 (cx+tw)/2
1701     * top = y_pos (y)
1702     * bottom = y_pos + total_height (y+th)
1703     *
1704     * Margins of branches:
1705     *
1706     * . .
1707     * . .
1708     * +--------------+
1709     * ^
1710     * | branch_margin .
1711     * v .
1712     * ----------------+ .
1713     * | ^ |
1714     * | | branch_connect |
1715     * | v |
1716     *..-+ +t-----+------+ +------+------+
1717     * | l | | |
1718     * | <--> | branch bbox | <--> | branch bbox |
1719     * | | | r | | |
1720     *..-+ | +------------b+ | +-------------+
1721     * | ^ branch_margin
1722     * | | branch_margin
1723     * | v
1724     * | +-------------+
1725     * | . .
1726     * | . .
1727     * |
1728     * branch_margin
1729     *
1730     * FIXME: There are probable som +/-1 errors in the code...
1731     * (notably shadows are not calculated in the margins)
1732 bertho 1.7 **************************************************************************
1733     */
1734 bertho 1.19 static void move_branch(branch_t *b, int x, int y)
1735 bertho 1.1 {
1736     int i;
1737 bertho 1.7 b->cx += x;
1738 bertho 1.1 b->y += y;
1739     for(i = 0; i < b->nrevs; i++)
1740     {
1741 bertho 1.7 b->revs[i]->cx += x;
1742 bertho 1.1 b->revs[i]->y += y;
1743     }
1744     }
1745    
1746 bertho 1.19 static void initial_reposition_branch(revision_t *r, int *x, int *w)
1747 bertho 1.6 {
1748     int i, j;
1749     for(j = 0; j < r->nbranches; j++)
1750     {
1751     branch_t *b = r->branches[j];
1752 bertho 1.7 *x += *w + conf.rev_minline + b->tw/2 - b->cx;
1753 bertho 1.6 *w = b->tw/2;
1754 bertho 1.7 move_branch(b, *x, r->y + r->h/2 + conf.branch_connect);
1755     *x = b->cx;
1756 bertho 1.6 /* Recurse to move branches of branched revisions */
1757     for(i = b->nrevs-1; i >= 0; i--)
1758     {
1759 bertho 1.18 initial_reposition_branch(b->revs[i], x, w);
1760 bertho 1.6 }
1761     }
1762     }
1763    
1764 bertho 1.25 static void initial_reposition_branch_lr(revision_t *r, int *y, int *h)
1765     {
1766     int i, j;
1767     for(j = 0; j < r->nbranches; j++)
1768     {
1769     branch_t *b = r->branches[j];
1770     *y += *h + conf.rev_minline + b->th/2 - b->y;
1771     *h = b->th/2;
1772     move_branch(b, r->cx + r->w/2 + conf.branch_connect, *y);
1773     *y = b->y;
1774     /* Recurse to move branches of branched revisions */
1775     for(i = b->nrevs-1; i >= 0; i--)
1776     {
1777     initial_reposition_branch(b->revs[i], y, h);
1778     }
1779     }
1780     }
1781    
1782 bertho 1.19 static void rect_union(int *x, int *y, int *w, int *h, branch_t *b)
1783 bertho 1.1 {
1784     int x1 = *x;
1785     int x2 = x1 + *w;
1786     int y1 = *y;
1787     int y2 = y1 + *h;
1788 bertho 1.25 int xx1;
1789     int xx2;
1790     int yy1;
1791     int yy2;
1792    
1793     if(conf.left_right)
1794     {
1795     xx1 = b->cx;
1796     yy1 = b->y - b->th/2;
1797     }
1798     else
1799     {
1800     xx1 = b->cx - b->tw/2;
1801     yy1 = b->y;
1802     }
1803     xx2 = xx1 + b->tw;
1804     yy2 = yy1 + b->th;
1805    
1806 bertho 1.1 x1 = MIN(x1, xx1);
1807     x2 = MAX(x2, xx2);
1808     y1 = MIN(y1, yy1);
1809     y2 = MAX(y2, yy2);
1810     *x = x1;
1811     *y = y1;
1812     *w = x2 - x1;
1813     *h = y2 - y1;
1814     }
1815    
1816 bertho 1.19 static int branch_intersects(int top, int bottom, int left, branch_t *b)
1817 bertho 1.7 {
1818     int br = b->cx + b->tw/2;
1819     int bt = b->y - conf.branch_connect - conf.branch_margin/2;
1820     int bb = b->y + b->th + conf.branch_margin/2;
1821     return !(bt > bottom || bb < top || br >= left);
1822     }
1823    
1824 bertho 1.25 static int branch_intersects_lr(int left, int right, int top, branch_t *b)
1825     {
1826     int bt = b->y + b->th/2;
1827     int bl = b->cx - conf.branch_connect - conf.branch_margin/2;
1828     int br = b->cx + b->tw + conf.branch_margin/2;
1829     return !(bl > right || br < left || bt >= top);
1830     }
1831    
1832 bertho 1.19 static int kern_branch(rcsfile_t *rcs, branch_t *b)
1833 bertho 1.7 {
1834     int left = b->cx - b->tw/2;
1835     int top = b->y - conf.branch_connect - conf.branch_margin/2;
1836     int bottom = b->y + b->th + conf.branch_margin/2;
1837     int i;
1838     int xpos = 0;
1839    
1840     for(i = 0; i < rcs->nbranches; i++)
1841     {
1842     branch_t *bp = rcs->branches[i];
1843     if(bp == b)
1844     continue;
1845     if(branch_intersects(top, bottom, left, bp))
1846     {
1847     int m = bp->cx + bp->tw/2 + conf.branch_margin;
1848     if(m > xpos)
1849     xpos = m;
1850     }
1851     }
1852     if(xpos && (b->cx - b->tw/2) - xpos > 0)
1853     {
1854     move_branch(b, xpos - (b->cx - b->tw/2), 0);
1855     return 1;
1856     }
1857     return 0;
1858     }
1859    
1860 bertho 1.25 static int kern_branch_lr(rcsfile_t *rcs, branch_t *b)
1861     {
1862     int top = b->y - b->th/2;
1863     int left = b->cx - conf.branch_connect - conf.branch_margin/2;
1864     int right = b->cx + b->tw + conf.branch_margin/2;
1865     int i;
1866     int ypos = 0;
1867    
1868     for(i = 0; i < rcs->nbranches; i++)
1869     {
1870     branch_t *bp = rcs->branches[i];
1871     if(bp == b)
1872     continue;
1873     if(branch_intersects_lr(left, right, top, bp))
1874     {
1875     int m = bp->y + bp->th/2 + conf.branch_margin;
1876     if(m > ypos)
1877     ypos = m;
1878     }
1879     }
1880     if(ypos && (b->y - b->th/2) - ypos > 0)
1881     {
1882     move_branch(b, 0, ypos - (b->y - b->th/2));
1883     return 1;
1884     }
1885     return 0;
1886     }
1887    
1888 bertho 1.19 static int kern_tree(rcsfile_t *rcs)
1889 bertho 1.18 {
1890     int i;
1891     int moved;
1892 bertho 1.19 int safeguard;
1893     int totalmoved = 0;
1894     for(moved = 1, safeguard = LOOPSAFEGUARD; moved && safeguard; safeguard--)
1895 bertho 1.18 {
1896     moved = 0;
1897     for(i = 1; i < rcs->nbranches; i++)
1898     {
1899 bertho 1.25 if(conf.left_right)
1900     moved += kern_branch_lr(rcs, rcs->branches[i]);
1901     else
1902     moved += kern_branch(rcs, rcs->branches[i]);
1903 bertho 1.18 }
1904 bertho 1.19 totalmoved += moved;
1905 bertho 1.18 #ifdef DEBUG
1906     fprintf(stderr, "kern_tree: moved=%d\n", moved);
1907     #endif
1908     }
1909 bertho 1.19 if(!quiet && !safeguard)
1910     fprintf(stderr, "kern_tree: safeguard terminated possible infinite loop; please report.\n");
1911     return totalmoved;
1912     }
1913    
1914     static int index_of_revision(revision_t *r)
1915     {
1916     branch_t *b = r->branch;
1917     int i;
1918     for(i = 0; i < b->nrevs; i++)
1919     {
1920     if(r == b->revs[i])
1921     return i;
1922     }
1923     fprintf(stderr, "index_of_revision: Cannot find revision in branch\n");
1924     return 0;
1925 bertho 1.18 }
1926    
1927 bertho 1.19 static void branch_bbox(branch_t *br, int *l, int *r, int *t, int *b)
1928     {
1929     if(l) *l = br->cx - br->tw/2;
1930     if(r) *r = br->cx + br->tw/2;
1931     if(t) *t = br->y;
1932     if(b) *b = br->y + br->th + (conf.branch_dupbox ? conf.rev_minline + br->h : 0);
1933     }
1934    
1935     static void branch_ext_bbox(branch_t *br, int *l, int *r, int *t, int *b)
1936     {
1937     int extra = conf.branch_margin & 1; /* Correct +/-1 error on div 2 */
1938     branch_bbox(br, l, r, t, b);
1939     if(l) *l -= conf.branch_margin/2;
1940     if(r) *r += conf.branch_margin/2 + extra;
1941     if(t) *t -= conf.branch_connect + conf.branch_margin/2;
1942     if(b) *b += conf.branch_margin/2 + extra;
1943     }
1944    
1945     static int branch_distance(branch_t *br1, branch_t *br2)
1946     {
1947     int l1, r1, t1, b1;
1948     int l2, r2, t2, b2;
1949     assert(br1 != NULL);
1950     assert(br2 != NULL);
1951     branch_bbox(br1, &l1, &r1, NULL, NULL);
1952     branch_bbox(br2, &l2, &r2, NULL, NULL);
1953     branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
1954     branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
1955     /* Return:
1956     * - 0 if branches have no horizontal overlap
1957     * - positive if b1 is left of b2
1958     * - negative if b2 is left of b1
1959     */
1960     if((t1 > t2 && t1 < b2) || (b1 > t2 && b1 < b2))
1961     return l1 < l2 ? l2 - r1 : -(l1 - r2);
1962     else
1963     return 0;
1964     }
1965    
1966     static int space_needed(branch_t *br1, branch_t *br2)
1967     {
1968     int t1, b1;
1969     int t2, b2;
1970     assert(br1 != NULL);
1971     assert(br2 != NULL);
1972     assert(br1->cx < br2->cx); /* br1 must be left of br2 */
1973     branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
1974     branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
1975     /* Return:
1976     * - positive if top br1 is located lower than br2
1977     * - negatve is top br2 is located lower than br1
1978     */
1979     if(t1 > t2)
1980     return -(t1 - b2);
1981     else
1982     return t2 - b1;
1983     }
1984    
1985     static void move_yr_branch(branch_t *b, int dy)
1986 bertho 1.18 {
1987     int i, j;
1988     #ifdef DEBUG
1989 bertho 1.23 /* fprintf(stderr, "move_yr_branch: b=%s, dy=%d\n", b->branch->branch, dy);*/
1990 bertho 1.18 #endif
1991 bertho 1.19 b->y += dy;
1992     for(i = 0; i < b->nrevs; i++)
1993 bertho 1.18 {
1994 bertho 1.19 b->revs[i]->y += dy;
1995     for(j = 0; j < b->revs[i]->nbranches; j++)
1996 bertho 1.18 {
1997 bertho 1.19 #ifdef DEBUG
1998 bertho 1.23 /* fprintf(stderr, ".");*/
1999 bertho 1.19 #endif
2000     move_yr_branch(b->revs[i]->branches[j], dy);
2001 bertho 1.18 }
2002     }
2003     }
2004    
2005 bertho 1.19 static void move_trunk(revision_t *r, int dy)
2006 bertho 1.18 {
2007 bertho 1.19 int i, j;
2008 bertho 1.18 branch_t *b = r->branch;
2009 bertho 1.19 b->th += dy;
2010     for(i = index_of_revision(r); i < b->nrevs; i++)
2011 bertho 1.18 {
2012 bertho 1.19 #ifdef DEBUG
2013     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);
2014     #endif
2015     b->revs[i]->y += dy;
2016     for(j = 0; j < b->revs[i]->nbranches; j++)
2017     {
2018     move_yr_branch(b->revs[i]->branches[j], dy);
2019     }
2020 bertho 1.18 }
2021     }
2022    
2023 bertho 1.19 static int space_below(rcsfile_t *rcs, revision_t *r)
2024 bertho 1.18 {
2025 bertho 1.19 int i, j;
2026     int bl, br, bb;
2027 bertho 1.18 int space = INT_MAX;
2028 bertho 1.19 branch_t *b = r->branch;
2029     branch_t *minb = NULL;
2030    
2031     branch_ext_bbox(b, &bl, &br, NULL, &bb);
2032 bertho 1.18 for(i = 0; i < rcs->nbranches; i++)
2033     {
2034 bertho 1.19 int tbl, tbr, tbt;
2035 bertho 1.18 branch_t *tb = rcs->branches[i];
2036 bertho 1.19 branch_ext_bbox(tb, &tbl, &tbr, &tbt, NULL);
2037 bertho 1.18 if(tb == b)
2038     continue;
2039 bertho 1.19 if(tbt > bb) /* Must be below our branch */
2040 bertho 1.18 {
2041 bertho 1.19 if(tb->branchpoint) /* Take account for the horiz connector */
2042     tbl = tb->branchpoint->cx + tb->branchpoint->branch->tw/2;
2043     if((bl >= tbl && bl <= tbr) || (br <= tbr && br >= tbl))
2044 bertho 1.18 {
2045 bertho 1.19 int s = tbt - bb - conf.branch_connect;
2046     if(s < space)
2047 bertho 1.18 {
2048 bertho 1.19 space = s;
2049     minb = tb;
2050 bertho 1.18 }
2051     }
2052     }
2053     }
2054 bertho 1.19 if(b->branchpoint)
2055     {
2056     for(i = index_of_revision(r); i < b->nrevs; i++)
2057     {
2058     for(j = 0; j < b->revs[i]->nbranches; j++)
2059     {
2060     int s = space_below(rcs, b->revs[i]->branches[j]->revs[0]);
2061     if(s < space)
2062     space = s;
2063     }
2064     }
2065     }
2066     #ifdef DEBUG
2067     fprintf(stderr, "space_below: from %s have %d to %s\n", b->branch->branch, space, minb ? minb->branch->branch : "<recursed>");
2068     #endif
2069 bertho 1.18 return space;
2070     }
2071    
2072 bertho 1.19 static int space_available(rcsfile_t *rcs, branch_t *colbr, branch_t *tagbr, int *nl, revision_t **bpcommon)
2073 bertho 1.18 {
2074 bertho 1.19 int i;
2075     int space = 0;
2076     int nlinks = 0;
2077     revision_t *r;
2078     branch_t *b;
2079     branch_t *ancestor;
2080     revision_t *branchpoint;
2081    
2082     if(!tagbr->branchpoint || !colbr->branchpoint)
2083 bertho 1.18 {
2084 bertho 1.19 if(!quiet)
2085     fprintf(stderr, "space_available: Trying to stretch the top?\n");
2086     return 0;
2087 bertho 1.18 }
2088 bertho 1.19
2089     r = colbr->branchpoint;
2090     b = r->branch;
2091     branchpoint = tagbr->branchpoint;
2092     ancestor = branchpoint->branch;
2093     assert(b != NULL);
2094     assert(ancestor != NULL);
2095    
2096     while(1)
2097 bertho 1.18 {
2098 bertho 1.19 int s;
2099     int rtag = b == ancestor ? index_of_revision(branchpoint)+1 : 0;
2100     for(i = index_of_revision(r); i >= rtag; i--)
2101 bertho 1.18 {
2102 bertho 1.19 if(i > 0)
2103     s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
2104     else
2105     s = b->revs[i]->y - (b->y + b->h);
2106     if(s < conf.rev_maxline)
2107     {
2108     space += conf.rev_maxline - s;
2109     nlinks++;
2110     }
2111 bertho 1.18 }
2112 bertho 1.19 s = space_below(rcs, r);
2113     if(s < space)
2114     space = s;
2115     #ifdef DEBUG
2116     if(space < 0)
2117     return -1;
2118     #endif
2119     if(b == ancestor)
2120     break;
2121     r = b->branchpoint;
2122     if(!r)
2123     {
2124     /* Not a common ancestor */
2125     r = colbr->branchpoint;
2126     b = r->branch;
2127     branchpoint = ancestor->branchpoint;
2128     if(!branchpoint)
2129     {
2130     if(!quiet)
2131     fprintf(stderr, "space_available: No common ancestor?\n");
2132     return 0;
2133     }
2134     ancestor = branchpoint->branch;
2135     assert(ancestor != NULL);
2136     nlinks = 0;
2137     space = 0;
2138     continue; /* Restart with a new ancestor */
2139     }
2140     b = r->branch;
2141     }
2142     if(nl)
2143     *nl = nlinks; /* Return the number of links that can stretch */
2144     if(bpcommon)
2145     *bpcommon = branchpoint; /* Return the ancestral branchpoint on the common branch */
2146     return space;
2147 bertho 1.18 }
2148    
2149 bertho 1.19 static int stretch_branches(rcsfile_t *rcs, branch_t *br1, branch_t *br2, int totalstretch)
2150 bertho 1.18 {
2151 bertho 1.19 revision_t *r;
2152     revision_t *bpcommon = NULL;
2153     branch_t *ancestor = NULL;
2154     branch_t *b;
2155 bertho 1.18 int i;
2156 bertho 1.19 int space;
2157     int nlinks;
2158     int dy;
2159     int rest;
2160    
2161     space = space_available(rcs, br1, br2, &nlinks, &bpcommon);
2162     if(bpcommon)
2163     ancestor = bpcommon->branch;
2164    
2165     #ifdef DEBUG
2166     if(space == -1)
2167     return 0;
2168     fprintf(stderr, "stretch_branches: space available %d over %d links common %s\n", space, nlinks, ancestor->branch->branch);
2169     #endif
2170     if(space < totalstretch)
2171     return 0;
2172    
2173     dy = totalstretch / nlinks;
2174     rest = totalstretch - dy * nlinks;
2175    
2176     r = br1->branchpoint;
2177     b = r->branch;
2178     while(1)
2179 bertho 1.18 {
2180 bertho 1.19 int rtag = b == ancestor ? index_of_revision(bpcommon)+1 : 0;
2181     for(i = index_of_revision(r); i >= rtag; i--)
2182     {
2183     int s, q;
2184     if(i > 0)
2185     s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
2186     else
2187     s = b->revs[i]->y - (b->y + b->h);
2188     q = conf.rev_maxline - s;
2189     if(q > 0)
2190     {
2191     int d = rest ? rest/nlinks+1 : 0;
2192     if(q >= dy+d)
2193     {
2194     move_trunk(b->revs[i], dy+d);
2195     }
2196     else
2197     {
2198     move_trunk(b->revs[i], q);
2199     rest += dy+d - q;
2200     }
2201     rest -= d;
2202     nlinks--;
2203     }
2204     }
2205     if(b == ancestor)
2206     break;
2207     r = b->branchpoint;
2208     assert(r != NULL); /* else 'space_available' wouldn't have returned positively */
2209     b = r->branch;
2210 bertho 1.18 }
2211 bertho 1.19 return 1;
2212 bertho 1.18 }
2213    
2214 bertho 1.19 static branch_t *find_collision_branch(rcsfile_t *rcs, branch_t *b)
2215 bertho 1.18 {
2216     int i;
2217 bertho 1.19 int dist = INT_MAX;
2218     branch_t *col = NULL;
2219    
2220 bertho 1.18 for(i = 0; i < rcs->nbranches; i++)
2221     {
2222 bertho 1.19 int t = branch_distance(rcs->branches[i], b);
2223     if(t > 0 && t < dist)
2224 bertho 1.18 {
2225 bertho 1.19 dist = t;
2226     col = rcs->branches[i];
2227 bertho 1.18 }
2228     }
2229 bertho 1.19 return col;
2230 bertho 1.18 }
2231    
2232 bertho 1.19 void auto_stretch(rcsfile_t *rcs)
2233 bertho 1.18 {
2234 bertho 1.19 int i;
2235     int safeguard;
2236 bertho 1.18
2237 bertho 1.19 for(i = 0, safeguard = LOOPSAFEGUARD; i < rcs->nbranches && safeguard; i++)
2238 bertho 1.18 {
2239 bertho 1.19 int bl, pr;
2240     branch_t *b = rcs->branches[i];
2241     if(!b->branchpoint)
2242     continue;
2243     branch_bbox(b, &bl, NULL, NULL, NULL);
2244     branch_bbox(b->branchpoint->branch, NULL, &pr, NULL, NULL);
2245     if(bl - conf.branch_margin - pr > 0)
2246 bertho 1.18 {
2247 bertho 1.19 branch_t *col;
2248 bertho 1.18 int spaceneeded;
2249 bertho 1.19 /* There is a potential to move branch b further left.
2250     * All branches obstructing this one from moving further
2251     * left must be originating from revisions below
2252     * b->branchpoint until a common ancester.
2253     * So, we search all branches for a branch that lies left
2254     * of b and is closest to b. This is then the collission
2255     * branch that needs to be moved.
2256     */
2257     col = find_collision_branch(rcs, b);
2258     if(!col)
2259 bertho 1.18 continue;
2260 bertho 1.19 spaceneeded = space_needed(col, b);
2261     if(spaceneeded < 0)
2262     continue;
2263     #ifdef DEBUG
2264     fprintf(stderr, "auto_stretch: %s collides %s need %d\n", b->branch->branch, col->branch->branch, spaceneeded);
2265     #endif
2266     /* Trace the collision branch back to find the common ancester
2267     * of both col and b. All revisions encountered while traversing
2268     * backwards must be stretched, including all revisions on the
2269     * common ancester from where the branches sprout.
2270     */
2271     if(stretch_branches(rcs, col, b, spaceneeded))
2272 bertho 1.18 {
2273 bertho 1.19 if(kern_tree(rcs))
2274     {
2275     /* Restart the process because movement can
2276     * cause more movement.
2277     */
2278     i = 0 - 1; /* -1 for the i++ of the loop */
2279     safeguard--; /* Prevent infinite loop, just in case */
2280     }
2281 bertho 1.23 /*return;*/
2282 bertho 1.18 }
2283     }
2284     }
2285 bertho 1.19 if(!quiet && !safeguard)
2286     fprintf(stderr, "auto_stretch: safeguard terminated possible infinite loop; please report.\n");
2287 bertho 1.18 }
2288    
2289 bertho 1.7 void make_layout(rcsfile_t *rcs)
2290 bertho 1.1 {
2291     int i, j;
2292     int x, y;
2293     int w, h;
2294     int w2;
2295    
2296 bertho 1.16 /* Remove all unwanted revisions */
2297     if(conf.strip_untagged)
2298     {
2299 bertho 1.21 int fr = conf.strip_first_rev ? 0 : 1;
2300 bertho 1.16 for(i = 0; i < rcs->nbranches; i++)
2301     {
2302     branch_t *bp = rcs->branches[i];
2303 bertho 1.18 for(j = fr; j < bp->nrevs-1; j++)
2304 bertho 1.16 {
2305     if(!bp->revs[j]->ntags && !bp->revs[j]->nbranches)
2306     {
2307     memmove(&bp->revs[j], &bp->revs[j+1], (bp->nrevs-j-1) * sizeof(bp->revs[0]));
2308     bp->nrevs--;
2309 bertho 1.17 bp->revs[j]->stripped = 1;
2310 bertho 1.16 j--;
2311     }
2312     }
2313     }
2314     }
2315    
2316 bertho 1.1 /* Calculate the box-sizes of the revisions */
2317 bertho 1.7 for(i = 0; i < rcs->nsrev; i++)
2318 bertho 1.1 {
2319     revision_t *rp;
2320     int w;
2321     int h;
2322 bertho 1.7 rp = rcs->srev[i];
2323 bertho 1.9 rp->revtext = expand_string(conf.rev_text, rcs, rp, rp->rev, NULL, rp->ntags ? rp->tags[0] : NULL);
2324     w = get_swidth(rp->revtext, &conf.rev_text_font);
2325     j = get_swidth(rp->rev->rev, &conf.rev_font);
2326     if(j > w)
2327     w = j;
2328     h = get_sheight(rp->revtext, &conf.rev_text_font) + get_sheight(rp->rev->rev, &conf.rev_font);
2329 bertho 1.1 for(j = 0; j < rp->ntags; j++)
2330     {
2331     int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);
2332     if(ww > w) w = ww;
2333     h += get_sheight(rp->tags[j]->tag, &conf.tag_font) + conf.rev_separator;
2334     }
2335     rp->w = w + conf.rev_lspace + conf.rev_rspace;
2336     rp->h = h + conf.rev_tspace + conf.rev_bspace;
2337     }
2338    
2339     /* Calculate the box-sizes of the branches */
2340     for(i = 0; i < rcs->nbranches; i++)
2341     {
2342     branch_t *bp = rcs->branches[i];
2343     int w;
2344     int h;
2345 bertho 1.7 w = get_swidth(bp->branch->branch, &conf.branch_font);
2346     h = get_sheight(bp->branch->branch, &conf.branch_font);
2347     for(j = 0; j < bp->ntags; j++)
2348 bertho 1.1 {
2349 bertho 1.19 int ww = get_swidth(bp->tags[j]->tag, &conf.branch_tag_font);
2350 bertho 1.1 if(ww > w) w = ww;
2351 bertho 1.19 h += get_sheight(bp->tags[j]->tag, &conf.branch_tag_font);
2352 bertho 1.1 }
2353     w += conf.branch_lspace + conf.branch_rspace;
2354     h += conf.branch_tspace + conf.branch_bspace;
2355     bp->w = w;
2356     bp->h = h;
2357 bertho 1.25 if(conf.left_right)
2358     {
2359     for(j = 0; j < bp->nrevs; j++)
2360     {
2361     if(bp->revs[j]->h > h)
2362     h = bp->revs[j]->h;
2363     w += bp->revs[j]->w + conf.rev_minline;
2364     }
2365     if(conf.branch_dupbox)
2366     w += bp->w + conf.rev_minline;
2367     }
2368     else
2369 bertho 1.1 {
2370 bertho 1.25 for(j = 0; j < bp->nrevs; j++)
2371     {
2372     if(bp->revs[j]->w > w)
2373     w = bp->revs[j]->w;
2374     h += bp->revs[j]->h + conf.rev_minline;
2375     }
2376     if(conf.branch_dupbox)
2377     h += bp->h + conf.rev_minline;
2378 bertho 1.1 }
2379     bp->th = h;
2380     bp->tw = w;
2381     }
2382    
2383     /* Calculate the relative positions of revs in a branch */
2384 bertho 1.25 if(conf.left_right)
2385     {
2386     for(i = 0; i < rcs->nbranches; i++)
2387     {
2388     branch_t *b = rcs->branches[i];
2389     y = b->th/2;
2390     x = b->w;
2391     b->y = y;
2392     b->cx = 0;
2393     for(j = 0; j < b->nrevs; j++)
2394     {
2395     x += conf.rev_minline;
2396     b->revs[j]->y = y;
2397     b->revs[j]->cx = x;
2398     x += b->revs[j]->w;
2399     }
2400     }
2401     }
2402     else
2403 bertho 1.1 {
2404 bertho 1.25 for(i = 0; i < rcs->nbranches; i++)
2405     {
2406     branch_t *b = rcs->branches[i];
2407     x = b->tw/2;
2408     y = b->h;
2409     b->cx = x;
2410     b->y = 0;
2411     for(j = 0; j < b->nrevs; j++)
2412     {
2413     y += conf.rev_minline;
2414     b->revs[j]->cx = x;
2415     b->revs[j]->y = y;
2416     y += b->revs[j]->h;
2417     }
2418 bertho 1.1 }
2419     }
2420    
2421 bertho 1.18 /* Initially reposition the branches from bottom to top progressively right */
2422 bertho 1.25 if(conf.left_right)
2423     {
2424     x = rcs->branches[0]->y;
2425     w2 = rcs->branches[0]->th / 2;
2426     for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
2427     {
2428     initial_reposition_branch_lr(rcs->branches[0]->revs[i], &x, &w2);
2429     }
2430     }
2431     else
2432 bertho 1.1 {
2433 bertho 1.25 x = rcs->branches[0]->cx;
2434     w2 = rcs->branches[0]->tw / 2;
2435     for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
2436     {
2437     initial_reposition_branch(rcs->branches[0]->revs[i], &x, &w2);
2438     }
2439 bertho 1.1 }
2440    
2441 bertho 1.19 /* Initially move branches left if there is room */
2442     kern_tree(rcs);
2443    
2444 bertho 1.18 /* Try to kern the branches more by expanding the inter-revision spacing */
2445 bertho 1.25 if(conf.auto_stretch && !conf.left_right)
2446 bertho 1.19 auto_stretch(rcs);
2447 bertho 1.7
2448     /* Move everything w.r.t. the top-left margin */
2449 bertho 1.1 for(i = 0; i < rcs->nbranches; i++)
2450     move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
2451    
2452     /* Calculate overall image size */
2453 bertho 1.25 if(conf.left_right)
2454     {
2455     x = rcs->branches[0]->cx;
2456     y = rcs->branches[0]->y - rcs->branches[0]->th/2;
2457     }
2458     else
2459     {
2460     x = rcs->branches[0]->cx - rcs->branches[0]->tw/2;
2461     y = rcs->branches[0]->y;
2462     }
2463 bertho 1.1 w = rcs->branches[0]->tw;
2464     h = rcs->branches[0]->th;
2465     for(i = 1; i < rcs->nbranches; i++)
2466     rect_union(&x, &y, &w, &h, rcs->branches[i]);
2467     rcs->tw = w;
2468     rcs->th = h;
2469 bertho 1.16
2470     /* Flip the entire tree */
2471     if(conf.upside_down)
2472     {
2473 bertho 1.25 if(conf.left_right)
2474     {
2475     x += rcs->tw;
2476     for(i = 0; i < rcs->nbranches; i++)
2477     {
2478     branch_t *b = rcs->branches[i];
2479     for(j = 0; j < b->nrevs; j++)
2480     {
2481     revision_t *r = b->revs[j];
2482 bertho 1.26 r->cx = x - r->cx - r->w + conf.margin_left;
2483 bertho 1.25 }
2484 bertho 1.26 b->cx = x - b->cx - b->w + conf.margin_left;
2485 bertho 1.25 }
2486     }
2487     else
2488 bertho 1.16 {
2489 bertho 1.25 y += rcs->th;
2490     for(i = 0; i < rcs->nbranches; i++)
2491 bertho 1.16 {
2492 bertho 1.25 branch_t *b = rcs->branches[i];
2493     for(j = 0; j < b->nrevs; j++)
2494     {
2495     revision_t *r = b->revs[j];
2496     r->y = y - r->y - r->h + conf.margin_top;
2497     }
2498     b->y = y - b->y - b->h + conf.margin_top;
2499 bertho 1.16 }
2500     }
2501     }
2502 bertho 1.1 }
2503    
2504     /*
2505     **************************************************************************
2506 bertho 1.6 * Imagemap functions
2507     **************************************************************************
2508     */
2509 bertho 1.26 void make_imagemap(rcsfile_t *rcs, FILE *fp, gdImagePtr im)
2510 bertho 1.6 {
2511     int i, j;
2512 bertho 1.29 const char *htp = conf.html_level == HTMLLEVEL_X ? " /" : "";
2513    
2514     switch(conf.html_level)
2515     {
2516     case HTMLLEVEL_4:
2517     fprintf(fp, "<map name=\"%s\" id=\"%s\">\n", conf.map_name, conf.map_name);
2518     break;
2519     case HTMLLEVEL_X:
2520     fprintf(fp, "<map id=\"%s\">\n", conf.map_name);
2521     break;
2522     default:
2523     fprintf(fp, "<map name=\"%s\">\n", conf.map_name);
2524     }
2525    
2526 bertho 1.6 for(i = 0; i < rcs->nbranches; i++)
2527     {
2528     branch_t *b = rcs->branches[i];
2529 bertho 1.8 tag_t *tag = b->ntags ? b->tags[0] : NULL;
2530 bertho 1.16 char *bhref = expand_string(conf.map_branch_href, rcs, NULL, b->branch, NULL, tag);
2531     char *balt = expand_string(conf.map_branch_alt, rcs, NULL, b->branch, NULL, tag);
2532 bertho 1.26 int x1;
2533     int x2;
2534     int y1;
2535     int y2;
2536    
2537     if(conf.left_right)
2538     {
2539     x1 = b->cx;
2540     y1 = b->y - b->h/2;
2541     x2 = b->cx + b->w;
2542     y2 = b->y + b->h/2;
2543     }
2544     else
2545     {
2546     x1 = b->cx - b->w/2;
2547     y1 = b->y;
2548     x2 = b->cx + b->w/2;
2549     y2 = b->y + b->h;
2550     }
2551 bertho 1.29 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
2552     bhref, x1, y1, x2, y2, balt, htp);
2553 bertho 1.26 if(im)
2554     {
2555     gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
2556     gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
2557     gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
2558     }
2559 bertho 1.6 for(j = 0; j < b->nrevs; j++)
2560     {
2561     revision_t *r = b->revs[j];
2562 bertho 1.13 revision_t* r1;
2563 bertho 1.26 int xoff = 1;
2564     int yoff = 1;
2565 bertho 1.16 char *href;
2566     char *alt;
2567 bertho 1.13
2568 bertho 1.8 tag = r->ntags ? r->tags[0] : NULL;
2569 bertho 1.9 href = expand_string(conf.map_rev_href, rcs, r, r->rev, NULL, tag);
2570     alt = expand_string(conf.map_rev_alt, rcs, r, r->rev, NULL, tag);
2571 bertho 1.26 if(conf.left_right)
2572     {
2573     x1 = r->cx;
2574     y1 = r->y - r->h/2;
2575     x2 = r->cx + r->w;
2576     y2 = r->y + r->h/2;
2577     }
2578     else
2579     {
2580     x1 = r->cx - r->w/2;
2581     y1 = r->y;
2582     x2 = r->cx + r->w/2;
2583     y2 = r->y + r->h;
2584     }
2585 bertho 1.29 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
2586     href, x1, y1, x2, y2, alt, htp);
2587 bertho 1.26 if(im)
2588     {
2589     gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
2590     gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
2591     gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
2592     }
2593 bertho 1.8 xfree(href);
2594     xfree(alt);
2595 bertho 1.16 if(j > 0 || b->branchpoint)
2596 bertho 1.9 {
2597 bertho 1.16 if(j > 0)
2598     {
2599     r1 = b->revs[j-1];
2600 bertho 1.26 if(conf.left_right)
2601     {
2602     yoff = MIN(r->h, r1->h)/4;
2603     x1 = conf.upside_down ? r1->cx : r1->cx + r1->w;
2604     }
2605     else
2606     {
2607     xoff = MIN(r->w, r1->w)/4;
2608     y1 = conf.upside_down ? r1->y : r1->y + r1->h;
2609     }
2610 bertho 1.16 }
2611     else
2612     {
2613     r1 = b->branchpoint;
2614 bertho 1.26 if(conf.left_right)
2615     {
2616     yoff = MIN(r->h, b->h)/4;
2617     x1 = conf.upside_down ? b->cx : b->cx + b->w;
2618     }
2619     else
2620     {
2621     xoff = MIN(r->w, b->w)/4;
2622     y1 = conf.upside_down ? b->y : b->y + b->h;
2623     }
2624     }
2625     if(conf.left_right)
2626     {
2627     y1 = r->y - yoff;
2628     y2 = r->y + yoff;
2629     x2 = conf.upside_down ? r->cx + r->w : r->cx;
2630     yoff = 0;
2631     }
2632     else
2633     {
2634     x1 = r->cx - xoff;
2635     x2 = r->cx + xoff;
2636     y2 = conf.upside_down ? r->y + r->h : r->y;
2637     xoff = 0;
2638     }
2639     if(x1 > x2)
2640     {
2641     int tt = x1;
2642     x1 = x2;
2643     x2 = tt;
2644     }
2645     if(y1 > y2)
2646     {
2647     int tt = y1;
2648     y1 = y2;
2649     y2 = tt;
2650 bertho 1.16 }
2651     href = expand_string(conf.map_diff_href, rcs, r, r->rev, r1->rev, tag);
2652     alt = expand_string(conf.map_diff_alt, rcs, r, r->rev, r1->rev, tag);
2653 bertho 1.29 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
2654 bertho 1.16 href,
2655 bertho 1.26 x1+xoff, y1+yoff, x2-xoff, y2-yoff,
2656 bertho 1.29 alt, htp);
2657 bertho 1.26 if(im)
2658     {
2659     gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
2660     gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
2661     gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
2662     }
2663 bertho 1.16 xfree(href);
2664     xfree(alt);
2665 bertho 1.9 }
2666 bertho 1.6 }
2667 bertho 1.17 if(conf.branch_dupbox)
2668 bertho 1.16 {
2669 bertho 1.26 if(conf.left_right)
2670     {
2671     x1 = conf.upside_down ? b->cx + b->w - b->tw : b->cx - b->w + b->tw;
2672     y1 = b->y - b->h/2;
2673     x2 = x1 + b->w;
2674     y2 = b->y + b->h/2;
2675     }
2676 bertho 1.16 else
2677 bertho 1.26 {
2678     x1 = b->cx - b->w/2;
2679     y1 = conf.upside_down ? b->y + b->h - b->th : b->y - b->h + b->th;
2680     x2 = b->cx + b->w/2;
2681     y2 = y1 + b->h;
2682     }
2683 bertho 1.29 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
2684     bhref, x1, y1, x2, y2, balt, htp);
2685 bertho 1.26 if(im)
2686     {
2687     gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
2688     gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
2689     gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
2690     }
2691 bertho 1.16 }
2692     xfree(bhref);
2693     xfree(balt);
2694 bertho 1.6 }
2695     fprintf(fp, "</map>\n");
2696     }
2697    
2698     /*
2699     **************************************************************************
2700 bertho 1.1 * Program entry
2701     **************************************************************************
2702     */
2703     static const char usage_str[] =
2704     "Usage: cvsgraph [options] <file>\n"
2705 bertho 1.16 " -b Add a branch box at both sides of the trunk (config value is negated)\n"
2706 bertho 1.8 " -c <file> Read alternative config from <file>\n"
2707     " -d <level> Enable debug mode at <level>\n"
2708     " -h This message\n"
2709     " -i Generate an imagemap instead of image\n"
2710 bertho 1.18 " -I <file> Also write the imagemap to <file>\n"
2711 bertho 1.19 " -k Auto stretch the tree (config value is negated)\n"
2712 bertho 1.8 " -M <name> Use <name> as imagemap name\n"
2713     " -m <mod> Use <mod> as cvs module\n"
2714     " -o <file> Output to <file>\n"
2715 bertho 1.18 " -O <opt=val> Set option opt to value val\n"
2716 bertho 1.8 " -q Be quiet (i.e. no warnings)\n"
2717     " -r <path> Use <path> as cvsroot path\n"
2718 bertho 1.16 " -s Strip untagged revisions (config value is negated)\n"
2719 bertho 1.18 " -S Also strip the first revision (config value is negated)\n"
2720 bertho 1.16 " -u Upside down image (mirror vertically; config value is negated)\n"
2721 bertho 1.8 " -V Print version and exit\n"
2722 bertho 1.29 " -x [34x] Specify level of HTML 3.2 (default), 4.0 or XHTML\n"
2723 bertho 1.8 " -[0-9] <txt> Use <txt> for expansion\n"
2724 bertho 1.1 ;
2725    
2726 bertho 1.29 #define VERSION_STR "1.3.1"
2727     #define NOTICE_STR "Copyright (c) 2001,2002,2003 B.Stultiens"
2728 bertho 1.1
2729 bertho 1.19 static void append_slash(char **path)
2730 bertho 1.9 {
2731     int l;
2732     assert(path != NULL);
2733     assert(*path != NULL);
2734     l = strlen(*path);
2735     if(!l || (*path)[l-1] == '/')
2736     return;
2737     *path = xrealloc(*path, l+2);
2738     strcat(*path, "/");
2739     }
2740    
2741 bertho 1.1 int main(int argc, char *argv[])
2742     {
2743 bertho 1.7 extern int rcs_flex_debug;
2744     extern int rcsdebug;
2745 bertho 1.1 int optc;
2746     char *confpath = NULL;
2747     char *outfile = NULL;
2748     char *cvsroot = NULL;
2749     char *cvsmodule = NULL;
2750 bertho 1.7 int imagemap = 0;
2751 bertho 1.16 int upsidedown = 0;
2752 bertho 1.17 int bdupbox = 0;
2753 bertho 1.16 int stripuntag = 0;
2754 bertho 1.18 int stripfirst = 0;
2755 bertho 1.19 int autostretch = 0;
2756 bertho 1.29 int htmllevel = 0;
2757 bertho 1.7 char *imgmapname = NULL;
2758 bertho 1.18 char *imgmapfile = NULL;
2759 bertho 1.1 int lose = 0;
2760     FILE *fp;
2761 bertho 1.18 char *rcsfilename;
2762 bertho 1.7 rcsfile_t *rcs;
2763 bertho 1.1 gdImagePtr im;
2764    
2765 bertho 1.29 while((optc = getopt(argc, argv, "0:1:2:3:4:5:6:7:8:9:bc:d:hI:ikM:m:O:o:qr:SsuVx:")) != EOF)
2766 bertho 1.1 {
2767     switch(optc)
2768     {
2769 bertho 1.16 case 'b':
2770 bertho 1.17 bdupbox = 1;
2771 bertho 1.16 break;
2772 bertho 1.1 case 'c':
2773     confpath = xstrdup(optarg);
2774     break;
2775 bertho 1.7 case 'd':
2776     debuglevel = strtol(optarg, NULL, 0);
2777     break;
2778 bertho 1.18 case 'I':
2779     imgmapfile = xstrdup(optarg);
2780     break;
2781 bertho 1.6 case 'i':
2782 bertho 1.7 imagemap = 1;
2783     break;
2784 bertho 1.18 case 'k':
2785 bertho 1.19 autostretch = 1;
2786 bertho 1.18 break;
2787 bertho 1.7 case 'M':
2788     imgmapname = xstrdup(optarg);
2789 bertho 1.6 break;
2790 bertho 1.1 case 'm':
2791     cvsmodule = xstrdup(optarg);
2792     break;
2793 bertho 1.18 case 'O':
2794     stack_option(optarg);
2795     break;
2796 bertho 1.1 case 'o':
2797     outfile = xstrdup(optarg);
2798     break;
2799 bertho 1.7 case 'q':
2800     quiet = 1;
2801     break;
2802 bertho 1.1 case 'r':
2803     cvsroot = xstrdup(optarg);
2804     break;
2805 bertho 1.18 case 'S':
2806     stripfirst = 1;
2807     break;
2808 bertho 1.16 case 's':
2809     stripuntag = 1;
2810     break;
2811     case 'u':
2812     upsidedown = 1;
2813     break;
2814 bertho 1.1 case 'V':
2815     fprintf(stdout, "cvsgraph v%s, %s\n", VERSION_STR, NOTICE_STR);
2816     return 0;
2817 bertho 1.29 case 'x':
2818     switch(optarg[0])
2819     {
2820     case '3':
2821     htmllevel = HTMLLEVEL_3;
2822     break;
2823     case '4':
2824     htmllevel = HTMLLEVEL_4;
2825     break;
2826     case 'x':
2827     htmllevel = HTMLLEVEL_X;
2828     break;
2829     default:
2830     fprintf(stderr, "Invalid HTML level in -x\n");
2831     lose++;
2832     }
2833     break;
2834 bertho 1.1 case 'h':
2835     fprintf(stdout, "%s", usage_str);
2836     return 0;
2837     default:
2838 bertho 1.8 if(isdigit(optc))
2839     {
2840     conf.expand[optc-'0'] = xstrdup(optarg);
2841     }
2842     else
2843     lose++;
2844 bertho 1.1 }
2845     }
2846    
2847     if(lose)
2848     {
2849     fprintf(stderr, "%s", usage_str);
2850     return 1;
2851     }
2852    
2853 bertho 1.7 if(debuglevel)
2854     {
2855     setvbuf(stdout, NULL, 0, _IONBF);
2856     setvbuf(stderr, NULL, 0, _IONBF);
2857     }
2858     rcs_flex_debug = (debuglevel & DEBUG_RCS_LEX) != 0;
2859     rcsdebug = (debuglevel & DEBUG_RCS_YACC) != 0;
2860    
2861 bertho 1.1 /* Set defaults */
2862 bertho 1.21 conf.tag_font.gdfont = gdFontTiny;
2863     conf.rev_font.gdfont = gdFontTiny;
2864     conf.branch_font.gdfont = gdFontTiny;
2865     conf.branch_tag_font.gdfont = gdFontTiny;
2866     conf.title_font.gdfont = gdFontTiny;
2867     conf.rev_text_font.gdfont = gdFontTiny;
2868    
2869 bertho 1.19 conf.anti_alias = 1;
2870 bertho 1.20 conf.thick_lines = 1;
2871 bertho 1.9
2872     conf.cvsroot = xstrdup("");
2873     conf.cvsmodule = xstrdup("");
2874     conf.date_format = xstrdup("%d-%b-%Y %H:%M:%S");
2875     conf.title = xstrdup("");
2876     conf.map_name = xstrdup("CvsGraphImageMap");
2877     conf.map_branch_href = xstrdup("href=\"unset: conf.map_branch_href\"");
2878     conf.map_branch_alt = xstrdup("alt=\"%B\"");
2879     conf.map_rev_href = xstrdup("href=\"unset: conf.map_rev_href\"");
2880     conf.map_rev_alt = xstrdup("alt=\"%R\"");
2881     conf.map_diff_href = xstrdup("href=\"unset: conf.map_diff_href\"");
2882 bertho 1.12 conf.map_diff_alt = xstrdup("alt=\"%P &lt;-&gt; %R\"");
2883 bertho 1.10 conf.rev_text = xstrdup("%d");
2884 bertho 1.30 conf.merge_from = xstrdup("");
2885     conf.merge_to = xstrdup("");
2886 bertho 1.9
2887     conf.color_bg = white_color;
2888     conf.branch_bgcolor = white_color;
2889     conf.branch_color = black_color;
2890 bertho 1.19 conf.branch_tag_color = black_color;
2891 bertho 1.9 conf.rev_color = black_color;
2892     conf.rev_bgcolor = white_color;
2893 bertho 1.30 conf.merge_color = black_color;
2894 bertho 1.9 conf.tag_color = black_color;
2895     conf.title_color = black_color;
2896     conf.rev_text_color = black_color;
2897 bertho 1.10
2898     conf.image_quality = 100;
2899 bertho 1.18 conf.rev_maxline = -1; /* Checked later to set to default */
2900    
2901     read_config(confpath);
2902 bertho 1.1
2903 bertho 1.18 if(conf.rev_maxline == -1) conf.rev_maxline = 5 * conf.rev_minline;
2904 bertho 1.1
2905     /* Set overrides */
2906 bertho 1.7 if(cvsroot) conf.cvsroot = cvsroot;
2907     if(cvsmodule) conf.cvsmodule = cvsmodule;
2908     if(imgmapname) conf.map_name = imgmapname;
2909 bertho 1.16 if(upsidedown) conf.upside_down = !conf.upside_down;
2910 bertho 1.17 if(bdupbox) conf.branch_dupbox = !conf.branch_dupbox;
2911 bertho 1.16 if(stripuntag) conf.strip_untagged = !conf.strip_untagged;
2912 bertho 1.18 if(stripfirst) conf.strip_first_rev = !conf.strip_first_rev;
2913 bertho 1.19 if(autostretch) conf.auto_stretch = !conf.auto_stretch;
2914 bertho 1.29 if(htmllevel) conf.html_level = htmllevel;
2915 bertho 1.18
2916     if(conf.rev_minline >= conf.rev_maxline)
2917     {
2918 bertho 1.19 if(conf.auto_stretch && !quiet)
2919     fprintf(stderr, "Auto stretch is only possible if rev_minline < rev_maxline\n");
2920     conf.auto_stretch = 0;
2921 bertho 1.18 }
2922 bertho 1.20
2923     if(conf.thick_lines < 1)
2924     conf.thick_lines = 1;
2925     if(conf.thick_lines > 11)
2926     conf.thick_lines = 11;
2927 bertho 1.1
2928 bertho 1.9 append_slash(&conf.cvsroot);
2929     append_slash(&conf.cvsmodule);
2930    
2931 bertho 1.18 if(optind >= argc)
2932     {
2933     #ifdef __WIN32__
2934     /* Bad hack for DOS/Windows */
2935     if(setmode(fileno(stdin), O_BINARY) == -1)
2936     {
2937     perror("Set binary mode for stdin");
2938     return 1;
2939     }
2940     #endif
2941     rcsfilename = NULL;
2942     }
2943     else
2944     rcsfilename = argv[optind];
2945    
2946     rcs = get_rcsfile(conf.cvsroot, conf.cvsmodule, rcsfilename);
2947 bertho 1.1 if(!rcs)
2948     return 1;
2949    
2950 bertho 1.7 if(debuglevel & DEBUG_RCS_FILE)
2951     dump_rcsfile(rcs);
2952 bertho 1.1
2953 bertho 1.7 if(!reorganise_branches(rcs))
2954     return 1;
2955 bertho 1.1
2956 bertho 1.7 if(!assign_tags(rcs))
2957     return 1;
2958 bertho 1.1
2959     if(outfile)
2960     {
2961 bertho 1.15 if((fp = fopen(outfile, "wb")) == NULL)
2962 bertho 1.1 {
2963     perror(outfile);
2964     return 1;
2965     }
2966     }
2967     else
2968 bertho 1.15 {
2969 bertho 1.1 fp = stdout;
2970 bertho 1.15 #ifdef __WIN32__
2971     /* Bad hack for DOS/Windows */
2972     if(setmode(fileno(fp), O_BINARY) == -1)
2973     {
2974     perror("Set binary mode for stdout");
2975     return 1;
2976     }
2977     #endif
2978     }
2979 bertho 1.5
2980 bertho 1.7 make_layout(rcs);
2981    
2982     if(!imagemap)
2983 bertho 1.5 {
2984 bertho 1.7 /* Create an image */
2985     im = make_image(rcs);
2986 bertho 1.26 #ifdef DEBUG_IMAGEMAP
2987     {
2988     FILE *nulfile = fopen("/dev/null", "w");
2989     make_imagemap(rcs, nulfile, im);
2990     fclose(nulfile);
2991     }
2992     #endif
2993 bertho 1.7 switch(conf.image_type)
2994     {
2995 bertho 1.5 #ifdef HAVE_IMAGE_GIF
2996 bertho 1.7 # ifndef HAVE_IMAGE_PNG
2997     default:
2998     # endif
2999     case IMAGE_GIF:
3000     gdImageGif(im, fp);
3001     break;
3002 bertho 1.5 #endif
3003     #ifdef HAVE_IMAGE_PNG
3004 bertho 1.7 default:
3005     case IMAGE_PNG:
3006     gdImagePng(im, fp);
3007     break;
3008 bertho 1.5 #endif
3009     #ifdef HAVE_IMAGE_JPEG
3010     # if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG)
3011 bertho 1.7 default:
3012 bertho 1.5 # endif
3013 bertho 1.7 case IMAGE_JPEG:
3014     gdImageJpeg(im, fp, conf.image_quality);
3015     break;
3016 bertho 1.5 #endif
3017 bertho 1.7 }
3018    
3019     gdImageDestroy(im);
3020     }
3021     else
3022     {
3023     /* Create an imagemap */
3024 bertho 1.26 make_imagemap(rcs, fp, NULL);
3025 bertho 1.18 }
3026    
3027     /* Also create imagemap to file if requested */
3028     if(imgmapfile)
3029     {
3030     FILE *ifp = fopen(imgmapfile, "wb");
3031     if(!ifp)
3032     {
3033     perror(imgmapfile);
3034     return 1;
3035     }
3036 bertho 1.26 make_imagemap(rcs, ifp, NULL);
3037 bertho 1.18 fclose(ifp);
3038 bertho 1.5 }
3039    
3040 bertho 1.1 if(outfile)
3041     fclose(fp);
3042 bertho 1.6
3043 bertho 1.1 return 0;
3044     }
3045    

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0