/[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.45 - (show annotations)
Mon Aug 16 08:39:27 2004 UTC (13 years, 2 months ago) by bertho
Branch: MAIN
Changes since 1.44: +3 -1 lines
File MIME type: text/plain
Add a configure check for sys/wait.h so that Windows/MingW should compile without editing cvsgraph.c.
1 /*
2 * CvsGraph graphical representation generator of brances and revisions
3 * of a file in cvs/rcs.
4 *
5 * Copyright (C) 2001,2002,2003 B. Stultiens
6 *
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 #include "config.h"
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <unistd.h>
28 #include <string.h>
29 #include <assert.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #ifdef HAVE_SYS_WAIT_H
33 # include <sys/wait.h>
34 #endif
35 #include <fcntl.h>
36 #include <regex.h>
37 #include <errno.h>
38 #include <ctype.h>
39 #include <time.h>
40 #include <limits.h>
41 #include <regex.h>
42 #include <math.h>
43
44 #ifdef HAVE_GETOPT_H
45 # include <getopt.h>
46 #endif
47
48 #include <gd.h>
49 #include <gdfontt.h>
50
51 #include "cvsgraph.h"
52 #include "utils.h"
53 #include "readconf.h"
54 #include "rcs.h"
55
56 #if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG) && !defined(HAVE_IMAGE_JPEG)
57 # error No image output format available. Check libgd
58 #endif
59
60
61 /*#define DEBUG 1*/
62 /*#define NOGDFILL 1*/
63 /*#define DEBUG_IMAGEMAP 1*/
64
65 #define LOOPSAFEGUARD 10000 /* Max itterations in possible infinite loops */
66
67 #ifndef MAX
68 # define MAX(a,b) ((a) > (b) ? (a) : (b))
69 #endif
70
71 #ifndef MIN
72 # define MIN(a,b) ((a) < (b) ? (a) : (b))
73 #endif
74
75 #define ALIGN_HL 0x00
76 #define ALIGN_HC 0x01
77 #define ALIGN_HR 0x02
78 #define ALIGN_HX 0x0f
79 #define ALIGN_VT 0x00
80 #define ALIGN_VC 0x10
81 #define ALIGN_VB 0x20
82 #define ALIGN_VX 0xf0
83
84 #ifndef M_PI /* math.h should have defined this */
85 # define M_PI 3.14159265358979323846
86 #endif
87 #define ROUND(f) ((f >= 0.0)?((int)(f + 0.5)):((int)(f - 0.5)))
88
89 #define ARROW_LENGTH 12 /* Default arrow dimensions */
90 #define ARROW_WIDTH 3
91
92 /*
93 **************************************************************************
94 * Globals
95 **************************************************************************
96 */
97
98 config_t conf;
99 int debuglevel;
100
101 static color_t white_color = {255, 255, 255, 0};
102 static color_t black_color = {0, 0, 0, 0};
103
104 static branch_t *subtree_branch = NULL; /* Set to the (first) subtree branch that we want to show */
105 static revision_t *subtree_rev = NULL; /* Set to the subtree revision which branches we want to show */
106
107 static msg_stack_t *msg_stack = NULL; /* Messages that would otherwise be sent to stderr goto the image */
108 static int nmsg_stack = 0;
109
110 /*
111 **************************************************************************
112 * Forwards
113 **************************************************************************
114 */
115 static void zap_string(void);
116 static char *dup_string(void);
117 static void add_string_str(const char *s);
118 static void add_string_ch(int ch);
119 static void add_string_date(const char *d);
120 static void add_string_str_html(const char *s, int maxlen);
121 static void add_string_str_len(const char *s, int maxlen);
122
123 static void calc_subtree_size(branch_t *b, int *x, int *y, int *w, int *h);
124
125 /*
126 **************************************************************************
127 * Debug routines
128 **************************************************************************
129 */
130 static void dump_rev(char *p, rev_t *r)
131 {
132 printf("%s", p);
133 if(r)
134 printf("'%s', '%s', %d\n", r->rev, r->branch, r->isbranch);
135 else
136 printf("<null>\n");
137 }
138
139 static void dump_id(char *p, char *d)
140 {
141 printf("%s", p);
142 if(d)
143 printf("'%s'\n", d);
144 else
145 printf("<null>\n");
146 }
147
148 static void dump_idrev(char *p, idrev_t *t)
149 {
150 printf("%s", p);
151 if(t)
152 {
153 printf("'%s' -> ", t->id);
154 dump_rev("", t->rev);
155 }
156 else
157 printf("<null>\n");
158 }
159
160 static void dump_tag(char *p, tag_t *t)
161 {
162 printf("%s", p);
163 if(t)
164 {
165 printf("'%s' -> ", t->tag);
166 dump_rev("", t->rev);
167 }
168 else
169 printf("<null>\n");
170 }
171
172 static void dump_delta(char *p, delta_t *d)
173 {
174 int i;
175 printf("%sdelta.rev : ", p);
176 dump_rev("", d->rev);
177 printf("%sdelta.date : %s\n", p, d->date);
178 printf("%sdelta.author: %s\n", p, d->author);
179 printf("%sdelta.state : %s\n", p, d->state);
180 for(i = 0; d->branches && i < d->branches->nrevs; i++)
181 {
182 printf("%sdelta.branch: ", p);
183 dump_rev("", d->branches->revs[i]);
184 }
185 printf("%sdelta.next : ", p);
186 dump_rev("", d->next);
187 printf("\n");
188 }
189
190 static void dump_dtext(char *p, dtext_t *d)
191 {
192 printf("%sdtext.rev : ", p);
193 dump_rev("", d->rev);
194 printf("%sdtext.log : %d bytes\n", p, d->log ? strlen(d->log) : -1);
195 printf("%sdtext.text : %d bytes\n", p, d->text ? strlen(d->text) : -1);
196 printf("\n");
197 }
198
199 static void dump_rcsfile(rcsfile_t *rcs)
200 {
201 int i;
202 printf("root : '%s'\n", rcs->root);
203 printf("module : '%s'\n", rcs->module);
204 printf("file : '%s'\n", rcs->file);
205 dump_rev("head : ", rcs->head);
206 dump_rev("branch : ", rcs->branch);
207 printf("access :\n");
208 for(i = 0; rcs->access && i < rcs->access->nids; i++)
209 dump_id("\t", rcs->access->ids[i]);
210 printf("tags :\n");
211 for(i = 0; rcs->tags && i < rcs->tags->ntags; i++)
212 dump_tag("\t", rcs->tags->tags[i]);
213 printf("locks :%s\n", rcs->strict ? " (strict)" : "");
214 for(i = 0; rcs->locks && i < rcs->locks->nidrevs; i++)
215 dump_idrev("\t", rcs->locks->idrevs[i]);
216 printf("comment: '%s'\n", rcs->comment);
217 printf("expand : '%s'\n", rcs->expand ? rcs->expand : "(default -kv)");
218 printf("deltas :\n");
219 for(i = 0; rcs->deltas && i < rcs->deltas->ndeltas; i++)
220 dump_delta("\t", rcs->deltas->deltas[i]);
221 printf("desc : '%s'\n", rcs->desc);
222 printf("dtexts :\n");
223 for(i = 0; rcs->dtexts && i < rcs->dtexts->ndtexts; i++)
224 dump_dtext("\t", rcs->dtexts->dtexts[i]);
225
226 fflush(stdout);
227 }
228
229 /*
230 **************************************************************************
231 * Error/Warning Message helpers
232 **************************************************************************
233 */
234 #define MSGBUFSIZE 256
235 void stack_msg(int severity, const char *fmt, ...)
236 {
237 va_list va;
238 int i;
239 char *buf = xmalloc(MSGBUFSIZE);
240 switch(severity)
241 {
242 case MSG_WARN: sprintf(buf, "Warning: "); break;
243 case MSG_ERR: sprintf(buf, "Error: "); break;
244 default: sprintf(buf, "Unqualified error: "); break;
245 }
246 i = strlen(buf);
247 assert(i < MSGBUFSIZE);
248 va_start(va, fmt);
249 vsnprintf(buf+i, MSGBUFSIZE-i, fmt, va);
250 va_end(va);
251 if(!msg_stack)
252 msg_stack = xmalloc(sizeof(*msg_stack));
253 else
254 {
255 msg_stack = xrealloc(msg_stack, (nmsg_stack+1)*sizeof(*msg_stack));
256 }
257 msg_stack[nmsg_stack].msg = buf;
258 msg_stack[nmsg_stack].severity = severity;
259 nmsg_stack++;
260 }
261
262 /*
263 **************************************************************************
264 * Read the rcs file
265 **************************************************************************
266 */
267 static rcsfile_t *get_rcsfile(const char *cvsroot, const char *module, const char *file)
268 {
269 char *cmd = NULL;
270 int rv;
271
272 if(file)
273 {
274 cmd = xmalloc(strlen(cvsroot) + strlen(module) + strlen(file) + 1);
275 sprintf(cmd, "%s%s%s", cvsroot, module, file);
276 if(!(rcsin = fopen(cmd, "rb")))
277 {
278 perror(cmd);
279 return NULL;
280 }
281 input_file = cmd;
282 }
283 else
284 {
285 rcsin = stdin;
286 input_file = "<stdin>";
287 }
288 line_number = 1;
289 rv = rcsparse();
290 if(file)
291 {
292 fclose(rcsin);
293 xfree(cmd);
294 }
295 if(rv)
296 return NULL;
297 input_file = NULL;
298 if(file)
299 {
300 rcsfile->root = xstrdup(cvsroot);
301 rcsfile->module = xstrdup(module);
302 rcsfile->file = xstrdup(file);
303 }
304 else
305 {
306 rcsfile->root = xstrdup("");
307 rcsfile->module = xstrdup("");
308 rcsfile->file = xstrdup("<stdin>");
309 }
310 return rcsfile;
311 }
312
313 /*
314 **************************************************************************
315 * Sort and find helpers
316 **************************************************************************
317 */
318 static int count_dots(const char *s)
319 {
320 int i;
321 for(i = 0; *s; s++)
322 {
323 if(*s == '.')
324 i++;
325 }
326 return i;
327 }
328
329 static int compare_rev(int bcmp, const rev_t *r1, const rev_t *r2)
330 {
331 int d1, d2;
332 char *c1, *c2;
333 char *v1, *v2;
334 char *s1, *s2;
335 int retval = 0;
336 assert(r1 != NULL);
337 assert(r2 != NULL);
338 if(bcmp)
339 {
340 assert(r1->branch != NULL);
341 assert(r2->branch != NULL);
342 c1 = r1->branch;
343 c2 = r2->branch;
344 }
345 else
346 {
347 assert(r1->rev != NULL);
348 assert(r2->rev != NULL);
349 c1 = r1->rev;
350 c2 = r2->rev;
351 }
352
353 d1 = count_dots(c1);
354 d2 = count_dots(c2);
355 if(d1 != d2)
356 {
357 return d1 - d2;
358 }
359
360 s1 = v1 = xstrdup(c1);
361 s2 = v2 = xstrdup(c2);
362 while(1)
363 {
364 char *vc1 = strchr(s1, '.');
365 char *vc2 = strchr(s2, '.');
366 if(vc1 && vc2)
367 *vc1 = *vc2 = '\0';
368 if(*s1 && *s2)
369 {
370 d1 = atoi(s1);
371 d2 = atoi(s2);
372 if(d1 != d2)
373 {
374 retval = d1 - d2;
375 break;
376 }
377 }
378 if(!vc1 || !vc2)
379 break;
380 s1 = vc1 + 1;
381 s2 = vc2 + 1;
382 }
383 xfree(v1);
384 xfree(v2);
385 return retval;
386 }
387
388 /*
389 **************************************************************************
390 * Reorganise the rcsfile for the branches
391 *
392 * Basically, we have a list of deltas (i.e. administrative info on
393 * revisions) and a list of delta text (the actual logs and diffs).
394 * The deltas are linked through the 'next' and the 'branches' fields
395 * which describe the tree-structure of revisions.
396 * The reorganisation means that we put each delta and corresponding
397 * delta text in a revision structure and assign it to a specific
398 * branch. This is required because we want to be able to calculate
399 * the bounding boxes of each branch. The revisions expand vertically
400 * and the branches expand horizontally.
401 * The reorganisation is performed in these steps:
402 * 1 - sort deltas and delta text on revision number for quick lookup
403 * 2 - start at the denoted head revision:
404 * * create a branch structure and add this revision
405 * * for each 'branches' in the delta do:
406 * - walk all 'branches' of the delta and recursively goto 2
407 * with the denoted branch delta as new head
408 * - backlink the newly create sub-branch to the head revision
409 * so that we can draw them recursively
410 * * set head to the 'next' field and goto 2 until no next is
411 * available
412 * 3 - update the administration
413 **************************************************************************
414 */
415 static int sort_delta(const void *d1, const void *d2)
416 {
417 return compare_rev(0, (*(delta_t **)d1)->rev, (*(delta_t **)d2)->rev);
418 }
419
420 static int search_delta(const void *r, const void *d)
421 {
422 return compare_rev(0, (rev_t *)r, (*(delta_t **)d)->rev);
423 }
424
425 static delta_t *find_delta(delta_t **dl, int n, rev_t *r)
426 {
427 delta_t **d;
428 if(!n)
429 return NULL;
430 d = bsearch(r, dl, n, sizeof(*dl), search_delta);
431 if(!d)
432 return NULL;
433 return *d;
434 }
435
436 static int sort_dtext(const void *d1, const void *d2)
437 {
438 return compare_rev(0, (*(dtext_t **)d1)->rev, (*(dtext_t **)d2)->rev);
439 }
440
441 static int search_dtext(const void *r, const void *d)
442 {
443 return compare_rev(0, (rev_t *)r, (*(dtext_t **)d)->rev);
444 }
445
446 static dtext_t *find_dtext(dtext_t **dl, int n, rev_t *r)
447 {
448 dtext_t **d;
449 if(!n)
450 return NULL;
451 d = bsearch(r, dl, n, sizeof(*dl), search_dtext);
452 if(!d)
453 return NULL;
454 return *d;
455 }
456
457 static rev_t *dup_rev(const rev_t *r)
458 {
459 rev_t *t = xmalloc(sizeof(*t));
460 t->rev = xstrdup(r->rev);
461 t->branch = xstrdup(r->branch);
462 t->isbranch = r->isbranch;
463 return t;
464 }
465
466 static branch_t *new_branch(delta_t *d, dtext_t *t)
467 {
468 branch_t *b = xmalloc(sizeof(*b));
469 revision_t *r = xmalloc(sizeof(*r));
470 r->delta = d;
471 r->dtext = t;
472 r->rev = d->rev;
473 r->branch = b;
474 b->branch = dup_rev(d->rev);
475 b->branch->isbranch = 1;
476 b->nrevs = 1;
477 b->revs = xmalloc(sizeof(b->revs[0]));
478 b->revs[0] = r;
479 return b;
480 }
481
482 static revision_t *add_to_branch(branch_t *b, delta_t *d, dtext_t *t)
483 {
484 revision_t *r = xmalloc(sizeof(*r));
485 r->delta = d;
486 r->dtext = t;
487 r->rev = d->rev;
488 r->branch = b;
489 b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));
490 b->revs[b->nrevs] = r;
491 b->nrevs++;
492 return r;
493 }
494
495 static void build_branch(branch_t ***bl, int *nbl, delta_t **sdl, int nsdl, dtext_t **sdt, int nsdt, delta_t *head)
496 {
497 branch_t *b;
498 dtext_t *text;
499 revision_t *currev;
500
501 assert(head != NULL);
502
503 if(head->flag)
504 {
505 stack_msg(MSG_ERR, "Circular reference on '%s' in branchpoint\n", head->rev->rev);
506 return;
507 }
508 head->flag++;
509 text = find_dtext(sdt, nsdt, head->rev);
510
511 /* Create a new branch for this head */
512 b = new_branch(head, text);
513 *bl = xrealloc(*bl, (*nbl+1)*sizeof((*bl)[0]));
514 (*bl)[*nbl] = b;
515 (*nbl)++;
516 currev = b->revs[0];
517 while(1)
518 {
519 /* Process all sub-branches */
520 if(head->branches)
521 {
522 int i;
523 for(i = 0; i < head->branches->nrevs; i++)
524 {
525 delta_t *d = find_delta(sdl, nsdl, head->branches->revs[i]);
526 int btag = *nbl;
527 if(!d)
528 continue;
529 build_branch(bl, nbl, sdl, nsdl, sdt, nsdt, d);
530
531 /* Set the new branch's origin */
532 (*bl)[btag]->branchpoint = currev;
533
534 /* Add branch to this revision */
535 currev->branches = xrealloc(currev->branches, (currev->nbranches+1)*sizeof(currev->branches[0]));
536 currev->branches[currev->nbranches] = (*bl)[btag];
537 currev->nbranches++;
538 }
539 }
540
541 /* Walk through the next list */
542 if(!head->next)
543 return;
544
545 head = find_delta(sdl, nsdl, head->next);
546 if(!head)
547 {
548 stack_msg(MSG_ERR, "Next revision (%s) not found in deltalist\n", head->next->rev);
549 return;
550 }
551 if(head->flag)
552 {
553 stack_msg(MSG_ERR, "Circular reference on '%s'\n", head->rev->rev);
554 return;
555 }
556 head->flag++;
557 text = find_dtext(sdt, nsdt, head->rev);
558 currev = add_to_branch(b, head, text);
559 }
560 }
561
562 int reorganise_branches(rcsfile_t *rcs)
563 {
564 delta_t **sdelta;
565 int nsdelta;
566 dtext_t **sdtext;
567 int nsdtext;
568 delta_t *head;
569 branch_t **bl;
570 int nbl;
571 int i;
572
573 assert(rcs->deltas != NULL);
574 assert(rcs->head != NULL);
575
576 /* Make a new list for quick lookup */
577 nsdelta = rcs->deltas->ndeltas;
578 sdelta = xmalloc(nsdelta * sizeof(sdelta[0]));
579 memcpy(sdelta, rcs->deltas->deltas, nsdelta * sizeof(sdelta[0]));
580 qsort(sdelta, nsdelta, sizeof(sdelta[0]), sort_delta);
581
582 /* Do the same for the delta text */
583 if(rcs->dtexts)
584 {
585 nsdtext = rcs->dtexts->ndtexts;
586 sdtext = xmalloc(nsdtext * sizeof(sdtext[0]));
587 memcpy(sdtext, rcs->dtexts->dtexts, nsdtext * sizeof(sdtext[0]));
588 qsort(sdtext, nsdtext, sizeof(sdtext[0]), sort_dtext);
589 }
590 else
591 {
592 nsdtext = 0;
593 sdtext = NULL;
594 }
595
596 /* Start from the head of the trunk */
597 head = find_delta(sdelta, nsdelta, rcs->head);
598 if(!head)
599 {
600 stack_msg(MSG_ERR, "Head revision (%s) not found in deltalist\n", rcs->head->rev);
601 return 0;
602 }
603 bl = NULL;
604 nbl = 0;
605 build_branch(&bl, &nbl, sdelta, nsdelta, sdtext, nsdtext, head);
606
607 /* Reverse the head branch */
608 for(i = 0; i < bl[0]->nrevs/2; i++)
609 {
610 revision_t *r;
611 r = bl[0]->revs[i];
612 bl[0]->revs[i] = bl[0]->revs[bl[0]->nrevs-i-1];
613 bl[0]->revs[bl[0]->nrevs-i-1] = r;
614 }
615
616 /* Update the branch-number of the head because it was reversed */
617 xfree(bl[0]->branch->branch);
618 bl[0]->branch->branch = xstrdup(bl[0]->revs[0]->rev->branch);
619
620 /* Keep the admin */
621 rcs->branches = bl;
622 rcs->nbranches = nbl;
623 rcs->sdelta = sdelta;
624 rcs->nsdelta = nsdelta;
625 rcs->sdtext = sdtext;
626 rcs->nsdtext = nsdtext;
627 rcs->active = bl[0];
628 return 1;
629 }
630
631 /*
632 **************************************************************************
633 * Assign the symbolic tags to the revisions and branches
634 *
635 * The tags point to revision numbers somewhere in the tree structure
636 * of branches and revisions. First we make a sorted list of all
637 * revisions and then we assign each tag to the proper revision.
638 **************************************************************************
639 */
640 static int sort_revision(const void *r1, const void *r2)
641 {
642 return compare_rev(0, (*(revision_t **)r1)->delta->rev, (*(revision_t **)r2)->delta->rev);
643 }
644
645 static int search_revision(const void *t, const void *r)
646 {
647 return compare_rev(0, (rev_t *)t, (*(revision_t **)r)->delta->rev);
648 }
649
650 static int sort_branch(const void *b1, const void *b2)
651 {
652 return compare_rev(1, (*(branch_t **)b1)->branch, (*(branch_t **)b2)->branch);
653 }
654
655 static int search_branch(const void *t, const void *b)
656 {
657 return compare_rev(1, (rev_t *)t, (*(branch_t **)b)->branch);
658 }
659
660 static char *previous_rev(const char *c)
661 {
662 int dots = count_dots(c);
663 char *cptr;
664 char *r;
665 if(!dots)
666 {
667 stack_msg(MSG_ERR, "FIXME: previous_rev(\"%s\"): Cannot determine parent branch revision\n", c);
668 return xstrdup("1.0"); /* FIXME: don't know what the parent is */
669 }
670 if(dots & 1)
671 {
672 /* Is is a revision we want the parent of */
673 r = xstrdup(c);
674 cptr = strrchr(r, '.');
675 assert(cptr != NULL);
676 if(dots == 1)
677 {
678 stack_msg(MSG_ERR, "FIXME: previous_rev(\"%s\"): Going beyond top-level?\n", c);
679 /* FIXME: What is the parent of 1.1? */
680 cptr[1] = '\0';
681 strcat(r, "0");
682 return r;
683 }
684 /* Here we have a "x.x[.x.x]+" case */
685 *cptr = '\0';
686 cptr = strrchr(r, '.');
687 assert(cptr != NULL);
688 *cptr = '\0';
689 return r;
690 }
691 /* It is a branch we want the parent of */
692 r = xstrdup(c);
693 cptr = strrchr(r, '.');
694 assert(cptr != NULL);
695 *cptr = '\0';
696 return r;
697 }
698
699 static char *build_regex(size_t n, regmatch_t *m, const char *ms)
700 {
701 char *cptr;
702 int i;
703
704 if(!conf.merge_to || !conf.merge_to[0])
705 return NULL;
706
707 zap_string();
708 for(cptr = conf.merge_to; *cptr; cptr++)
709 {
710 if(*cptr == '%')
711 {
712 if(cptr[1] >= '1' && cptr[1] <= '9')
713 {
714 int idx = cptr[1] - '0';
715 regmatch_t *p = &m[idx];
716 if(idx < n && !(p->rm_so == -1 || p->rm_so >= p->rm_eo))
717 {
718 for(i = p->rm_so; i < p->rm_eo; i++)
719 {
720 if(strchr("^$.*+\\[{()", ms[i]))
721 add_string_ch('\\');
722 add_string_ch(ms[i]);
723 }
724 }
725 cptr++;
726 }
727 else
728 add_string_ch('%');
729 }
730 else
731 add_string_ch(*cptr);
732 }
733 return dup_string();
734 }
735
736 static void find_merges(rcsfile_t *rcs)
737 {
738 int i;
739 int err;
740 int rcflags = REG_EXTENDED | (conf.merge_nocase ? REG_ICASE : 0);
741 regex_t *refrom = NULL;
742 regex_t *reto = NULL;
743 regmatch_t *matchfrom = NULL;
744
745 if(!conf.merge_from || !conf.merge_from[0] || !conf.merge_to || !conf.merge_to[0])
746 return;
747
748 refrom = xmalloc(sizeof(*refrom));
749 reto = xmalloc(sizeof(*reto));
750
751 /* Compile the 'from' regex match for merge identification */
752 err = regcomp(refrom, conf.merge_from, rcflags);
753 if(err)
754 {
755 char *msg;
756 i = regerror(err, refrom, NULL, 0);
757 msg = xmalloc(i+1);
758 regerror(err, refrom, msg, i+1);
759 stack_msg(MSG_WARN, "%s", msg);
760 xfree(msg);
761 xfree(refrom);
762 xfree(reto);
763 return;
764 }
765 else
766 matchfrom = xmalloc((refrom->re_nsub+1) * sizeof(*matchfrom));
767
768 for(i = 0; i < rcs->tags->ntags; i++)
769 {
770 tag_t *t = rcs->tags->tags[i];
771
772 /* Must be revision tags and not detached */
773 if(t->rev->isbranch || !t->logrev)
774 continue;
775
776 /* Try to find merge tag matches */
777 if(!regexec(refrom, t->tag, refrom->re_nsub+1, matchfrom, 0))
778 {
779 int n;
780 char *to;
781
782 to = build_regex(refrom->re_nsub+1, matchfrom, t->tag);
783 if(to)
784 {
785 err = regcomp(reto, to, rcflags);
786 if(err)
787 {
788 char *msg;
789 i = regerror(err, reto, NULL, 0);
790 msg = xmalloc(i+1);
791 regerror(err, reto, msg, i+1);
792 stack_msg(MSG_WARN, "%s", msg);
793 xfree(msg);
794 }
795 else if(!err)
796 {
797 for(n = 0; n < rcs->tags->ntags; n++)
798 {
799 tag_t *nt = rcs->tags->tags[n];
800 /* From and To never should match the same tag or belong to a branch */
801 if(n == i || nt->rev->isbranch || !nt->logrev)
802 continue;
803
804 if(!regexec(reto, nt->tag, 0, NULL, 0))
805 {
806 /* Tag matches */
807 rcs->merges = xrealloc(rcs->merges,
808 sizeof(rcs->merges[0]) * (rcs->nmerges+1));
809 rcs->merges[rcs->nmerges].to = nt;
810 rcs->merges[rcs->nmerges].from = t;
811 rcs->nmerges++;
812 /* Merges cannot be ignored tags */
813 nt->ignore = 0;
814 t->ignore = 0;
815 /* We cannot (should not) match multiple times */
816 break;
817 }
818 }
819 regfree(reto);
820 }
821 xfree(to);
822 }
823 }
824 }
825 if(matchfrom) xfree(matchfrom);
826 if(refrom) { regfree(refrom); xfree(refrom); }
827 if(reto) xfree(reto);
828 }
829
830 static void assign_tags(rcsfile_t *rcs)
831 {
832 int i;
833 int nr;
834 regex_t *regextag = NULL;
835
836 if(conf.tag_ignore && conf.tag_ignore[0])
837 {
838 int err;
839 regextag = xmalloc(sizeof(*regextag));
840 err = regcomp(regextag, conf.tag_ignore, REG_EXTENDED | REG_NOSUB | (conf.tag_nocase ? REG_ICASE : 0));
841 if(err)
842 {
843 char *msg;
844 i = regerror(err, regextag, NULL, 0);
845 msg = xmalloc(i+1);
846 regerror(err, regextag, msg, i+1);
847 stack_msg(MSG_WARN, "%s", msg);
848 xfree(msg);
849 xfree(regextag);
850 regextag = NULL;
851 }
852 }
853
854 for(i = nr = 0; i < rcs->nbranches; i++)
855 nr += rcs->branches[i]->nrevs;
856
857 rcs->srev = xmalloc(nr * sizeof(rcs->srev[0]));
858 rcs->nsrev = nr;
859 for(i = nr = 0; i < rcs->nbranches; i++)
860 {
861 memcpy(&rcs->srev[nr], rcs->branches[i]->revs, rcs->branches[i]->nrevs * sizeof(rcs->branches[i]->revs[0]));
862 nr += rcs->branches[i]->nrevs;
863 }
864
865 qsort(rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), sort_revision);
866 qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
867
868 if(!rcs->branch)
869 {
870 /* The main trunk is the active trunk */
871 rcs->tags->tags = xrealloc(rcs->tags->tags, (rcs->tags->ntags+1)*sizeof(rcs->tags->tags[0]));
872 rcs->tags->tags[rcs->tags->ntags] = xmalloc(sizeof(tag_t));
873 rcs->tags->tags[rcs->tags->ntags]->tag = xstrdup("MAIN");
874 rcs->tags->tags[rcs->tags->ntags]->rev = xmalloc(sizeof(rev_t));
875 rcs->tags->tags[rcs->tags->ntags]->rev->rev = xstrdup(rcs->active->branch->rev);
876 rcs->tags->tags[rcs->tags->ntags]->rev->branch = xstrdup(rcs->active->branch->branch);
877 rcs->tags->tags[rcs->tags->ntags]->rev->isbranch = 1;
878 rcs->tags->ntags++;
879 }
880
881 /* We should have at least two tags (HEAD and MAIN) */
882 assert(rcs->tags != NULL);
883
884 for(i = 0; i < rcs->tags->ntags; i++)
885 {
886 tag_t *t = rcs->tags->tags[i];
887 if(t->rev->isbranch)
888 {
889 branch_t **b;
890 add_btag:
891 b = bsearch(t->rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
892 if(!b)
893 {
894 rev_t rev;
895 revision_t **r;
896 /* This happens for the magic branch numbers if there are
897 * no commits within the new branch yet. So, we add the
898 * branch and try to continue.
899 */
900 rev.rev = previous_rev(t->rev->branch);
901 rev.branch = NULL;
902 rev.isbranch = 0;
903 r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
904 xfree(rev.rev);
905 if(!r)
906 {
907 stack_msg(MSG_WARN, "No branch found for tag '%s:%s'", t->tag, t->rev->branch);
908 }
909 else
910 {
911 rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1)*sizeof(rcs->branches[0]));
912 rcs->branches[rcs->nbranches] = xmalloc(sizeof(branch_t));
913 rcs->branches[rcs->nbranches]->branch = dup_rev(t->rev);
914 rcs->branches[rcs->nbranches]->branchpoint = *r;
915 (*r)->branches = xrealloc((*r)->branches, ((*r)->nbranches+1)*sizeof((*r)->branches[0]));
916 (*r)->branches[(*r)->nbranches] = rcs->branches[rcs->nbranches];
917 (*r)->nbranches++;
918 rcs->nbranches++;
919 /* Resort the branches */
920 qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
921 goto add_btag;
922 }
923 }
924 else
925 {
926 branch_t *bb = *b;
927 bb->tags = xrealloc(bb->tags, (bb->ntags+1)*sizeof(bb->tags[0]));
928 bb->tags[bb->ntags] = t;
929 bb->ntags++;
930 }
931 }
932 else
933 {
934 revision_t **r = bsearch(t->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
935 if(!r)
936 {
937 stack_msg(MSG_WARN, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);
938 }
939 else
940 {
941 revision_t *rr = *r;
942 t->logrev = rr;
943 if(!conf.rev_maxtags || rr->ntags <= conf.rev_maxtags)
944 {
945 rr->tags = xrealloc(rr->tags, (rr->ntags+1)*sizeof(rr->tags[0]));
946 if(conf.rev_maxtags && rr->ntags == conf.rev_maxtags)
947 {
948 rr->tags[rr->ntags] = xmalloc(sizeof(tag_t));
949 rr->tags[rr->ntags]->tag = xstrdup("...");
950 rr->tags[rr->ntags]->rev = t->rev;
951 }
952 else
953 rr->tags[rr->ntags] = t;
954 rr->ntags++;
955 }
956 }
957
958 if(conf.tag_negate)
959 t->ignore++;
960 /* Mark the tag ignored if it matches the configuration */
961 if(regextag && !regexec(regextag, t->tag, 0, NULL, 0))
962 {
963 if(conf.tag_negate)
964 t->ignore--;
965 else
966 t->ignore++;
967 }
968 }
969 }
970
971 /* We need to reset the first in the list of branches to the
972 * active branch to ensure the drawing of all
973 */
974 if(rcs->active != rcs->branches[0])
975 {
976 branch_t **b = bsearch(rcs->active->branch, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
977 branch_t *t;
978 assert(b != NULL);
979 t = *b;
980 *b = rcs->branches[0];
981 rcs->branches[0] = t;
982 }
983
984 if(regextag)
985 {
986 regfree(regextag);
987 xfree(regextag);
988 }
989 }
990
991 /*
992 **************************************************************************
993 * String expansion
994 **************************************************************************
995 */
996 static char *_string;
997 static int _nstring;
998 static int _nastring;
999
1000 static void zap_string(void)
1001 {
1002 _nstring = 0;
1003 if(_string)
1004 _string[0] = '\0';
1005 }
1006
1007 static char *dup_string(void)
1008 {
1009 if(_string)
1010 return xstrdup(_string);
1011 else
1012 return "";
1013 }
1014
1015 static void add_string_str(const char *s)
1016 {
1017 int l = strlen(s) + 1;
1018 if(_nstring + l > _nastring)
1019 {
1020 _nastring += MAX(128, l);
1021 _string = xrealloc(_string, _nastring * sizeof(_string[0]));
1022 }
1023 memcpy(_string+_nstring, s, l);
1024 _nstring += l-1;
1025 }
1026
1027 static void add_string_ch(int ch)
1028 {
1029 char buf[2];
1030 buf[0] = ch;
1031 buf[1] = '\0';
1032 add_string_str(buf);
1033 }
1034
1035 static void add_string_date(const char *d)
1036 {
1037 struct tm tm, *tmp;
1038 int n;
1039 time_t t;
1040 char *buf;
1041 int nbuf;
1042
1043 memset(&tm, 0, sizeof(tm));
1044 n = sscanf(d, "%d.%d.%d.%d.%d.%d",
1045 &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
1046 &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
1047 tm.tm_mon--;
1048 if(tm.tm_year > 1900)
1049 tm.tm_year -= 1900;
1050 t = mktime(&tm) - timezone;
1051 if(n != 6 || t == (time_t)(-1))
1052 {
1053 add_string_str("<invalid date>");
1054 return;
1055 }
1056
1057 tmp = localtime(&t);
1058 nbuf = strlen(conf.date_format) * 16; /* Should be enough to hold all types of expansions */
1059 buf = xmalloc(nbuf);
1060 strftime(buf, nbuf, conf.date_format, tmp);
1061 add_string_str(buf);
1062 xfree(buf);
1063 }
1064
1065 static void add_string_str_html(const char *s, int maxlen)
1066 {
1067 int l = 0;
1068 char *str = xmalloc(6 * strlen(s) + 1); /* Should hold all char entity-expand */
1069 char *cptr = str;
1070 for(; *s; s++)
1071 {
1072 if(maxlen && l > abs(maxlen))
1073 {
1074 cptr += sprintf(cptr, "...");
1075 break;
1076 }
1077 if(*s < 0x20)
1078 {
1079 if(*s == '\n')
1080 {
1081 if(maxlen < 0)
1082 *cptr++ = ' ';
1083 else
1084 cptr += sprintf(cptr, "<br%s>", conf.html_level == HTMLLEVEL_X ? " /" : "");
1085 }
1086 }
1087 else if(*s >= 0x7f || *s == '"')
1088 cptr += sprintf(cptr, "&#%d;", (int)(unsigned char)*s);
1089 else if(*s == '<')
1090 cptr += sprintf(cptr, "&lt;");
1091 else if(*s == '>')
1092 cptr += sprintf(cptr, "&gt;");
1093 else if(*s == '&')
1094 cptr += sprintf(cptr, "&amp;");
1095 else if(*s == '"')
1096 cptr += sprintf(cptr, "&quot;");
1097 else
1098 *cptr++ = *s;
1099 l++;
1100 }
1101 *cptr = '\0';
1102 add_string_str(str);
1103 xfree(str);
1104 }
1105
1106 static void add_string_str_len(const char *s, int maxlen)
1107 {
1108 int l = strlen(s);
1109 char *str = xmalloc(l + 1 + 3);
1110 strcpy(str, s);
1111 if(maxlen < l)
1112 sprintf(&str[maxlen], "...");
1113 add_string_str(str);
1114 xfree(str);
1115 }
1116
1117 static char *expand_string(const char *s, rcsfile_t *rcs, revision_t *r, rev_t *rev, rev_t *prev, tag_t *tag)
1118 {
1119 char nb[32];
1120 char nr[32];
1121 char *base;
1122 char *exp;
1123 int l;
1124 char ch;
1125
1126 if(!s)
1127 return xstrdup("");
1128
1129 zap_string();
1130
1131 sprintf(nb, "%d", rcs->nbranches);
1132 sprintf(nr, "%d", rcs->nsrev);
1133 for(; *s; s++)
1134 {
1135 if(*s == '%')
1136 {
1137 switch(*++s)
1138 {
1139 case 'c':
1140 case 'C':
1141 add_string_str(conf.cvsroot);
1142 if(*s == 'C' && conf.cvsroot[0] && conf.cvsroot[strlen(conf.cvsroot)-1] == '/')
1143 {
1144 /* Strip the trailing '/' */
1145 _nstring--;
1146 _string[_nstring] = '\0';
1147 }
1148 break;
1149 case 'f':
1150 case 'F':
1151 base = strrchr(rcs->file, '/');
1152 if(!base)
1153 add_string_str(rcs->file);
1154 else
1155 add_string_str(base+1);
1156 if(*s == 'F' && _string[_nstring-1] == 'v' && _string[_nstring-2] == ',')
1157 {
1158 _nstring -= 2;
1159 _string[_nstring] = '\0';
1160 }
1161 break;
1162 case 'p':
1163 base = strrchr(rcs->file, '/');
1164 if(base)
1165 {
1166 char *c = xstrdup(rcs->file);
1167 base = strrchr(c, '/');
1168 assert(base != NULL);
1169 base[1] = '\0';
1170 add_string_str(c);
1171 xfree(c);
1172 }
1173 /*
1174 * We should not add anything here because we can encounter
1175 * a completely empty path, in which case we do not want
1176 * to add any slash. This prevents an inadvertent root redirect.
1177 *
1178 * else
1179 * add_string_str("/");
1180 */
1181 break;
1182 case 'm':
1183 case 'M':
1184 add_string_str(conf.cvsmodule);
1185 if(*s == 'M' && conf.cvsmodule[0] && conf.cvsmodule[strlen(conf.cvsmodule)-1] == '/')
1186 {
1187 /* Strip the trailing '/' */
1188 _nstring--;
1189 _string[_nstring] = '\0';
1190 }
1191 break;
1192 case 'r': add_string_str(nr); break;
1193 case 'b': add_string_str(nb); break;
1194 case '%': add_string_ch('%'); break;
1195 case '0': if(conf.expand[0]) add_string_str(conf.expand[0]); break;
1196 case '1': if(conf.expand[1]) add_string_str(conf.expand[1]); break;
1197 case '2': if(conf.expand[2]) add_string_str(conf.expand[2]); break;
1198 case '3': if(conf.expand[3]) add_string_str(conf.expand[3]); break;
1199 case '4': if(conf.expand[4]) add_string_str(conf.expand[4]); break;
1200 case '5': if(conf.expand[5]) add_string_str(conf.expand[5]); break;
1201 case '6': if(conf.expand[6]) add_string_str(conf.expand[6]); break;
1202 case '7': if(conf.expand[7]) add_string_str(conf.expand[7]); break;
1203 case '8': if(conf.expand[8]) add_string_str(conf.expand[8]); break;
1204 case '9': if(conf.expand[9]) add_string_str(conf.expand[9]); break;
1205 case 'R': if(rev && rev->rev) add_string_str(rev->rev); break;
1206 case 'P': if(prev && prev->rev) add_string_str(prev->rev); break;
1207 case 'B': if(rev && rev->branch) add_string_str(rev->branch); break;
1208 case 't': if(tag && tag->tag) add_string_str(tag->tag); break;
1209 case 'd': if(r && r->delta && r->delta->date) add_string_date(r->delta->date); break;
1210 case 's': if(r && r->delta && r->delta->state) add_string_str(r->delta->state); break;
1211 case 'a': if(r && r->delta && r->delta->author) add_string_str(r->delta->author); break;
1212 case 'L':
1213 case 'l':
1214 ch = *s;
1215 l = 0;
1216 if(s[1] == '[')
1217 {
1218 char *cptr = strchr(s, ']');
1219 char *eptr;
1220 if(cptr)
1221 {
1222 l = strtol(&s[2], &eptr, 10);
1223 if(eptr != cptr)
1224 l = 0;
1225 else
1226 s = cptr;
1227 }
1228 }
1229 if(!conf.parse_logs)
1230 add_string_str("N/A");
1231 else if(r && r->dtext && r->dtext->log)
1232 {
1233 if(ch == 'l')
1234 add_string_str_html(r->dtext->log, l);
1235 else
1236 add_string_str_len(r->dtext->log, abs(l));
1237 }
1238 break;
1239 case '(':
1240 base = dup_string();
1241 exp = expand_string(s+1, rcs, r, rev, prev, tag);
1242 zap_string();
1243 add_string_str(base);
1244 add_string_str_html(exp, 0);
1245 xfree(base);
1246 xfree(exp);
1247 /* Find the %) in this recursion level */
1248 for(; *s; s++)
1249 {
1250 if(*s == '%' && s[1] == ')')
1251 {
1252 s++;
1253 break;
1254 }
1255 }
1256 if(!*s)
1257 {
1258 s--; /* To end outer loop */
1259 stack_msg(MSG_WARN, "string expand: Missing %%) in expansion");
1260 }
1261 break;
1262 case ')':
1263 return dup_string();
1264 default:
1265 add_string_ch('%');
1266 add_string_ch(*s);
1267 break;
1268 }
1269 }
1270 else
1271 add_string_ch(*s);
1272 }
1273 return dup_string();
1274 }
1275
1276 /*
1277 **************************************************************************
1278 * Drawing routines
1279 **************************************************************************
1280 */
1281 static int get_swidth(const char *s, font_t *f)
1282 {
1283 int n;
1284 int m;
1285 if(!s || !*s)
1286 return 0;
1287
1288 #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1289 if(conf.use_ttf && f->ttfont)
1290 {
1291 int bb[8];
1292 char *e;
1293 #ifdef HAVE_GDIMAGESTRINGFT
1294 e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1295 #else
1296 e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1297 #endif
1298 if(!e)
1299 return bb[2] - bb[6];
1300 }
1301 #endif
1302 for(n = m = 0; *s; n++, s++)
1303 {
1304 if(*s == '\n')
1305 {
1306 if(n > m)
1307 m = n;
1308 n = 0;
1309 }
1310 }
1311 if(n > m)
1312 m = n;
1313 return f->gdfont ? m * f->gdfont->w : m;
1314 }
1315
1316 static int get_sheight(const char *s, font_t *f)
1317 {
1318 int nl;
1319 if(!s || !*s)
1320 return 0;
1321
1322 #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1323 if(conf.use_ttf && f->ttfont)
1324 {
1325 int bb[8];
1326 char *e;
1327 #ifdef HAVE_GDIMAGESTRINGFT
1328 e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1329 #else
1330 e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1331 #endif
1332 if(!e)
1333 return bb[3] - bb[7] + 4;
1334 }
1335 #endif
1336 for(nl = 1; *s; s++)
1337 {
1338 if(*s == '\n' && s[1])
1339 nl++;
1340 }
1341 return nl * f->gdfont->h;
1342 }
1343
1344 static void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color, color_t *bgcolor)
1345 {
1346 int r2 = 2*r;
1347 if(!r)
1348 gdImageFilledRectangle(im, x1, y1, x2, y2, bgcolor->id);
1349 #ifdef HAVE_GDIMAGEFILLEDARC
1350 else
1351 {
1352 gdImageFilledArc(im, x1+r, y1+r, r2, r2, 180, 270, bgcolor->id, gdArc);
1353 gdImageFilledArc(im, x2-r, y1+r, r2, r2, 270, 360, bgcolor->id, gdArc);
1354 gdImageFilledArc(im, x1+r, y2-r, r2, r2, 90, 180, bgcolor->id, gdArc);
1355 gdImageFilledArc(im, x2-r, y2-r, r2, r2, 0, 90, bgcolor->id, gdArc);
1356 gdImageFilledRectangle(im, x1+r, y1, x2-r, y1+r, bgcolor->id);
1357 gdImageFilledRectangle(im, x1, y1+r, x2, y2-r, bgcolor->id);
1358 gdImageFilledRectangle(im, x1+r, y2-r, x2-r, y2, bgcolor->id);
1359 }
1360 #endif
1361 gdImageLine(im, x1+r, y1, x2-r, y1, color->id);
1362 gdImageLine(im, x1+r, y2, x2-r, y2, color->id);
1363 gdImageLine(im, x1, y1+r, x1, y2-r, color->id);
1364 gdImageLine(im, x2, y1+r, x2, y2-r, color->id);
1365 if(conf.box_shadow)
1366 {
1367 gdImageLine(im, x1+r+1, y2+1, x2-r, y2+1, black_color.id);
1368 gdImageLine(im, x2+1, y1+r+1, x2+1, y2-r, black_color.id);
1369 }
1370 if(r)
1371 {
1372 /* FIXME: Pixelization is not perfect */
1373 gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);
1374 gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);
1375 gdImageArc(im, x1+r, y2-r, r2, r2, 90, 180, color->id);
1376 if(conf.box_shadow)
1377 {
1378 gdImageArc(im, x2-r+1, y2-r+1, r2, r2, 0, 90, black_color.id);
1379 gdImageArc(im, x2-r+1, y2-r, r2, r2, 0, 90, black_color.id);
1380 gdImageArc(im, x2-r, y2-r+1, r2, r2, 0, 90, black_color.id);
1381 }
1382 gdImageArc(im, x2-r, y2-r, r2, r2, 0, 90, color->id);
1383 #if !defined(NOGDFILL) && !defined(HAVE_GDIMAGEFILLEDARC)
1384 /* BUG: We clip manually because libgd segfaults on out of bound values */
1385 if((x1+x2)/2 >= 0 && (x1+x2)/2 < gdImageSX(im) && (y1+y2)/2 >= 0 && (y1+y2)/2 < gdImageSY(im))
1386 gdImageFillToBorder(im, (x1+x2)/2, (y1+y2)/2, color->id, bgcolor->id);
1387 #endif
1388 }
1389 }
1390
1391 static void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
1392 {
1393 int h = get_sheight(s, f);
1394 int xx, yy;
1395 switch(align & ALIGN_HX)
1396 {
1397 default:
1398 case ALIGN_HL: xx = 0; break;
1399 case ALIGN_HC: xx = -get_swidth(s, f)/2; break;
1400 case ALIGN_HR: xx = -get_swidth(s, f); break;
1401 }
1402 switch(align & ALIGN_VX)
1403 {
1404 default:
1405 case ALIGN_VT: yy = 0; break;
1406 case ALIGN_VC: yy = h/2; break;
1407 case ALIGN_VB: yy = h; break;
1408 }
1409 #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1410 if(conf.use_ttf && f->ttfont)
1411 {
1412 int bb[8];
1413 char *e;
1414 int cid = conf.anti_alias ? c->id : -c->id;
1415 #ifdef HAVE_GDIMAGESTRINGFT
1416 e = gdImageStringFT(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1417 #else
1418 e = gdImageStringTTF(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1419 #endif
1420 if(!e)
1421 return;
1422 }
1423 #endif
1424 yy = -yy;
1425 gdImageString(im, f->gdfont, x+xx+1, y+yy, s, c->id);
1426 }
1427
1428 static void draw_stringnl(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
1429 {
1430 char *t;
1431 char *d;
1432 d = s = xstrdup(s);
1433 do
1434 {
1435 t = strchr(s, '\n');
1436 if(t)
1437 *t = '\0';
1438 draw_string(im, s, f, x, y, align, c);
1439 y += get_sheight(s, f);
1440 s = t+1;
1441 } while(t);
1442 xfree(d);
1443 }
1444
1445 static void draw_rev(gdImagePtr im, revision_t *r)
1446 {
1447 int lx;
1448 int rx;
1449 int x2;
1450 int i;
1451 int ty;
1452
1453 if(conf.left_right)
1454 {
1455 lx = r->cx;
1456 rx = r->cx + r->w;
1457 ty = r->y - r->h/2;
1458 x2 = r->cx + r->w/2;
1459 }
1460 else
1461 {
1462 lx = r->cx - r->w/2;
1463 rx = lx + r->w;
1464 ty = r->y;
1465 x2 = r->cx;
1466 }
1467 draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color, &conf.rev_bgcolor);
1468 ty += conf.rev_tspace;
1469 draw_string(im, r->rev->rev, &conf.rev_font, x2, ty, ALIGN_HC, &conf.rev_color);
1470 ty += get_sheight(r->rev->rev, &conf.rev_font);
1471 draw_stringnl(im, r->revtext, &conf.rev_text_font, x2, ty, ALIGN_HC, &conf.rev_text_color);
1472 ty += get_sheight(r->revtext, &conf.rev_text_font);
1473 for(i = 0; i < r->ntags; i++)
1474 {
1475 draw_string(im, r->tags[i]->tag, &conf.tag_font, x2, ty, ALIGN_HC, &conf.tag_color);
1476 ty += get_sheight(r->tags[i]->tag, &conf.tag_font) + conf.rev_separator;
1477 }
1478 }
1479
1480 static void draw_branch_box(gdImagePtr im, branch_t *b, int xp, int yp)
1481 {
1482 int lx;
1483 int rx;
1484 int i;
1485 int yy;
1486 int x2;
1487
1488 if(conf.left_right)
1489 {
1490 lx = b->cx;
1491 rx = lx + b->w;
1492 x2 = b->cx + b->w/2;
1493 }
1494 else
1495 {
1496 lx = b->cx - b->w/2;
1497 rx = lx + b->w;
1498 x2 = b->cx;
1499 }
1500 draw_rbox(im, lx+xp, yp, rx+xp, yp+b->h, 5, &conf.branch_color, &conf.branch_bgcolor);
1501 yy = conf.branch_tspace;
1502 if(!b->nfolds)
1503 {
1504 draw_string(im, b->branch->branch, &conf.branch_font, x2+xp, yp+yy, ALIGN_HC, &conf.branch_color);
1505 yy += get_sheight(b->branch->branch, &conf.branch_font);
1506 for(i = 0; i < b->ntags; i++)
1507 {
1508 draw_string(im, b->tags[i]->tag, &conf.branch_tag_font, x2+xp, yp+yy, ALIGN_HC, &conf.branch_tag_color);
1509 yy += get_sheight(b->tags[i]->tag, &conf.branch_tag_font);
1510 }
1511 }
1512 else
1513 {
1514 int y1, y2;
1515 int tx = lx + b->fw + conf.branch_lspace;
1516 int nx = tx - get_swidth(" ", &conf.branch_font);
1517 draw_string(im, b->branch->branch, &conf.branch_font, nx+xp, yp+yy, ALIGN_HR, &conf.branch_color);
1518 y1 = get_sheight(b->branch->branch, &conf.branch_font);
1519 draw_string(im, b->tags[0]->tag, &conf.branch_tag_font, tx+xp, yp+yy, ALIGN_HL, &conf.branch_tag_color);
1520 y2 = get_sheight(b->tags[0]->tag, &conf.branch_font);
1521 yy += MAX(y1, y2);
1522 for(i = 0; i < b->nfolds; i++)
1523 {
1524 draw_string(im, b->folds[i]->branch->branch, &conf.branch_font, nx+xp, yp+yy, ALIGN_HR, &conf.branch_color);
1525 y1 = get_sheight(b->folds[i]->branch->branch, &conf.branch_font);
1526 draw_string(im, b->folds[i]->tags[0]->tag, &conf.branch_tag_font, tx+xp, yp+yy, ALIGN_HL, &conf.branch_tag_color);
1527 y2 = get_sheight(b->folds[i]->tags[0]->tag, &conf.branch_tag_font);
1528 yy += MAX(y1, y2);
1529 }
1530 }
1531 }
1532
1533 static void draw_branch(gdImagePtr im, branch_t *b)
1534 {
1535 int yy, xx;
1536 int i;
1537 int line[4];
1538 int l;
1539 int sign;
1540
1541 line[0] = conf.rev_color.id;
1542 line[1] = gdTransparent;
1543 line[1] = gdTransparent;
1544 line[3] = conf.rev_color.id;
1545
1546 /* Trivial clip the branch */
1547 if(conf.left_right)
1548 {
1549 if(b->cx > gdImageSX(im) || b->cx+b->tw < 0 || b->y-b->th/2 > gdImageSY(im) || b->y+b->th/2 < 0)
1550 return;
1551 }
1552 else
1553 {
1554 if(b->cx-b->tw/2 > gdImageSX(im) || b->cx+b->tw/2 < 0 || b->y > gdImageSY(im) || b->y+b->th < 0)
1555 return;
1556 }
1557
1558 draw_branch_box(im, b, 0, conf.left_right ? b->y - b->h/2 : b->y);
1559
1560 if(conf.left_right)
1561 {
1562 if(conf.upside_down)
1563 {
1564 xx = b->cx;
1565 for(i = 0; i < b->nrevs; i++)
1566 {
1567 revision_t *r = b->revs[i];
1568 gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1569 gdImageLine(im, xx, r->y, r->cx+r->w, r->y, gdStyled);
1570 for(sign = l = 1; l < conf.thick_lines; l++)
1571 {
1572 int pp = (l+1)/2*sign;
1573 gdImageLine(im, xx, r->y+pp, r->cx+r->w, r->y+pp, gdStyled);
1574 sign *= -1;
1575 }
1576 draw_rev(im, r);
1577 xx = r->cx;
1578 }
1579 if(conf.branch_dupbox && b->nrevs)
1580 {
1581 i = b->cx - b->tw + b->w;
1582 gdImageLine(im, xx, b->y, i+b->w, b->y, conf.rev_color.id);
1583 for(sign = l = 1; l < conf.thick_lines; l++)
1584 {
1585 int pp = (l+1)/2*sign;
1586 gdImageLine(im, xx, b->y+pp, i+b->w, b->y+pp, conf.rev_color.id);
1587 sign *= -1;
1588 }
1589 draw_branch_box(im, b, i - b->cx, b->y - b->h/2);
1590 }
1591 }
1592 else
1593 {
1594 xx = b->cx + b->w;
1595 for(i = 0; i < b->nrevs; i++)
1596 {
1597 revision_t *r = b->revs[i];
1598 gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1599 gdImageLine(im, xx, r->y, r->cx, r->y, gdStyled);
1600 for(sign = l = 1; l < conf.thick_lines; l++)
1601 {
1602 int pp = (l+1)/2*sign;
1603 gdImageLine(im, xx, r->y+pp, r->cx, r->y+pp, gdStyled);
1604 sign *= -1;
1605 }
1606 draw_rev(im, r);
1607 xx = r->cx + r->w;
1608 }
1609 if(conf.branch_dupbox && b->nrevs)
1610 {
1611 i = b->cx + b->tw - b->w;
1612 gdImageLine(im, xx, b->y, i, b->y, conf.rev_color.id);
1613 for(sign = l = 1; l < conf.thick_lines; l++)
1614 {
1615 int pp = (l+1)/2*sign;
1616 gdImageLine(im, xx, b->y+pp, i, b->y+pp, conf.rev_color.id);
1617 sign *= -1;
1618 }
1619 draw_branch_box(im, b, i - b->cx, b->y - b->h/2);
1620 }
1621 }
1622 }
1623 else
1624 {
1625 if(conf.upside_down)
1626 {
1627 yy = b->y;
1628 for(i = 0; i < b->nrevs; i++)
1629 {
1630 revision_t *r = b->revs[i];
1631 gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1632 gdImageLine(im, r->cx, yy, r->cx, r->y+r->h, gdStyled);
1633 for(sign = l = 1; l < conf.thick_lines; l++)
1634 {
1635 int pp = (l+1)/2*sign;
1636 gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y+r->h, gdStyled);
1637 sign *= -1;
1638 }
1639 draw_rev(im, r);
1640 yy = r->y;
1641 }
1642 if(conf.branch_dupbox && b->nrevs)
1643 {
1644 i = b->y - b->th + b->h;
1645 gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);
1646 for(sign = l = 1; l < conf.thick_lines; l++)
1647 {
1648 int pp = (l+1)/2*sign;
1649 gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, conf.rev_color.id);
1650 sign *= -1;
1651 }
1652 draw_branch_box(im, b, 0, i);
1653 }
1654 }
1655 else
1656 {
1657 yy = b->y + b->h;
1658 for(i = 0; i < b->nrevs; i++)
1659 {
1660 revision_t *r = b->revs[i];
1661 gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1662 gdImageLine(im, r->cx, yy, r->cx, r->y, gdStyled);
1663 for(sign = l = 1; l < conf.thick_lines; l++)
1664 {
1665 int pp = (l+1)/2*sign;
1666 gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y, gdStyled);
1667 sign *= -1;
1668 }
1669 draw_rev(im, r);
1670 yy = r->y + r->h;
1671 }
1672 if(conf.branch_dupbox && b->nrevs)
1673 {
1674 i = b->y + b->th - b->h;
1675 gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);
1676 for(sign = l = 1; l < conf.thick_lines; l++)
1677 {
1678 int pp = (l+1)/2*sign;
1679 gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, conf.rev_color.id);
1680 sign *= -1;
1681 }
1682 draw_branch_box(im, b, 0, i);
1683 }
1684 }
1685 }
1686 }
1687
1688 static void draw_connector(gdImagePtr im, branch_t *b)
1689 {
1690 int l;
1691 int sign;
1692 revision_t *r = b->branchpoint;
1693 int x1 = r->cx + r->w/2 + 2;
1694 int y1 = r->y + r->h/2;
1695 int x2 = b->cx;
1696 int y2 = b->y;
1697
1698 if(conf.left_right)
1699 {
1700 x2 = r->cx + r->w/2;
1701 y2 = r->y + r->h/2 + 3;
1702 x1 = b->cx;
1703 y1 = b->y;
1704 if(conf.upside_down)
1705 x1 += b->w;
1706 }
1707 else
1708 {
1709 x1 = r->cx + r->w/2 + 2;
1710 y1 = r->y + r->h/2;
1711 x2 = b->cx;
1712 y2 = b->y;
1713 if(conf.upside_down)
1714 y2 += b->h;
1715 }
1716 gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);
1717 gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);
1718 for(sign = l = 1; l < conf.thick_lines; l++)
1719 {
1720 int pp = (l+1)/2*sign;
1721 gdImageLine(im, x1, y1+pp, x2, y1+pp, conf.branch_color.id);
1722 gdImageLine(im, x2+pp, y1, x2+pp, y2, conf.branch_color.id);
1723 sign *= -1;
1724 }
1725 }
1726
1727 static void draw_merges(gdImagePtr im, rcsfile_t *rcs, int dot)
1728 {
1729 int i;
1730 for(i = 0; i < rcs->nmerges; i++)
1731 {
1732 revision_t *fr = rcs->merges[i].from->logrev;
1733 revision_t *tr = rcs->merges[i].to->logrev;
1734 int x1, x2, y1, y2;
1735 if(!fr || !tr || fr == tr)
1736 continue; /* This can happen with detached tags and self-references */
1737 if(conf.left_right)
1738 {
1739 if(fr->branch == tr->branch)
1740 {
1741 y1 = fr->y - fr->h/2;
1742 y2 = tr->y - tr->h/2;
1743 }
1744 else
1745 {
1746 if(fr->y < tr->y)
1747 {
1748 y1 = fr->y + fr->h/2;
1749 y2 = tr->y - tr->h/2;
1750 }
1751 else
1752 {
1753 y1 = fr->y - fr->h/2;
1754 y2 = tr->y + tr->h/2;
1755 }
1756 }
1757 x1 = fr->cx + fr->w/2;
1758 x2 = tr->cx + tr->w/2;
1759 }
1760 else
1761 {
1762 if(fr->branch == tr->branch)
1763 {
1764 x1 = fr->cx - fr->w/2;
1765 x2 = tr->cx - tr->w/2;
1766 }
1767 else
1768 {
1769 if(fr->cx < tr->cx)
1770 {
1771 x1 = fr->cx + fr->w/2;
1772 x2 = tr->cx - tr->w/2;
1773 }
1774 else
1775 {
1776 x1 = fr->cx - fr->w/2;
1777 x2 = tr->cx + tr->w/2;
1778 }
1779 }
1780 y1 = fr->y + rcs->merges[i].from->yofs;
1781 y2 = tr->y + rcs->merges[i].to->yofs;
1782 }
1783 if(dot && !conf.merge_arrows)
1784 {
1785 int o = conf.left_right ? 1 : 0;
1786 gdImageArc(im, x2, y2+o, 8, 8, 0, 360, conf.merge_color.id);
1787 /* BUG: We clip manually because libgd segfaults on out of bound values */
1788 if(x2+1 >= 0 && x2+1 < gdImageSX(im) && y2+o+1 >= 0 && y2+o+1 < gdImageSY(im))
1789 gdImageFillToBorder(im, x2+1, y2+o+1, conf.merge_color.id, conf.merge_color.id);
1790 }
1791 else if(dot && conf.merge_arrows)
1792 {
1793 /*
1794 * Arrow patch from Haroon Rafique <haroon.rafique@utoronto.ca>
1795 * Slightly adapted to be more configurable.
1796 */
1797 int sx, sy; /* start point coordinates */
1798 int ex, ey; /* end point coordinates */
1799 double theta;
1800 double u1, v1, u2, v2;
1801 gdPoint p[3];
1802
1803 sx = x1; sy = y1;
1804 ex = x2; ey = y2;
1805 if(conf.left_right)
1806 {
1807 if(fr->branch == tr->branch)
1808 {
1809 int yy = (y1 < y2 ? y1 : y2) - 5;
1810 /* line from (x1,yy) to (x2,yy) */
1811 sy = ey = yy;
1812 }
1813 else
1814 {
1815 if(y1 > y2)
1816 {
1817 /* line from (x1,y1-3) to (x2,y2+3+1) */
1818 sy = y1-3;
1819 ey = y2+3+1;
1820 }
1821 else
1822 {
1823 /* line from (x1,y1+3+1) to (x2,y2-3) */
1824 sy = y1+3+1;
1825 ey = y2-3;
1826 }
1827 }
1828 }
1829 else
1830 {
1831 if(fr->branch == tr->branch)
1832 {
1833 int xx = (x1 < x2 ? x1 : x2) - 5;
1834 /* line from (xx,y1) to (xx,y2) */
1835 sx = ex = xx;
1836 }
1837 else
1838 {
1839 if(x1 > x2)
1840 {
1841 /* line from (x1-3,y1) to (x2+3,y2) */
1842 sx = x1-3;
1843 ex = x2+3;
1844 }
1845 else
1846 {
1847 /* line from (x1+3,y1) to (x2-3,y2) */
1848 sx = x1+3;
1849 ex = x2-3;
1850 }
1851 }
1852 }
1853 /*
1854 * inspiration for arrow code comes from arrows.c in the
1855 * graphviz package. Thank you, AT&T
1856 */
1857 /* theta in radians */
1858 theta = atan2((double)(sy-ey), (double)(sx-ex));
1859 u1 = (double)conf.arrow_length * cos(theta);
1860 v1 = (double)conf.arrow_length * sin(theta);
1861 u2 = (double)conf.arrow_width * cos(theta + M_PI/2.0);
1862 v2 = (double)conf.arrow_width * sin(theta + M_PI/2.0);
1863 /* points of polygon (triangle) */
1864 p[0].x = ROUND(ex + u1 - u2);
1865 p[0].y = ROUND(ey + v1 - v2);
1866 p[1].x = ex;
1867 p[1].y = ey;
1868 p[2].x = ROUND(ex + u1 + u2);
1869 p[2].y = ROUND(ey + v1 + v2);
1870 /* draw the polygon (triangle) */
1871 gdImageFilledPolygon(im, p, 3, conf.merge_color.id);
1872 }
1873 else
1874 {
1875 if(conf.left_right)
1876 {
1877 if(fr->branch == tr->branch)
1878 {
1879 int yy = (y1 < y2 ? y1 : y2) - 5;
1880 gdImageLine(im, x1, y1, x1, yy, conf.merge_color.id);
1881 gdImageLine(im, x2, y2, x2, yy, conf.merge_color.id);
1882 gdImageLine(im, x1, yy, x2, yy, conf.merge_color.id);
1883 }
1884 else
1885 {
1886 if(y1 > y2)
1887 {
1888 gdImageLine(im, x1, y1, x1, y1-3, conf.merge_color.id);
1889 gdImageLine(im, x2, y2+1, x2, y2+3+1, conf.merge_color.id);
1890 gdImageLine(im, x1, y1-3, x2, y2+3+1, conf.merge_color.id);
1891 }
1892 else
1893 {
1894 gdImageLine(im, x1, y1+1, x1, y1+3+1, conf.merge_color.id);
1895 gdImageLine(im, x2, y2, x2, y2-3, conf.merge_color.id);
1896 gdImageLine(im, x1, y1+3+1, x2, y2-3, conf.merge_color.id);
1897 }
1898 }
1899 }
1900 else
1901 {
1902 if(fr->branch == tr->branch)
1903 {
1904 int xx = (x1 < x2 ? x1 : x2) - 5;
1905 gdImageLine(im, xx, y1, x1, y1, conf.merge_color.id);
1906 gdImageLine(im, xx, y2, x2, y2, conf.merge_color.id);
1907 gdImageLine(im, xx, y1, xx, y2, conf.merge_color.id);
1908 }
1909 else
1910 {
1911 if(x1 > x2)
1912 {
1913 gdImageLine(im, x1, y1, x1-3, y1, conf.merge_color.id);
1914 gdImageLine(im, x2, y2, x2+3, y2, conf.merge_color.id);
1915 gdImageLine(im, x1-3, y1, x2+3, y2, conf.merge_color.id);
1916 }
1917 else
1918 {
1919 gdImageLine(im, x1, y1, x1+3, y1, conf.merge_color.id);
1920 gdImageLine(im, x2, y2, x2-3, y2, conf.merge_color.id);
1921 gdImageLine(im, x1+3, y1, x2-3, y2, conf.merge_color.id);
1922 }
1923 }
1924 }
1925 }
1926 }
1927 }
1928
1929 static void draw_messages(gdImagePtr im, int offset)
1930 {
1931 int i;
1932
1933 for(i = 0; i < nmsg_stack; i++)
1934 {
1935 draw_stringnl(im, msg_stack[i].msg, &conf.msg_font, conf.margin_left, offset, ALIGN_HL|ALIGN_VT, &conf.msg_color);
1936 offset += msg_stack[i].h;
1937 }
1938 }
1939
1940 static void alloc_color(gdImagePtr im, color_t *c)
1941 {
1942 c->id = gdImageColorAllocate(im, c->r, c->g, c->b);
1943 }
1944
1945 static gdImagePtr make_image(rcsfile_t *rcs)
1946 {
1947 gdImagePtr im;
1948 int i;
1949 char *cptr;
1950 int w, h;
1951 int subx = 0, suby = 0;
1952 int subw, subh;
1953 int msgh = 0;
1954
1955 if(subtree_branch)
1956 {
1957 subw = 0;
1958 subh = 0;
1959 if(subtree_rev)
1960 {
1961 for(i = 0; i < subtree_rev->nbranches; i++)
1962 calc_subtree_size(subtree_rev->branches[i], &subx, &suby, &subw, &subh);
1963 }
1964 else
1965 calc_subtree_size(subtree_branch, &subx, &suby, &subw, &subh);
1966 }
1967 else
1968 {
1969 subw = rcs->tw;
1970 subh = rcs->th;
1971 }
1972
1973 cptr = expand_string(conf.title, rcs, NULL, NULL, NULL, NULL);
1974 w = subw + conf.margin_left + conf.margin_right;
1975 h = subh + conf.margin_top + conf.margin_bottom;
1976 i = get_swidth(cptr, &conf.title_font);
1977 if(i > w)
1978 w = i;
1979
1980 if(!quiet && nmsg_stack)
1981 {
1982 int msgw = 0;
1983 for(i = 0; i < nmsg_stack; i++)
1984 {
1985 int ww = msg_stack[i].w = get_swidth(msg_stack[i].msg, &conf.msg_font);
1986 int hh = msg_stack[i].h = get_sheight(msg_stack[i].msg, &conf.msg_font);
1987 msgh += hh;
1988 h += hh;
1989 if(ww > msgw)
1990 msgw = ww;
1991 }
1992 if(msgw > w)
1993 w = msgw;
1994 }
1995
1996 im = gdImageCreate(w, h);
1997 alloc_color(im, &conf.color_bg);
1998 alloc_color(im, &conf.tag_color);
1999 alloc_color(im, &conf.rev_color);
2000 alloc_color(im, &conf.rev_bgcolor);
2001 alloc_color(im, &conf.rev_text_color);
2002 alloc_color(im, &conf.branch_color);
2003 alloc_color(im, &conf.branch_tag_color);
2004 alloc_color(im, &conf.branch_bgcolor);
2005 alloc_color(im, &conf.title_color);
2006 alloc_color(im, &conf.merge_color);
2007 alloc_color(im, &conf.msg_color);
2008 alloc_color(im, &black_color);
2009 alloc_color(im, &white_color);
2010
2011 if(conf.transparent_bg)
2012 gdImageColorTransparent(im, conf.color_bg.id);
2013
2014 if(!conf.merge_front)
2015 draw_merges(im, rcs, 0);
2016
2017 for(i = 0; i < rcs->nbranches; i++)
2018 {
2019 if(!rcs->branches[i]->folded && !(subtree_branch && !rcs->branches[i]->subtree_draw))
2020 draw_branch(im, rcs->branches[i]);
2021 }
2022
2023 draw_merges(im, rcs, 1); /* The dots of the merge dest */
2024
2025 for(i = 0; i < rcs->nbranches; i++)
2026 {
2027 if(rcs->branches[i]->branchpoint)
2028 draw_connector(im, rcs->branches[i]);
2029 }
2030
2031 /* Clear the margins if we have a partial tree */
2032 if(subtree_branch)
2033 {
2034 gdImageFilledRectangle(im, 0, 0, w-1, conf.margin_top-1, conf.color_bg.id);
2035 gdImageFilledRectangle(im, 0, 0, conf.margin_left-1, h-1, conf.color_bg.id);
2036 gdImageFilledRectangle(im, 0, h-conf.margin_bottom, w-1, h-1, conf.color_bg.id);
2037 gdImageFilledRectangle(im, w-conf.margin_right, 0, w-1, h-1, conf.color_bg.id);
2038 }
2039
2040 draw_stringnl(im, cptr, &conf.title_font, conf.title_x, conf.title_y, conf.title_align, &conf.title_color);
2041 xfree(cptr);
2042
2043 if(conf.merge_front)
2044 draw_merges(im, rcs, 0);
2045
2046 if(!quiet)
2047 draw_messages(im, h - conf.margin_bottom/2 - msgh);
2048
2049 return im;
2050 }
2051
2052 /*
2053 **************************************************************************
2054 * Layout routines
2055 *
2056 * Branch BBox:
2057 * left = center_x - total_width / 2 (cx-tw)/2
2058 * right = center_x + total_width / 2 (cx+tw)/2
2059 * top = y_pos (y)
2060 * bottom = y_pos + total_height (y+th)
2061 *
2062 * Margins of branches:
2063 *
2064 * . .
2065 * . .
2066 * +--------------+
2067 * ^
2068 * | branch_margin .
2069 * v .
2070 * ----------------+ .
2071 * | ^ |
2072 * | | branch_connect |
2073 * | v |
2074 *..-+ +t-----+------+ +------+------+
2075 * | l | | |
2076 * | <--> | branch bbox | <--> | branch bbox |
2077 * | | | r | | |
2078 *..-+ | +------------b+ | +-------------+
2079 * | ^ branch_margin
2080 * | | branch_margin
2081 * | v
2082 * | +-------------+
2083 * | . .
2084 * | . .
2085 * |
2086 * branch_margin
2087 *
2088 * FIXME: There are probable som +/-1 errors in the code...
2089 * (notably shadows are not calculated in the margins)
2090 **************************************************************************
2091 */
2092 static void move_branch(branch_t *b, int x, int y)
2093 {
2094 int i;
2095 b->cx += x;
2096 b->y += y;
2097 for(i = 0; i < b->nrevs; i++)
2098 {
2099 b->revs[i]->cx += x;
2100 b->revs[i]->y += y;
2101 }
2102 }
2103
2104 static void initial_reposition_branch(revision_t *r, int *x, int *w)
2105 {
2106 int i, j;
2107 for(j = 0; j < r->nbranches; j++)
2108 {
2109 branch_t *b = r->branches[j];
2110 *x += *w + conf.rev_minline + b->tw/2 - b->cx;
2111 *w = b->tw/2;
2112 move_branch(b, *x, r->y + r->h/2 + conf.branch_connect);
2113 *x = b->cx;
2114 /* Recurse to move branches of branched revisions */
2115 for(i = b->nrevs-1; i >= 0; i--)
2116 {
2117 initial_reposition_branch(b->revs[i], x, w);
2118 }
2119 }
2120 }
2121
2122 static void initial_reposition_branch_lr(revision_t *r, int *y, int *h)
2123 {
2124 int i, j;
2125 for(j = 0; j < r->nbranches; j++)
2126 {
2127 branch_t *b = r->branches[j];
2128 *y += *h + conf.rev_minline + b->th/2 - b->y;
2129 *h = b->th/2;
2130 move_branch(b, r->cx + r->w/2 + conf.branch_connect, *y);
2131 *y = b->y;
2132 /* Recurse to move branches of branched revisions */
2133 for(i = b->nrevs-1; i >= 0; i--)
2134 {
2135 initial_reposition_branch_lr(b->revs[i], y, h);
2136 }
2137 }
2138 }
2139
2140 static void rect_union(int *x, int *y, int *w, int *h, branch_t *b)
2141 {
2142 int x1 = *x;
2143 int x2 = x1 + *w;
2144 int y1 = *y;
2145 int y2 = y1 + *h;
2146 int xx1;
2147 int xx2;
2148 int yy1;
2149 int yy2;
2150
2151 if(conf.left_right)
2152 {
2153 xx1 = b->cx;
2154 yy1 = b->y - b->th/2;
2155 }
2156 else
2157 {
2158 xx1 = b->cx - b->tw/2;
2159 yy1 = b->y;
2160 }
2161 xx2 = xx1 + b->tw;
2162 yy2 = yy1 + b->th;
2163
2164 x1 = MIN(x1, xx1);
2165 x2 = MAX(x2, xx2);
2166 y1 = MIN(y1, yy1);
2167 y2 = MAX(y2, yy2);
2168 *x = x1;
2169 *y = y1;
2170 *w = x2 - x1;
2171 *h = y2 - y1;
2172 }
2173
2174 static void calc_subtree_size(branch_t *b, int *x, int *y, int *w, int *h)
2175 {
2176 int i, j;
2177
2178 rect_union(x, y, w, h, b);
2179
2180 for(i = 0; i < b->nrevs; i++)
2181 {
2182 for(j = 0; j < b->revs[i]->nbranches; j++)
2183 calc_subtree_size(b->revs[i]->branches[j], x, y, w, h);
2184 }
2185 }
2186
2187 static int branch_intersects(int top, int bottom, int left, branch_t *b)
2188 {
2189 int br = b->cx + b->tw/2;
2190 int bt = b->y - conf.branch_connect - conf.branch_margin/2;
2191 int bb = b->y + b->th + conf.branch_margin/2;
2192 return !(bt > bottom || bb < top || br >= left);
2193 }
2194
2195 static int branch_intersects_lr(int left, int right, int top, branch_t *b)
2196 {
2197 int bt = b->y + b->th/2;
2198 int bl = b->cx - conf.branch_connect - conf.branch_margin/2;
2199 int br = b->cx + b->tw + conf.branch_margin/2;
2200 return !(bl > right || br < left || bt >= top);
2201 }
2202
2203 static int kern_branch(rcsfile_t *rcs, branch_t *b)
2204 {
2205 int left = b->cx - b->tw/2;
2206 int top = b->y - conf.branch_connect - conf.branch_margin/2;
2207 int bottom = b->y + b->th + conf.branch_margin/2;
2208 int i;
2209 int xpos = 0;
2210
2211 for(i = 0; i < rcs->nbranches; i++)
2212 {
2213 branch_t *bp = rcs->branches[i];
2214 if(bp == b)
2215 continue;
2216 if(branch_intersects(top, bottom, left, bp))
2217 {
2218 int m = bp->cx + bp->tw/2 + conf.branch_margin;
2219 if(m > xpos)
2220 xpos = m;
2221 }
2222 }
2223 if(xpos && (b->cx - b->tw/2) - xpos > 0)
2224 {
2225 move_branch(b, xpos - (b->cx - b->tw/2), 0);
2226 return 1;
2227 }
2228 return 0;
2229 }
2230
2231 static int kern_branch_lr(rcsfile_t *rcs, branch_t *b)
2232 {
2233 int top = b->y - b->th/2;
2234 int left = b->cx - conf.branch_connect - conf.branch_margin/2;
2235 int right = b->cx + b->tw + conf.branch_margin/2;
2236 int i;
2237 int ypos = 0;
2238
2239 for(i = 0; i < rcs->nbranches; i++)
2240 {
2241 branch_t *bp = rcs->branches[i];
2242 if(bp == b)
2243 continue;
2244 if(branch_intersects_lr(left, right, top, bp))
2245 {
2246 int m = bp->y + bp->th/2 + conf.branch_margin;
2247 if(m > ypos)
2248 ypos = m;
2249 }
2250 }
2251 if(ypos && (b->y - b->th/2) - ypos > 0)
2252 {
2253 move_branch(b, 0, ypos - (b->y - b->th/2));
2254 return 1;
2255 }
2256 return 0;
2257 }
2258
2259 static int kern_tree(rcsfile_t *rcs)
2260 {
2261 int i;
2262 int moved;
2263 int safeguard;
2264 int totalmoved = 0;
2265 for(moved = 1, safeguard = LOOPSAFEGUARD; moved && safeguard; safeguard--)
2266 {
2267 moved = 0;
2268 for(i = 1; i < rcs->nbranches; i++)
2269 {
2270 if(conf.left_right)
2271 moved += kern_branch_lr(rcs, rcs->branches[i]);
2272 else
2273 moved += kern_branch(rcs, rcs->branches[i]);
2274 }
2275 totalmoved += moved;
2276 #ifdef DEBUG
2277 fprintf(stderr, "kern_tree: moved=%d\n", moved);
2278 #endif
2279 }
2280 if(!safeguard)
2281 stack_msg(MSG_WARN, "kern_tree: safeguard terminated possible infinite loop; please report.");
2282 return totalmoved;
2283 }
2284
2285 static int index_of_revision(revision_t *r)
2286 {
2287 branch_t *b = r->branch;
2288 int i;
2289 for(i = 0; i < b->nrevs; i++)
2290 {
2291 if(r == b->revs[i])
2292 return i;
2293 }
2294 stack_msg(MSG_ERR, "index_of_revision: Cannot find revision in branch\n");
2295 return 0;
2296 }
2297
2298 static void branch_bbox(branch_t *br, int *l, int *r, int *t, int *b)
2299 {
2300 if(l) *l = br->cx - br->tw/2;
2301 if(r) *r = br->cx + br->tw/2;
2302 if(t) *t = br->y;
2303 if(b) *b = br->y + br->th + ((conf.branch_dupbox && br->nrevs) ? conf.rev_minline + br->h : 0);
2304 }
2305
2306 static void branch_ext_bbox(branch_t *br, int *l, int *r, int *t, int *b)
2307 {
2308 int extra = conf.branch_margin & 1; /* Correct +/-1 error on div 2 */
2309 branch_bbox(br, l, r, t, b);
2310 if(l) *l -= conf.branch_margin/2;
2311 if(r) *r += conf.branch_margin/2 + extra;
2312 if(t) *t -= conf.branch_connect + conf.branch_margin/2;
2313 if(b) *b += conf.branch_margin/2 + extra;
2314 }
2315
2316 static int branch_distance(branch_t *br1, branch_t *br2)
2317 {
2318 int l1, r1, t1, b1;
2319 int l2, r2, t2, b2;
2320 assert(br1 != NULL);
2321 assert(br2 != NULL);
2322 branch_bbox(br1, &l1, &r1, NULL, NULL);
2323 branch_bbox(br2, &l2, &r2, NULL, NULL);
2324 branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
2325 branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
2326 /* Return:
2327 * - 0 if branches have no horizontal overlap
2328 * - positive if b1 is left of b2
2329 * - negative if b2 is left of b1
2330 */
2331 if((t1 > t2 && t1 < b2) || (b1 > t2 && b1 < b2))
2332 return l1 < l2 ? l2 - r1 : -(l1 - r2);
2333 else
2334 return 0;
2335 }
2336
2337 static int space_needed(branch_t *br1, branch_t *br2)
2338 {
2339 int t1, b1;
2340 int t2, b2;
2341 assert(br1 != NULL);
2342 assert(br2 != NULL);
2343 assert(br1->cx < br2->cx); /* br1 must be left of br2 */
2344 branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
2345 branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
2346 /* Return:
2347 * - positive if top br1 is located lower than br2
2348 * - negatve is top br2 is located lower than br1
2349 */
2350 if(t1 > t2)
2351 return -(t1 - b2);
2352 else
2353 return t2 - b1;
2354 }
2355
2356 static void move_yr_branch(branch_t *b, int dy)
2357 {
2358 int i, j;
2359 #ifdef DEBUG
2360 /* fprintf(stderr, "move_yr_branch: b=%s, dy=%d\n", b->branch->branch, dy);*/
2361 #endif
2362 b->y += dy;
2363 for(i = 0; i < b->nrevs; i++)
2364 {
2365 b->revs[i]->y += dy;
2366 for(j = 0; j < b->revs[i]->nbranches; j++)
2367 {
2368 #ifdef DEBUG
2369 /* fprintf(stderr, ".");*/
2370 #endif
2371 move_yr_branch(b->revs[i]->branches[j], dy);
2372 }
2373 }
2374 }
2375
2376 static void move_trunk(revision_t *r, int dy)
2377 {
2378 int i, j;
2379 branch_t *b = r->branch;
2380 b->th += dy;
2381 for(i = index_of_revision(r); i < b->nrevs; i++)
2382 {
2383 #ifdef DEBUG
2384 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);
2385 #endif
2386 b->revs[i]->y += dy;
2387 for(j = 0; j < b->revs[i]->nbranches; j++)
2388 {
2389 move_yr_branch(b->revs[i]->branches[j], dy);
2390 }
2391 }
2392 }
2393
2394 static int space_below(rcsfile_t *rcs, revision_t *r)
2395 {
2396 int i, j;
2397 int bl, br, bb;
2398 int space = INT_MAX;
2399 branch_t *b = r->branch;
2400 branch_t *minb = NULL;
2401
2402 branch_ext_bbox(b, &bl, &br, NULL, &bb);
2403 for(i = 0; i < rcs->nbranches; i++)
2404 {
2405 int tbl, tbr, tbt;
2406 branch_t *tb = rcs->branches[i];
2407 branch_ext_bbox(tb, &tbl, &tbr, &tbt, NULL);
2408 if(tb == b)
2409 continue;
2410 if(tbt > bb) /* Must be below our branch */
2411 {
2412 if(tb->branchpoint) /* Take account for the horiz connector */
2413 tbl = tb->branchpoint->cx + tb->branchpoint->branch->tw/2;
2414 if((bl >= tbl && bl <= tbr) || (br <= tbr && br >= tbl))
2415 {
2416 int s = tbt - bb - conf.branch_connect;
2417 if(s < space)
2418 {
2419 space = s;
2420 minb = tb;
2421 }
2422 }
2423 }
2424 }
2425 if(b->branchpoint)
2426 {
2427 for(i = index_of_revision(r); i < b->nrevs; i++)
2428 {
2429 for(j = 0; j < b->revs[i]->nbranches; j++)
2430 {
2431 int s = space_below(rcs, b->revs[i]->branches[j]->revs[0]);
2432 if(s < space)
2433 space = s;
2434 }
2435 }
2436 }
2437 #ifdef DEBUG
2438 fprintf(stderr, "space_below: from %s have %d to %s\n", b->branch->branch, space, minb ? minb->branch->branch : "<recursed>");
2439 #endif
2440 return space;
2441 }
2442
2443 static int space_available(rcsfile_t *rcs, branch_t *colbr, branch_t *tagbr, int *nl, revision_t **bpcommon)
2444 {
2445 int i;
2446 int space = 0;
2447 int nlinks = 0;
2448 revision_t *r;
2449 branch_t *b;
2450 branch_t *ancestor;
2451 revision_t *branchpoint;
2452
2453 if(!tagbr->branchpoint || !colbr->branchpoint)
2454 {
2455 stack_msg(MSG_WARN, "space_available: Trying to stretch the top?");
2456 return 0;
2457 }
2458
2459 r = colbr->branchpoint;
2460 b = r->branch;
2461 branchpoint = tagbr->branchpoint;
2462 ancestor = branchpoint->branch;
2463 assert(b != NULL);
2464 assert(ancestor != NULL);
2465
2466 while(1)
2467 {
2468 int s;
2469 int rtag = b == ancestor ? index_of_revision(branchpoint)+1 : 0;
2470 for(i = index_of_revision(r); i >= rtag; i--)
2471 {
2472 if(i > 0)
2473 s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
2474 else
2475 s = b->revs[i]->y - (b->y + b->h);
2476 if(s < conf.rev_maxline)
2477 {
2478 space += conf.rev_maxline - s;
2479 nlinks++;
2480 }
2481 }
2482 s = space_below(rcs, r);
2483 if(s < space)
2484 space = s;
2485 #ifdef DEBUG
2486 if(space < 0)
2487 return -1;
2488 #endif
2489 if(b == ancestor)
2490 break;
2491 r = b->branchpoint;
2492 if(!r)
2493 {
2494 /* Not a common ancestor */
2495 r = colbr->branchpoint;
2496 b = r->branch;
2497 branchpoint = ancestor->branchpoint;
2498 if(!branchpoint)
2499 {
2500 stack_msg(MSG_WARN, "space_available: No common ancestor?");
2501 return 0;
2502 }
2503 ancestor = branchpoint->branch;
2504 assert(ancestor != NULL);
2505 nlinks = 0;
2506 space = 0;
2507 continue; /* Restart with a new ancestor */
2508 }
2509 b = r->branch;
2510 }
2511 if(nl)
2512 *nl = nlinks; /* Return the number of links that can stretch */
2513 if(bpcommon)
2514 *bpcommon = branchpoint; /* Return the ancestral branchpoint on the common branch */
2515 return space;
2516 }
2517
2518 static int stretch_branches(rcsfile_t *rcs, branch_t *br1, branch_t *br2, int totalstretch)
2519 {
2520 revision_t *r;
2521 revision_t *bpcommon = NULL;
2522 branch_t *ancestor = NULL;
2523 branch_t *b;
2524 int i;
2525 int space;
2526 int nlinks;
2527 int dy;
2528 int rest;
2529
2530 space = space_available(rcs, br1, br2, &nlinks, &bpcommon);
2531 if(bpcommon)
2532 ancestor = bpcommon->branch;
2533
2534 #ifdef DEBUG
2535 if(space == -1)
2536 return 0;
2537 fprintf(stderr, "stretch_branches: space available %d over %d links common %s\n", space, nlinks, ancestor->branch->branch);
2538 #endif
2539 if(space < totalstretch)
2540 return 0;
2541
2542 dy = totalstretch / nlinks;
2543 rest = totalstretch - dy * nlinks;
2544
2545 r = br1->branchpoint;
2546 b = r->branch;
2547 while(1)
2548 {
2549 int rtag = b == ancestor ? index_of_revision(bpcommon)+1 : 0;
2550 for(i = index_of_revision(r); i >= rtag; i--)
2551 {
2552 int s, q;
2553 if(i > 0)
2554 s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
2555 else
2556 s = b->revs[i]->y - (b->y + b->h);
2557 q = conf.rev_maxline - s;
2558 if(q > 0)
2559 {
2560 int d = rest ? rest/nlinks+1 : 0;
2561 if(q >= dy+d)
2562 {
2563 move_trunk(b->revs[i], dy+d);
2564 }
2565 else
2566 {
2567 move_trunk(b->revs[i], q);
2568 rest += dy+d - q;
2569 }
2570 rest -= d;
2571 nlinks--;
2572 }
2573 }
2574 if(b == ancestor)
2575 break;
2576 r = b->branchpoint;
2577 assert(r != NULL); /* else 'space_available' wouldn't have returned positively */
2578 b = r->branch;
2579 }
2580 return 1;
2581 }
2582
2583 static branch_t *find_collision_branch(rcsfile_t *rcs, branch_t *b)
2584 {
2585 int i;
2586 int dist = INT_MAX;
2587 branch_t *col = NULL;
2588
2589 for(i = 0; i < rcs->nbranches; i++)
2590 {
2591 int t = branch_distance(rcs->branches[i], b);
2592 if(t > 0 && t < dist)
2593 {
2594 dist = t;
2595 col = rcs->branches[i];
2596 }
2597 }
2598 return col;
2599 }
2600
2601 static void auto_stretch(rcsfile_t *rcs)
2602 {
2603 int i;
2604 int safeguard;
2605
2606 for(i = 0, safeguard = LOOPSAFEGUARD; i < rcs->nbranches && safeguard; i++)
2607 {
2608 int bl, pr;
2609 branch_t *b = rcs->branches[i];
2610 if(!b->branchpoint)
2611 continue;
2612 branch_bbox(b, &bl, NULL, NULL, NULL);
2613 branch_bbox(b->branchpoint->branch, NULL, &pr, NULL, NULL);
2614 if(bl - conf.branch_margin - pr > 0)
2615 {
2616 branch_t *col;
2617 int spaceneeded;
2618 /* There is a potential to move branch b further left.
2619 * All branches obstructing this one from moving further
2620 * left must be originating from revisions below
2621 * b->branchpoint until a common ancester.
2622 * So, we search all branches for a branch that lies left
2623 * of b and is closest to b. This is then the collission
2624 * branch that needs to be moved.
2625 */
2626 col = find_collision_branch(rcs, b);
2627 if(!col)
2628 continue;
2629 spaceneeded = space_needed(col, b);
2630 if(spaceneeded < 0)
2631 continue;
2632 #ifdef DEBUG
2633 fprintf(stderr, "auto_stretch: %s collides %s need %d\n", b->branch->branch, col->branch->branch, spaceneeded);
2634 #endif
2635 /* Trace the collision branch back to find the common ancester
2636 * of both col and b. All revisions encountered while traversing
2637 * backwards must be stretched, including all revisions on the
2638 * common ancester from where the branches sprout.
2639 */
2640 if(stretch_branches(rcs, col, b, spaceneeded))
2641 {
2642 if(kern_tree(rcs))
2643 {
2644 /* Restart the process because movement can
2645 * cause more movement.
2646 */
2647 i = 0 - 1; /* -1 for the i++ of the loop */
2648 safeguard--; /* Prevent infinite loop, just in case */
2649 }
2650 /*return;*/
2651 }
2652 }
2653 }
2654 if(!safeguard)
2655 stack_msg(MSG_ERR, "auto_stretch: safeguard terminated possible infinite loop; please report.");
2656 }
2657
2658 static void fold_branch(rcsfile_t *rcs, revision_t *r)
2659 {
2660 int i, j;
2661 branch_t *btag = NULL;
2662
2663 for(i = 0; i < r->nbranches; i++)
2664 {
2665 branch_t *b = r->branches[i];
2666 if(!b->nrevs && b->ntags < 2)
2667 {
2668 /* No commits in this branch and no duplicate tags */
2669 if(!btag)
2670 btag = b;
2671 else
2672 {
2673 /* We have consecutive empty branches, fold */
2674 b->folded = 1;
2675 b->folded_to = btag;
2676 for(j = 0; j < rcs->nbranches; j++)
2677 {
2678 if(b == rcs->branches[j])
2679 {
2680 /* Zap the branch from the admin */
2681 memmove(&rcs->branches[j],
2682 &rcs->branches[j+1],
2683 (rcs->nbranches - j - 1)*sizeof(rcs->branches[0]));
2684 rcs->nbranches--;
2685 break;
2686 }
2687
2688 }
2689 memmove(&r->branches[i], &r->branches[i+1], (r->nbranches - i - 1)*sizeof(r->branches[0]));
2690 r->nbranches--;
2691 i--; /* We have one less now */
2692
2693 /* Add to the fold-list */
2694 btag->folds = xrealloc(btag->folds, (btag->nfolds+1) * sizeof(btag->folds[0]));
2695 btag->folds[btag->nfolds] = b;
2696 btag->nfolds++;
2697 }
2698 }
2699 else
2700 {
2701 if(!conf.branch_foldall)
2702 btag = NULL; /* Start a new box */
2703 /* Recursively fold sub-branches */
2704 for(j = 0; j < b->nrevs; j++)
2705 fold_branch(rcs, b->revs[j]);
2706 }
2707 }
2708 }
2709
2710 static void mark_subtree(branch_t *b)
2711 {
2712 int i, j;
2713 b->subtree_draw = 1;
2714 for(i = 0; i < b->nrevs; i++)
2715 {
2716 for(j = 0; j < b->revs[i]->nbranches; j++)
2717 mark_subtree(b->revs[i]->branches[j]);
2718 }
2719 }
2720
2721 static void make_layout(rcsfile_t *rcs)
2722 {
2723 int i, j;
2724 int x, y;
2725 int w, h;
2726 int w2;
2727
2728 /* Remove all unwanted revisions */
2729 if(conf.strip_untagged)
2730 {
2731 int fr = conf.strip_first_rev ? 0 : 1;
2732 for(i = 0; i < rcs->nbranches; i++)
2733 {
2734 branch_t *bp = rcs->branches[i];
2735 for(j = fr; j < bp->nrevs-1; j++)
2736 {
2737 if(!bp->revs[j]->ntags && !bp->revs[j]->nbranches)
2738 {
2739 memmove(&bp->revs[j], &bp->revs[j+1], (bp->nrevs-j-1) * sizeof(bp->revs[0]));
2740 bp->nrevs--;
2741 bp->revs[j]->stripped = 1;
2742 j--;
2743 }
2744 }
2745 }
2746 }
2747
2748 /* Find the sub-tree(s) we want to see */
2749 if(conf.branch_subtree && conf.branch_subtree[0])
2750 {
2751 branch_t **b;
2752 revision_t **r;
2753 rev_t rev;
2754 int k;
2755 char *tag = conf.branch_subtree;
2756
2757 /* First translate any symbolic tag to a real branch/revision number */
2758 if(rcs->tags)
2759 {
2760 for(k = 0; k < rcs->tags->ntags; k++)
2761 {
2762 if(!strcmp(conf.branch_subtree, rcs->tags->tags[k]->tag))
2763 {
2764 if(rcs->tags->tags[k]->rev->isbranch)
2765 tag = rcs->tags->tags[k]->rev->branch;
2766 else
2767 tag = rcs->tags->tags[k]->rev->rev;
2768 break;
2769 }
2770 }
2771 }
2772
2773 /* Find the corresponding branch */
2774 rev.branch = tag;
2775 rev.rev = NULL;
2776 rev.isbranch = 1;
2777 b = bsearch(&rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
2778 if(b)
2779 {
2780 if((*b)->branchpoint)
2781 {
2782 subtree_branch = *b;
2783 for(k = 0; k < (*b)->branchpoint->nbranches; k++)
2784 mark_subtree((*b)->branchpoint->branches[k]);
2785 }
2786 /*
2787 * else -> we want everything.
2788 * This happens for the top level branch because it has no
2789 * branchpoint. We do not set the subtree_branch, which then
2790 * results in drawing the whole tree as if we did not select a
2791 * particular branch.
2792 */
2793 }
2794 else
2795 {
2796 /* Maybe it is a revision we want all subtrees from */
2797 rev.rev = tag;
2798 rev.branch = NULL;
2799 rev.isbranch = 0;
2800 r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
2801 if(r)
2802 {
2803 if((*r)->nbranches)
2804 {
2805 subtree_branch = (*r)->branches[0];
2806 subtree_rev = *r;
2807 for(k = 0; k < (*r)->nbranches; k++)
2808 mark_subtree((*r)->branches[k]);
2809 }
2810 /*
2811 * else -> we select everything.
2812 * This happens for the any revision that has no branches.
2813 * We do not set the subtree_branch, which then results in
2814 * drawing the whole tree as if we did not select a
2815 * particular revision's branches.
2816 */
2817 }
2818 }
2819 }
2820
2821 /* Fold all empty branches in one box on the same branchpoint */
2822 if(conf.branch_fold)
2823 {
2824 for(i = 0; i < rcs->branches[0]->nrevs; i++)
2825 {
2826 if(rcs->branches[0]->revs[i]->nbranches > 0)
2827 fold_branch(rcs, rcs->branches[0]->revs[i]);
2828 }
2829 }
2830
2831 /* Remove all unwanted tags */
2832 for(i = 0; i < rcs->nbranches; i++)
2833 {
2834 branch_t *bp = rcs->branches[i];
2835 for(j = 0; j < bp->nrevs; j++)
2836 {
2837 revision_t *r = bp->revs[j];
2838 int k;
2839 for(k = 0; k < r->ntags; k++)
2840 {
2841 if(r->tags[k]->ignore > 0)
2842 {
2843 memmove(&r->tags[k], &r->tags[k+1], (r->ntags-k-1) * sizeof(r->tags[0]));
2844 r->ntags--;
2845 k--;
2846 }
2847 }
2848 }
2849 }
2850
2851 /* Calculate the box-sizes of the revisions */
2852 for(i = 0; i < rcs->nsrev; i++)
2853 {
2854 revision_t *rp;
2855 int w;
2856 int h;
2857 rp = rcs->srev[i];
2858 rp->revtext = expand_string(conf.rev_text, rcs, rp, rp->rev, NULL, rp->ntags ? rp->tags[0] : NULL);
2859 w = get_swidth(rp->revtext, &conf.rev_text_font);
2860 j = get_swidth(rp->rev->rev, &conf.rev_font);
2861 if(j > w)
2862 w = j;
2863 h = get_sheight(rp->revtext, &conf.rev_text_font) + get_sheight(rp->rev->rev, &conf.rev_font);
2864 for(j = 0; j < rp->ntags; j++)
2865 {
2866 int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);
2867 int th;
2868 if(ww > w) w = ww;
2869 th = get_sheight(rp->tags[j]->tag, &conf.tag_font) + conf.rev_separator;
2870 rp->tags[j]->yofs = h + th/2 + conf.rev_tspace;
2871 h += th;
2872 }
2873 rp->w = w + conf.rev_lspace + conf.rev_rspace;
2874 rp->h = h + conf.rev_tspace + conf.rev_bspace;
2875 }
2876
2877 /* Calculate the box-sizes of the branches */
2878 for(i = 0; i < rcs->nbranches; i++)
2879 {
2880 branch_t *bp = rcs->branches[i];
2881 int w;
2882 int h;
2883 if(!bp->nfolds)
2884 {
2885 w = get_swidth(bp->branch->branch, &conf.branch_font);
2886 h = get_sheight(bp->branch->branch, &conf.branch_font);
2887 for(j = 0; j < bp->ntags; j++)
2888 {
2889 int ww = get_swidth(bp->tags[j]->tag, &conf.branch_tag_font);
2890 if(ww > w) w = ww;
2891 h += get_sheight(bp->tags[j]->tag, &conf.branch_tag_font);
2892 }
2893 }
2894 else
2895 {
2896 int h1, h2;
2897 int w1, w2;
2898 int fw;
2899 w1 = get_swidth(bp->branch->branch, &conf.branch_font);
2900 w1 += get_swidth(" ", &conf.branch_font);
2901 w2 = get_swidth(bp->tags[0]->tag, &conf.branch_tag_font);
2902 fw = w1;
2903 w = w1 + w2;
2904 h1 = get_sheight(bp->branch->branch, &conf.branch_font);
2905 h2 = get_sheight(bp->tags[0]->tag, &conf.branch_tag_font);
2906 h = MAX(h1, h2);
2907 for(j = 0; j < bp->nfolds; j++)
2908 {
2909 w1 = get_swidth(bp->folds[j]->branch->branch, &conf.branch_font);
2910 w1 += get_swidth(" ", &conf.branch_font);
2911 w2 = get_swidth(bp->folds[j]->tags[0]->tag, &conf.branch_tag_font);
2912 if(w1 > fw)
2913 fw = w1;
2914 if(w1 + w2 > w)
2915 w = w1 + w2;
2916 h1 = get_sheight(bp->folds[j]->branch->branch, &conf.branch_font);
2917 h2 = get_sheight(bp->folds[j]->tags[0]->tag, &conf.branch_tag_font);
2918 h += MAX(h1, h2);
2919 }
2920 bp->fw = fw;
2921 }
2922 w += conf.branch_lspace + conf.branch_rspace;
2923 h += conf.branch_tspace + conf.branch_bspace;
2924 bp->w = w;
2925 bp->h = h;
2926 if(conf.left_right)
2927 {
2928 for(j = 0; j < bp->nrevs; j++)
2929 {
2930 if(bp->revs[j]->h > h)
2931 h = bp->revs[j]->h;
2932 w += bp->revs[j]->w + conf.rev_minline;
2933 }
2934 if(conf.branch_dupbox && bp->nrevs)
2935 w += bp->w + conf.rev_minline;
2936 }
2937 else
2938 {
2939 for(j = 0; j < bp->nrevs; j++)
2940 {
2941 if(bp->revs[j]->w > w)
2942 w = bp->revs[j]->w;
2943 h += bp->revs[j]->h + conf.rev_minline;
2944 }
2945 if(conf.branch_dupbox && bp->nrevs)
2946 h += bp->h + conf.rev_minline;
2947 }
2948 bp->th = h;
2949 bp->tw = w;
2950 }
2951
2952 /* Calculate the relative positions of revs in a branch */
2953 if(conf.left_right)
2954 {
2955 for(i = 0; i < rcs->nbranches; i++)
2956 {
2957 branch_t *b = rcs->branches[i];
2958 y = b->th/2;
2959 x = b->w;
2960 b->y = y;
2961 b->cx = 0;
2962 for(j = 0; j < b->nrevs; j++)
2963 {
2964 x += conf.rev_minline;
2965 b->revs[j]->y = y;
2966 b->revs[j]->cx = x;
2967 x += b->revs[j]->w;
2968 }
2969 }
2970 }
2971 else
2972 {
2973 for(i = 0; i < rcs->nbranches; i++)
2974 {
2975 branch_t *b = rcs->branches[i];
2976 x = b->tw/2;
2977 y = b->h;
2978 b->cx = x;
2979 b->y = 0;
2980 for(j = 0; j < b->nrevs; j++)
2981 {
2982 y += conf.rev_minline;
2983 b->revs[j]->cx = x;
2984 b->revs[j]->y = y;
2985 y += b->revs[j]->h;
2986 }
2987 }
2988 }
2989
2990 /* Initially reposition the branches from bottom to top progressively right */
2991 if(conf.left_right)
2992 {
2993 x = rcs->branches[0]->y;
2994 w2 = rcs->branches[0]->th / 2;
2995 for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
2996 {
2997 initial_reposition_branch_lr(rcs->branches[0]->revs[i], &x, &w2);
2998 }
2999 }
3000 else
3001 {
3002 x = rcs->branches[0]->cx;
3003 w2 = rcs->branches[0]->tw / 2;
3004 for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
3005 {
3006 initial_reposition_branch(rcs->branches[0]->revs[i], &x, &w2);
3007 }
3008 }
3009
3010 /* Initially move branches left if there is room */
3011 kern_tree(rcs);
3012
3013 /* Try to kern the branches more by expanding the inter-revision spacing */
3014 if(conf.auto_stretch && !conf.left_right)
3015 auto_stretch(rcs);
3016
3017 /* Calculate overall image size */
3018 if(conf.left_right)
3019 {
3020 x = rcs->branches[0]->cx;
3021 y = rcs->branches[0]->y - rcs->branches[0]->th/2;
3022 }
3023 else
3024 {
3025 x = rcs->branches[0]->cx - rcs->branches[0]->tw/2;
3026 y = rcs->branches[0]->y;
3027 }
3028 w = rcs->branches[0]->tw;
3029 h = rcs->branches[0]->th;
3030 for(i = 1; i < rcs->nbranches; i++)
3031 rect_union(&x, &y, &w, &h, rcs->branches[i]);
3032 rcs->tw = w;
3033 rcs->th = h;
3034
3035 /* Flip the entire tree */
3036 if(conf.upside_down)
3037 {
3038 if(conf.left_right)
3039 {
3040 x += rcs->tw;
3041 for(i = 0; i < rcs->nbranches; i++)
3042 {
3043 branch_t *b = rcs->branches[i];
3044 for(j = 0; j < b->nrevs; j++)
3045 {
3046 revision_t *r = b->revs[j];
3047 r->cx = x - r->cx - r->w;
3048 }
3049 b->cx = x - b->cx - b->w;
3050 }
3051 }
3052 else
3053 {
3054 y += rcs->th;
3055 for(i = 0; i < rcs->nbranches; i++)
3056 {
3057 branch_t *b = rcs->branches[i];
3058 for(j = 0; j < b->nrevs; j++)
3059 {
3060 revision_t *r = b->revs[j];
3061 r->y = y - r->y - r->h;
3062 }
3063 b->y = y - b->y - b->h;
3064 }
3065 }
3066 }
3067
3068 /* Relocate the lot if we only draw a sub-tree */
3069 if(subtree_branch)
3070 {
3071 int xx, yy;
3072
3073 if(subtree_branch->folded) /* Fix the reference if the branch got folded */
3074 subtree_branch = subtree_branch->folded_to;
3075
3076 xx = conf.left_right ? subtree_branch->cx : subtree_branch->cx - subtree_branch->tw/2;
3077 yy = conf.left_right ? subtree_branch->y - subtree_branch->th/2 : subtree_branch->y;
3078 if(subtree_branch != rcs->branches[0])
3079 {
3080 if(conf.left_right)
3081 xx -= conf.branch_connect;
3082 else
3083 yy -= conf.branch_connect;
3084 }
3085 for(i = 0; i < rcs->nbranches; i++)
3086 move_branch(rcs->branches[i], -xx, -yy);
3087 }
3088
3089 /* Move everything w.r.t. the top-left margin */
3090 for(i = 0; i < rcs->nbranches; i++)
3091 move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
3092 }
3093
3094 /*
3095 **************************************************************************
3096 * Imagemap functions
3097 **************************************************************************
3098 */
3099 static void map_merge_box(rcsfile_t *rcs, FILE *fp, revision_t *fr, revision_t *tr, gdImagePtr im, int x1, int y1, int x2, int y2)
3100 {
3101 char *href = expand_string(conf.map_merge_href, rcs, tr, tr->rev, fr->rev, NULL);
3102 char *alt = expand_string(conf.map_merge_alt, rcs, tr, tr->rev, fr->rev, NULL);
3103 const char *htp = conf.html_level == HTMLLEVEL_X ? " /" : "";
3104
3105 if(x1 > 0 && x2 > 0 && y1 > 0 && y2 > 0)
3106 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3107 href, x1, y1, x2, y2, alt, htp);
3108 xfree(alt);
3109 xfree(href);
3110
3111 if(im)
3112 {
3113 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3114 gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3115 gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3116 }
3117 }
3118
3119 static void map_merges(rcsfile_t *rcs, FILE *fp, gdImagePtr im)
3120 {
3121 int i;
3122 int tagh2 = get_sheight("Hg", &conf.tag_font) / 2;
3123 int bm = conf.branch_margin / 2;
3124
3125 for(i = 0; i < rcs->nmerges; i++)
3126 {
3127 revision_t *fr = rcs->merges[i].from->logrev;
3128 revision_t *tr = rcs->merges[i].to->logrev;
3129 int x1, x2, y1, y2;
3130 if(!fr || !tr || fr == tr)
3131 continue; /* This can happen with detached tags and self-references */
3132 if(conf.left_right)
3133 {
3134 if(fr->branch == tr->branch)
3135 {
3136 y1 = fr->y - fr->h/2;
3137 y2 = tr->y - tr->h/2;
3138 }
3139 else
3140 {
3141 if(fr->y < tr->y)
3142 {
3143 y1 = fr->y + fr->h/2;
3144 y2 = tr->y - tr->h/2;
3145 }
3146 else
3147 {
3148 y1 = fr->y - fr->h/2;
3149 y2 = tr->y + tr->h/2;
3150 }
3151 }
3152 x1 = fr->cx + fr->w/2;
3153 x2 = tr->cx + tr->w/2;
3154 }
3155 else
3156 {
3157 if(fr->branch == tr->branch)
3158 {
3159 x1 = fr->cx - fr->w/2;
3160 x2 = tr->cx - tr->w/2;
3161 }
3162 else
3163 {
3164 if(fr->cx < tr->cx)
3165 {
3166 x1 = fr->cx + fr->w/2;
3167 x2 = tr->cx - tr->w/2;
3168 }
3169 else
3170 {
3171 x1 = fr->cx - fr->w/2;
3172 x2 = tr->cx + tr->w/2;
3173 }
3174 }
3175 y1 = fr->y + rcs->merges[i].from->yofs;
3176 y2 = tr->y + rcs->merges[i].to->yofs;
3177 }
3178
3179 if(conf.left_right)
3180 {
3181 if(fr->branch == tr->branch)
3182 {
3183 map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-bm, x1+bm, y1);
3184 map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-bm, x2+bm, y2);
3185 }
3186 else
3187 {
3188 if(y1 > y2)
3189 {
3190 map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-bm, x1+bm, y1);
3191 map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2, x2+bm, y2+bm);
3192 }
3193 else
3194 {
3195 map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1, x1+bm, y1+bm);
3196 map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-bm, x2+bm, y2);
3197 }
3198 }
3199 }
3200 else
3201 {
3202 if(fr->branch == tr->branch)
3203 {
3204 map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-tagh2, x1, y1+tagh2);
3205 map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-tagh2, x2, y2+tagh2);
3206 }
3207 else
3208 {
3209 if(x1 > x2)
3210 {
3211 map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-tagh2, x1, y1+tagh2);
3212 map_merge_box(rcs, fp, fr, tr, im, x2, y2-tagh2, x2+bm, y2+tagh2);
3213 }
3214 else
3215 {
3216 map_merge_box(rcs, fp, fr, tr, im, x1, y1-tagh2, x1+bm, y1+tagh2);
3217 map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-tagh2, x2, y2+tagh2);
3218 }
3219 }
3220 }
3221 }
3222 }
3223
3224 static void make_imagemap(rcsfile_t *rcs, FILE *fp, gdImagePtr im)
3225 {
3226 int i, j;
3227 const char *htp = conf.html_level == HTMLLEVEL_X ? " /" : "";
3228
3229 switch(conf.html_level)
3230 {
3231 case HTMLLEVEL_4:
3232 fprintf(fp, "<map name=\"%s\" id=\"%s\">\n", conf.map_name, conf.map_name);
3233 break;
3234 case HTMLLEVEL_X:
3235 fprintf(fp, "<map id=\"%s\">\n", conf.map_name);
3236 break;
3237 default:
3238 fprintf(fp, "<map name=\"%s\">\n", conf.map_name);
3239 }
3240
3241 for(i = 0; i < rcs->nbranches; i++)
3242 {
3243 branch_t *b = rcs->branches[i];
3244 tag_t *tag = b->ntags ? b->tags[0] : NULL;
3245 char *bhref;
3246 char *balt;
3247 int x1;
3248 int x2;
3249 int y1;
3250 int y2;
3251
3252 if(subtree_branch && !b->subtree_draw)
3253 continue;
3254
3255 bhref = expand_string(conf.map_branch_href, rcs, NULL, b->branch, NULL, tag);
3256 balt = expand_string(conf.map_branch_alt, rcs, NULL, b->branch, NULL, tag);
3257
3258 if(!b->nfolds)
3259 {
3260 if(conf.left_right)
3261 {
3262 x1 = b->cx;
3263 y1 = b->y - b->h/2;
3264 x2 = b->cx + b->w;
3265 y2 = b->y + b->h/2;
3266 }
3267 else
3268 {
3269 x1 = b->cx - b->w/2;
3270 y1 = b->y;
3271 x2 = b->cx + b->w/2;
3272 y2 = b->y + b->h;
3273 }
3274 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3275 bhref, x1, y1, x2, y2, balt, htp);
3276 if(im)
3277 {
3278 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3279 gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3280 gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3281 }
3282 }
3283 else
3284 {
3285 int yy1, yy2, yy;
3286 if(conf.left_right)
3287 {
3288 x1 = b->cx + conf.branch_lspace;
3289 y1 = b->y - b->h/2 + conf.branch_tspace;
3290 }
3291 else
3292 {
3293 x1 = b->cx - b->w/2 + conf.branch_lspace;
3294 y1 = b->y + conf.branch_tspace;
3295 }
3296 x2 = x1 + b->w - conf.branch_rspace;
3297
3298 yy1 = get_sheight(b->branch->branch, &conf.branch_font);
3299 yy2 = get_sheight(b->tags[0]->tag, &conf.branch_tag_font);
3300 yy = MAX(yy1, yy2);
3301 y2 = y1 + yy;
3302 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3303 bhref, x1, y1, x2, y2, balt, htp);
3304
3305 y1 += yy;
3306 y2 += yy;
3307 for(j = 0; j < b->nfolds; j++)
3308 {
3309 branch_t *fb = b->folds[j];
3310 tag_t *t = fb->tags[0];
3311 xfree(bhref);
3312 xfree(balt);
3313 bhref = expand_string(conf.map_branch_href, rcs, NULL, fb->branch, NULL, t);
3314 balt = expand_string(conf.map_branch_alt, rcs, NULL, fb->branch, NULL, t);
3315 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3316 bhref, x1, y1, x2, y2, balt, htp);
3317 yy1 = get_sheight(fb->branch->branch, &conf.branch_font);
3318 yy2 = get_sheight(fb->tags[0]->tag, &conf.branch_tag_font);
3319 yy = MAX(yy1, yy2);
3320 y1 += yy;
3321 y2 += yy;
3322 }
3323 }
3324
3325 for(j = 0; j < b->nrevs; j++)
3326 {
3327 revision_t *r = b->revs[j];
3328 revision_t* r1;
3329 int xoff = 1;
3330 int yoff = 1;
3331 char *href;
3332 char *alt;
3333
3334 tag = r->ntags ? r->tags[0] : NULL;
3335 href = expand_string(conf.map_rev_href, rcs, r, r->rev, NULL, tag);
3336 alt = expand_string(conf.map_rev_alt, rcs, r, r->rev, NULL, tag);
3337 if(conf.left_right)
3338 {
3339 x1 = r->cx;
3340 y1 = r->y - r->h/2;
3341 x2 = r->cx + r->w;
3342 y2 = r->y + r->h/2;
3343 }
3344 else
3345 {
3346 x1 = r->cx - r->w/2;
3347 y1 = r->y;
3348 x2 = r->cx + r->w/2;
3349 y2 = r->y + r->h;
3350 }
3351 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3352 href, x1, y1, x2, y2, alt, htp);
3353 if(im)
3354 {
3355 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3356 gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3357 gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3358 }
3359 xfree(href);
3360 xfree(alt);
3361 if(j > 0 || b->branchpoint)
3362 {
3363 if(j > 0)
3364 {
3365 r1 = b->revs[j-1];
3366 if(conf.left_right)
3367 {
3368 yoff = MIN(r->h, r1->h)/4;
3369 x1 = conf.upside_down ? r1->cx : r1->cx + r1->w;
3370 }
3371 else
3372 {
3373 xoff = MIN(r->w, r1->w)/4;
3374 y1 = conf.upside_down ? r1->y : r1->y + r1->h;
3375 }
3376 }
3377 else
3378 {
3379 r1 = b->branchpoint;
3380 if(conf.left_right)
3381 {
3382 yoff = MIN(r->h, b->h)/4;
3383 x1 = conf.upside_down ? b->cx : b->cx + b->w;
3384 }
3385 else
3386 {
3387 xoff = MIN(r->w, b->w)/4;
3388 y1 = conf.upside_down ? b->y : b->y + b->h;
3389 }
3390 }
3391 if(conf.left_right)
3392 {
3393 y1 = r->y - yoff;
3394 y2 = r->y + yoff;
3395 x2 = conf.upside_down ? r->cx + r->w : r->cx;
3396 yoff = 0;
3397 }
3398 else
3399 {
3400 x1 = r->cx - xoff;
3401 x2 = r->cx + xoff;
3402 y2 = conf.upside_down ? r->y + r->h : r->y;
3403 xoff = 0;
3404 }
3405 if(x1 > x2)
3406 {
3407 int tt = x1;
3408 x1 = x2;
3409 x2 = tt;
3410 }
3411 if(y1 > y2)
3412 {
3413 int tt = y1;
3414 y1 = y2;
3415 y2 = tt;
3416 }
3417 href = expand_string(conf.map_diff_href, rcs, r, r->rev, r1->rev, tag);
3418 alt = expand_string(conf.map_diff_alt, rcs, r, r->rev, r1->rev, tag);
3419 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3420 href,
3421 x1+xoff, y1+yoff, x2-xoff, y2-yoff,
3422 alt, htp);
3423 if(im)
3424 {
3425 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3426 gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3427 gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3428 }
3429 xfree(href);
3430 xfree(alt);
3431 }
3432 }
3433 if(conf.branch_dupbox && b->nrevs)
3434 {
3435 if(conf.left_right)
3436 {
3437 x1 = conf.upside_down ? b->cx + b->w - b->tw : b->cx - b->w + b->tw;
3438 y1 = b->y - b->h/2;
3439 x2 = x1 + b->w;
3440 y2 = b->y + b->h/2;
3441 }
3442 else
3443 {
3444 x1 = b->cx - b->w/2;
3445 y1 = conf.upside_down ? b->y + b->h - b->th : b->y - b->h + b->th;
3446 x2 = b->cx + b->w/2;
3447 y2 = y1 + b->h;
3448 }
3449 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3450 bhref, x1, y1, x2, y2, balt, htp);
3451 if(im)
3452 {
3453 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3454 gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3455 gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3456 }
3457 }
3458 xfree(bhref);
3459 xfree(balt);
3460 }
3461
3462 map_merges(rcs, fp, im);
3463
3464 fprintf(fp, "</map>\n");
3465 }
3466
3467 /*
3468 **************************************************************************
3469 * Program entry
3470 **************************************************************************
3471 */
3472 static const char usage_str[] =
3473 "Usage: cvsgraph [options] <file>\n"
3474 " -b Add a branch box at both sides of the trunk (config value is negated)\n"
3475 " -c <file> Read alternative config from <file>\n"
3476 " -d <level> Enable debug mode at <level>\n"
3477 " -h This message\n"
3478 " -i Generate an imagemap instead of image\n"
3479 " -I <file> Also write the imagemap to <file>\n"
3480 " -k Auto stretch the tree (config value is negated)\n"
3481 " -M <name> Use <name> as imagemap name\n"
3482 " -m <mod> Use <mod> as cvs module\n"
3483 " -o <file> Output to <file>\n"
3484 " -O <opt=val> Set option opt to value val\n"
3485 " -q Be quiet (i.e. no warnings)\n"
3486 " -r <path> Use <path> as cvsroot path\n"
3487 " -s Strip untagged revisions (config value is negated)\n"
3488 " -S Also strip the first revision (config value is negated)\n"
3489 " -u Upside down image (mirror vertically; config value is negated)\n"
3490 " -V Print version and exit\n"
3491 " -x [34x] Specify level of HTML 3.2 (default), 4.0 or XHTML\n"
3492 " -[0-9] <txt> Use <txt> for expansion\n"
3493 ;
3494
3495 #define VERSION_STR "1.5.0"
3496 #define NOTICE_STR "Copyright (c) 2001,2002,2003,2004 B.Stultiens"
3497
3498 static void append_slash(char **path)
3499 {
3500 int l;
3501 assert(path != NULL);
3502 assert(*path != NULL);
3503 l = strlen(*path);
3504 if(!l || (*path)[l-1] == '/')
3505 return;
3506 *path = xrealloc(*path, l+2);
3507 strcat(*path, "/");
3508 }
3509
3510 int main(int argc, char *argv[])
3511 {
3512 extern int rcs_flex_debug;
3513 extern int rcsdebug;
3514 int optc;
3515 char *confpath = NULL;
3516 char *outfile = NULL;
3517 char *cvsroot = NULL;
3518 char *cvsmodule = NULL;
3519 int imagemap = 0;
3520 int upsidedown = 0;
3521 int bdupbox = 0;
3522 int stripuntag = 0;
3523 int stripfirst = 0;
3524 int autostretch = 0;
3525 int htmllevel = 0;
3526 char *imgmapname = NULL;
3527 char *imgmapfile = NULL;
3528 int lose = 0;
3529 FILE *fp;
3530 char *rcsfilename;
3531 rcsfile_t *rcs;
3532 gdImagePtr im;
3533
3534 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)
3535 {
3536 switch(optc)
3537 {
3538 case 'b':
3539 bdupbox = 1;
3540 break;
3541 case 'c':
3542 confpath = xstrdup(optarg);
3543 break;
3544 case 'd':
3545 debuglevel = strtol(optarg, NULL, 0);
3546 break;
3547 case 'I':
3548 imgmapfile = xstrdup(optarg);
3549 break;
3550 case 'i':
3551 imagemap = 1;
3552 break;
3553 case 'k':
3554 autostretch = 1;
3555 break;
3556 case 'M':
3557 imgmapname = xstrdup(optarg);
3558 break;
3559 case 'm':
3560 cvsmodule = xstrdup(optarg);
3561 break;
3562 case 'O':
3563 stack_option(optarg);
3564 break;
3565 case 'o':
3566 outfile = xstrdup(optarg);
3567 break;
3568 case 'q':
3569 quiet = 1;
3570 break;
3571 case 'r':
3572 cvsroot = xstrdup(optarg);
3573 break;
3574 case 'S':
3575 stripfirst = 1;
3576 break;
3577 case 's':
3578 stripuntag = 1;
3579 break;
3580 case 'u':
3581 upsidedown = 1;
3582 break;
3583 case 'V':
3584 fprintf(stdout, "cvsgraph v%s, %s\n", VERSION_STR, NOTICE_STR);
3585 return 0;
3586 case 'x':
3587 switch(optarg[0])
3588 {
3589 case '3':
3590 htmllevel = HTMLLEVEL_3;
3591 break;
3592 case '4':
3593 htmllevel = HTMLLEVEL_4;
3594 break;
3595 case 'x':
3596 htmllevel = HTMLLEVEL_X;
3597 break;
3598 default:
3599 fprintf(stderr, "Invalid HTML level in -x\n");
3600 lose++;
3601 }
3602 break;
3603 case 'h':
3604 fprintf(stdout, "%s", usage_str);
3605 return 0;
3606 default:
3607 if(isdigit(optc))
3608 {
3609 conf.expand[optc-'0'] = xstrdup(optarg);
3610 }
3611 else
3612 lose++;
3613 }
3614 }
3615
3616 if(lose)
3617 {
3618 fprintf(stderr, "%s", usage_str);
3619 return 1;
3620 }
3621
3622 if(debuglevel)
3623 {
3624 setvbuf(stdout, NULL, 0, _IONBF);
3625 setvbuf(stderr, NULL, 0, _IONBF);
3626 }
3627 rcs_flex_debug = (debuglevel & DEBUG_RCS_LEX) != 0;
3628 rcsdebug = (debuglevel & DEBUG_RCS_YACC) != 0;
3629
3630 /* Set defaults */
3631 conf.tag_font.gdfont = gdFontTiny;
3632 conf.rev_font.gdfont = gdFontTiny;
3633 conf.branch_font.gdfont = gdFontTiny;
3634 conf.branch_tag_font.gdfont = gdFontTiny;
3635 conf.title_font.gdfont = gdFontTiny;
3636 conf.rev_text_font.gdfont = gdFontTiny;
3637 conf.msg_font.gdfont = gdFontTiny;
3638
3639 conf.anti_alias = 1;
3640 conf.thick_lines = 1;
3641 conf.branch_fold = 1;
3642
3643 conf.cvsroot = xstrdup("");
3644 conf.cvsmodule = xstrdup("");
3645 conf.date_format = xstrdup("%d-%b-%Y %H:%M:%S");
3646 conf.title = xstrdup("");
3647 conf.map_name = xstrdup("CvsGraphImageMap");
3648 conf.map_branch_href = xstrdup("href=\"unset: conf.map_branch_href\"");
3649 conf.map_branch_alt = xstrdup("alt=\"%B\"");
3650 conf.map_rev_href = xstrdup("href=\"unset: conf.map_rev_href\"");
3651 conf.map_rev_alt = xstrdup("alt=\"%R\"");
3652 conf.map_diff_href = xstrdup("href=\"unset: conf.map_diff_href\"");
3653 conf.map_diff_alt = xstrdup("alt=\"%P &lt;-&gt; %R\"");
3654 conf.map_merge_href = xstrdup("href=\"unset: conf.map_merge_href\"");
3655 conf.map_merge_alt = xstrdup("alt=\"%P &lt;-&gt; %R\"");
3656 conf.rev_text = xstrdup("%d");
3657 conf.branch_subtree = xstrdup("");
3658 conf.tag_ignore = xstrdup("");
3659 conf.merge_from = xstrdup("");
3660 conf.merge_to = xstrdup("");
3661 conf.merge_arrows = 1;
3662 conf.arrow_width = ARROW_WIDTH;
3663 conf.arrow_length = ARROW_LENGTH;
3664
3665 conf.color_bg = white_color;
3666 conf.branch_bgcolor = white_color;
3667 conf.branch_color = black_color;
3668 conf.branch_tag_color = black_color;
3669 conf.rev_color = black_color;
3670 conf.rev_bgcolor = white_color;
3671 conf.merge_color = black_color;
3672 conf.tag_color = black_color;
3673 conf.title_color = black_color;
3674 conf.rev_text_color = black_color;
3675 conf.msg_color = black_color;
3676
3677 conf.image_quality = 100;
3678 conf.rev_maxline = -1; /* Checked later to set to default */
3679
3680 read_config(confpath);
3681
3682 if(conf.rev_maxline == -1) conf.rev_maxline = 5 * conf.rev_minline;
3683
3684 /* Set overrides */
3685 if(cvsroot) conf.cvsroot = cvsroot;
3686 if(cvsmodule) conf.cvsmodule = cvsmodule;
3687 if(imgmapname) conf.map_name = imgmapname;
3688 if(upsidedown) conf.upside_down = !conf.upside_down;
3689 if(bdupbox) conf.branch_dupbox = !conf.branch_dupbox;
3690 if(stripuntag) conf.strip_untagged = !conf.strip_untagged;
3691 if(stripfirst) conf.strip_first_rev = !conf.strip_first_rev;
3692 if(autostretch) conf.auto_stretch = !conf.auto_stretch;
3693 if(htmllevel) conf.html_level = htmllevel;
3694
3695 if(conf.rev_minline >= conf.rev_maxline)
3696 {
3697 if(conf.auto_stretch)
3698 stack_msg(MSG_WARN, "Auto stretch is only possible if rev_minline < rev_maxline");
3699 conf.auto_stretch = 0;
3700 }
3701
3702 if(conf.thick_lines < 1)
3703 conf.thick_lines = 1;
3704 if(conf.thick_lines > 11)
3705 conf.thick_lines = 11;
3706
3707 append_slash(&conf.cvsroot);
3708 append_slash(&conf.cvsmodule);
3709
3710 if(optind >= argc)
3711 {
3712 #ifdef __WIN32__
3713 /* Bad hack for DOS/Windows */
3714 if(setmode(fileno(stdin), O_BINARY) == -1)
3715 {
3716 perror("Set binary mode for stdin");
3717 return 1;
3718 }
3719 #endif
3720 rcsfilename = NULL;
3721 }
3722 else
3723 rcsfilename = argv[optind];
3724
3725 rcs = get_rcsfile(conf.cvsroot, conf.cvsmodule, rcsfilename);
3726 if(!rcs)
3727 return 1;
3728
3729 if(debuglevel & DEBUG_RCS_FILE)
3730 dump_rcsfile(rcs);
3731
3732 if(!reorganise_branches(rcs))
3733 return 1;
3734
3735 assign_tags(rcs);
3736 find_merges(rcs);
3737
3738 if(outfile)
3739 {
3740 if((fp = fopen(outfile, "wb")) == NULL)
3741 {
3742 perror(outfile);
3743 return 1;
3744 }
3745 }
3746 else
3747 {
3748 fp = stdout;
3749 #ifdef __WIN32__
3750 /* Bad hack for DOS/Windows */
3751 if(setmode(fileno(fp), O_BINARY) == -1)
3752 {
3753 perror("Set binary mode for stdout");
3754 return 1;
3755 }
3756 #endif
3757 }
3758
3759 make_layout(rcs);
3760
3761 if(!imagemap)
3762 {
3763 /* Create an image */
3764 im = make_image(rcs);
3765 #ifdef DEBUG_IMAGEMAP
3766 {
3767 FILE *nulfile = fopen("/dev/null", "w");
3768 make_imagemap(rcs, nulfile, im);
3769 fclose(nulfile);
3770 }
3771 #endif
3772 switch(conf.image_type)
3773 {
3774 #ifdef HAVE_IMAGE_GIF
3775 # ifndef HAVE_IMAGE_PNG
3776 default:
3777 # endif
3778 case IMAGE_GIF:
3779 gdImageGif(im, fp);
3780 break;
3781 #endif
3782 #ifdef HAVE_IMAGE_PNG
3783 default:
3784 case IMAGE_PNG:
3785 gdImagePng(im, fp);
3786 break;
3787 #endif
3788 #ifdef HAVE_IMAGE_JPEG
3789 # if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG)
3790 default:
3791 # endif
3792 case IMAGE_JPEG:
3793 gdImageJpeg(im, fp, conf.image_quality);
3794 break;
3795 #endif
3796 }
3797
3798 gdImageDestroy(im);
3799 }
3800 else
3801 {
3802 /* Create an imagemap */
3803 make_imagemap(rcs, fp, NULL);
3804 }
3805
3806 /* Also create imagemap to file if requested */
3807 if(imgmapfile)
3808 {
3809 FILE *ifp = fopen(imgmapfile, "wb");
3810 if(!ifp)
3811 {
3812 perror(imgmapfile);
3813 return 1;
3814 }
3815 make_imagemap(rcs, ifp, NULL);
3816 fclose(ifp);
3817 }
3818
3819 if(outfile)
3820 fclose(fp);
3821
3822 return 0;
3823 }

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0