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

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0