Merge pull request #13 from rdebath/patch-fixes
[congif.git] / main.c
blobbac0a104a057f92af8bde1610e59983ad10207b4
1 #define _POSIX_C_SOURCE 200809L
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <stdint.h>
5 #include <float.h>
6 #include <string.h>
7 #include <unistd.h>
8 #include <sys/types.h>
9 #include <sys/stat.h>
10 #include <fcntl.h>
11 #include <sys/ioctl.h>
12 #include <termios.h>
14 #include "term.h"
15 #include "mbf.h"
16 #include "gif.h"
17 #include "default_font.h"
19 #define MIN(A, B) ((A) < (B) ? (A) : (B))
20 #define MAX(A, B) ((A) > (B) ? (A) : (B))
22 #define MIN_DELAY 6
24 static struct Options {
25 char *timings, *dialogue;
26 char *output;
27 float maxdelay, divisor;
28 int loop;
29 char *font;
30 int height, width;
31 int cursor;
32 int quiet;
33 int barsize;
35 int has_winsize;
36 struct winsize size;
37 } options;
39 uint8_t
40 get_pair(Term *term, int row, int col)
42 Cell cell;
43 uint8_t fore, back;
44 int inverse;
46 inverse = term->mode & M_REVERSE;
47 if (term->mode & M_CURSORVIS)
48 inverse = term->row == row && term->col == col ? !inverse : inverse;
49 cell = term->addr[row][col];
50 inverse = cell.attr & A_INVERSE ? !inverse : inverse;
51 fore = cell.pair >> 4;
52 back = cell.pair & 0xF;
53 if (cell.attr & (A_ITALIC | A_CROSSED))
54 fore = 0x2;
55 else if (cell.attr & A_UNDERLINE)
56 fore = 0x6;
57 else if (cell.attr & A_DIM)
58 fore = 0x8;
59 if (inverse) {
60 uint8_t t;
61 t = fore; fore = back; back = t;
63 if (cell.attr & A_BOLD)
64 fore |= 0x8;
65 if (cell.attr & A_BLINK)
66 back |= 0x8;
67 if ((cell.attr & A_INVISIBLE) != 0) fore = back;
68 return (fore << 4) | (back & 0xF);
71 void
72 draw_char(Font *font, GIF *gif, uint16_t code, uint8_t pair, int row, int col)
74 int i, j;
75 int x, y;
76 int index;
77 int pixel;
78 uint8_t *strip;
80 index = get_index(font, code);
81 if (index == -1)
82 return;
83 strip = &font->data[font->stride * font->header.h * index];
84 y = font->header.h * row;
85 for (i = 0; i < font->header.h; i++) {
86 x = font->header.w * col;
87 for (j = 0; j < font->header.w; j++) {
88 pixel = strip[j >> 3] & (1 << (7 - (j & 7)));
89 gif->cur[y * gif->w + x] = pixel ? pair >> 4 : pair & 0xF;
90 x++;
92 y++;
93 strip += font->stride;
97 void
98 render(Term *term, Font *font, GIF *gif, uint16_t delay)
100 int i, j;
101 uint16_t code;
102 uint8_t pair;
104 for (i = 0; i < term->rows; i++) {
105 for (j = 0; j < term->cols; j++) {
106 code = term->addr[i][j].code;
107 pair = get_pair(term, i, j);
108 draw_char(font, gif, code, pair, i, j);
112 if (term->plt_local)
113 gif->plt = term->plt;
114 else
115 gif->plt = 0;
116 gif->plt_dirty |= term->plt_dirty;
117 term->plt_dirty = 0;
119 add_frame(gif, delay);
123 convert_script()
125 FILE *ft;
126 int fd;
127 float t;
128 int n;
129 uint8_t ch;
130 Font *font;
131 int w, h;
132 int i, c;
133 float d;
134 uint16_t rd, id = 0;
135 float lastdone, done;
136 char pb[options.barsize+1];
137 char fl[512];
138 int fln = 0;
139 GIF *gif;
140 Term *term;
142 ft = fopen(options.timings, "r");
143 if (!ft) {
144 fprintf(stderr, "error: could not load timings: %s\n", options.timings);
145 goto no_ft;
147 fd = open(options.dialogue, O_RDONLY);
148 if (fd == -1) {
149 fprintf(stderr, "error: could not load dialogue: %s\n", options.dialogue);
150 goto no_fd;
152 if (options.font == 0) {
153 font = default_font;
154 } else {
155 font = load_font(options.font);
156 if (!font) {
157 fprintf(stderr, "error: could not load font: %s\n", options.font);
158 goto no_font;
162 /* Save first line of dialogue */
163 do {
164 if (read(fd, &ch, 1) <= 0) break;
165 if (fln<(int)sizeof(fl)-1) fl[fln++]=ch;
166 } while (ch != '\n');
167 /* Inspect it for the terminal size if needed */
168 if (fln > 16 && (options.height == 0 || options.width == 0)) {
169 int col=0, ln=0;
170 char * s;
171 fl[fln] = 0;
172 s = strstr(fl, "COLUMNS=\"");
173 if (s) col = atoi(s+9);
174 s = strstr(fl, "LINES=\"");
175 if (s) ln = atoi(s+7);
177 if (ln>0 && col>0) {
178 if (options.width <= 0)
179 options.width = col;
180 if (options.height <= 0)
181 options.height = ln;
185 /* Default the VT to our real terminal */
186 if (options.has_winsize && (options.height == 0 || options.width == 0)) {
187 if (options.height <= 0)
188 options.height = options.size.ws_row;
189 if (options.width <= 0)
190 options.width = options.size.ws_col;
193 if (options.width <= 0 || options.height <= 0) {
194 fprintf(stderr, "error: no terminal size specified\n");
195 goto no_termsize;
198 term = new_term(options.height, options.width);
199 w = term->cols * font->header.w;
200 h = term->rows * font->header.h;
201 gif = new_gif(options.output, w, h, term->plt, options.loop);
202 if (!gif) {
203 fprintf(stderr, "error: could not create GIF: %s\n", options.output);
204 goto no_gif;
206 if (options.barsize) {
207 pb[0] = '[';
208 pb[options.barsize-1] = ']';
209 pb[options.barsize] = '\0';
210 for (i = 1; i < options.barsize-1; i++)
211 pb[i] = '-';
212 lastdone = 0;
213 printf("%s\r[", pb);
214 /* get number of chunks */
215 for (c = 0; fscanf(ft, "%f %d\n", &t, &n) == 2; c++);
216 rewind(ft);
218 i = 0;
219 d = rd = 0;
220 while (fscanf(ft, "%f %d\n", &t, &n) == 2) {
221 if (options.barsize) {
222 done = i * (options.barsize-1) / c;
223 if (done > lastdone) {
224 while (done > lastdone) {
225 putchar('#');
226 lastdone++;
228 fflush(stdout);
231 d += (MIN(t, options.maxdelay) * 100.0 / options.divisor);
232 rd = (uint16_t) MIN((int)(d + 0.5), 65535);
233 if (i && rd >= MIN_DELAY) {
234 render(term, font, gif, rd);
235 d = 0;
237 if (i == 0) { id = rd; rd = 0; d = 0; }
238 while (n--) {
239 read(fd, &ch, 1);
240 parse(term, ch);
242 if (!options.cursor)
243 term->mode &= ~M_CURSORVIS;
244 i++;
246 rd += id;
247 if (options.barsize) {
248 while (lastdone < options.barsize-2) {
249 putchar('#');
250 lastdone++;
252 putchar('\n');
254 render(term, font, gif, MAX(rd, 1));
255 close_gif(gif);
256 free(term);
257 return 0;
258 no_gif:
259 free(term);
260 no_termsize:
261 if (options.font) free(font);
262 no_font:
263 close(fd);
264 no_fd:
265 fclose(ft);
266 no_ft:
267 return 1;
270 void
271 help(char *name)
273 fprintf(stderr,
274 "Usage: %s [options] timings dialogue\n\n"
275 "timings: File generated by script(1)'s -t option\n"
276 "dialogue: File generated by script(1)'s regular output\n\n"
277 "options:\n"
278 " -o output File name of GIF output\n"
279 " -m maxdelay Maximum delay, as in scriptreplay(1)\n"
280 " -d divisor Speedup, as in scriptreplay(1)\n"
281 " -l count GIF loop count (0 = infinite loop)\n"
282 " -f font File name of MBF font to use\n"
283 " -h lines Terminal height\n"
284 " -w columns Terminal width\n"
285 " -c on|off Show/hide cursor\n"
286 " -p palette Define color palette, '@help' for std else file.\n"
287 " -q Quiet mode (don't show progress bar)\n"
288 " -v Verbose mode (show parser logs)\n"
289 , name);
292 void
293 set_defaults()
295 options.height = 0;
296 options.width = 0;
297 options.output = "con.gif";
298 options.maxdelay = FLT_MAX;
299 options.divisor = 1.0;
300 options.loop = -1;
301 options.font = 0;
302 options.cursor = 1;
303 options.quiet = 0;
304 options.barsize = 0;
308 main(int argc, char *argv[])
310 int opt;
311 int ret;
313 set_defaults();
314 options.has_winsize = 0;
315 if (ioctl(0, TIOCGWINSZ, &options.size) != -1) {
316 options.has_winsize = 1;
318 while ((opt = getopt(argc, argv, "o:m:d:l:f:h:w:c:p:qv")) != -1) {
319 switch (opt) {
320 case 'o':
321 options.output = optarg;
322 break;
323 case 'm':
324 options.maxdelay = atof(optarg);
325 break;
326 case 'd':
327 options.divisor = atof(optarg);
328 break;
329 case 'l':
330 options.loop = atoi(optarg);
331 break;
332 case 'f':
333 options.font = optarg;
334 break;
335 case 'h':
336 options.height = atoi(optarg);
337 break;
338 case 'w':
339 options.width = atoi(optarg);
340 break;
341 case 'c':
342 if (!strcmp(optarg, "on") || !strcmp(optarg, "1"))
343 options.cursor = 1;
344 else if (!strcmp(optarg, "off") || !strcmp(optarg, "0"))
345 options.cursor = 0;
346 break;
347 case 'q':
348 options.quiet = 1;
349 break;
350 case 'v':
351 set_verbosity(1);
352 break;
353 case 'p':
354 set_default_palette(optarg);
355 break;
356 default:
357 help(argv[0]);
358 return 1;
361 if (optind >= argc - 1) {
362 fprintf(stderr, "error: no input given\n");
363 help(argv[0]);
364 return 1;
366 options.timings = argv[optind++];
367 options.dialogue = argv[optind++];
368 if (!options.quiet && options.has_winsize)
369 options.barsize = options.size.ws_col - 1;
370 ret = convert_script();
371 return ret;