1 | /* FriBidi |
2 | * fribidi-main.c - command line program for libfribidi |
3 | * |
4 | * Authors: |
5 | * Behdad Esfahbod, 2001, 2002, 2004 |
6 | * Dov Grobgeld, 1999, 2000 |
7 | * |
8 | * Copyright (C) 2004 Sharif FarsiWeb, Inc |
9 | * Copyright (C) 2001,2002 Behdad Esfahbod |
10 | * Copyright (C) 1999,2000 Dov Grobgeld |
11 | * |
12 | * This library is free software; you can redistribute it and/or |
13 | * modify it under the terms of the GNU Lesser General Public |
14 | * License as published by the Free Software Foundation; either |
15 | * version 2.1 of the License, or (at your option) any later version. |
16 | * |
17 | * This library is distributed in the hope that it will be useful, |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
20 | * Lesser General Public License for more details. |
21 | * |
22 | * You should have received a copy of the GNU Lesser General Public License |
23 | * along with this library, in a file named COPYING; if not, write to the |
24 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
25 | * Boston, MA 02110-1301, USA |
26 | * |
27 | * For licensing issues, contact <fribidi.license@gmail.com>. |
28 | */ |
29 | |
30 | #include <common.h> |
31 | |
32 | #include <fribidi.h> |
33 | #include <fribidi-deprecated.h> |
34 | |
35 | #include <stdio.h> |
36 | |
37 | #ifdef HAVE_CONFIG_H |
38 | # include <config.h> |
39 | #endif |
40 | |
41 | #ifdef STDC_HEADERS |
42 | # include <stdlib.h> |
43 | # include <stddef.h> |
44 | #else |
45 | # if HAVE_STDLIB_H |
46 | # include <stdlib.h> |
47 | # endif |
48 | #endif |
49 | #ifdef HAVE_STRING_H |
50 | # if STDC_HEADERS && HAVE_MEMORY_H |
51 | # else |
52 | # include <memory.h> |
53 | # endif |
54 | # include <string.h> |
55 | #endif |
56 | #ifdef HAVE_STRINGS_H |
57 | # include <strings.h> |
58 | #endif |
59 | |
60 | #include "getopt.h" |
61 | |
62 | #define appname "fribidi" |
63 | |
64 | #define MAX_STR_LEN 65000 |
65 | |
66 | |
67 | #define ALLOCATE(tp,ln) ((tp *) fribidi_malloc (sizeof (tp) * (ln))) |
68 | |
69 | static void |
70 | die2 ( |
71 | const char *fmt, |
72 | const char *arg |
73 | ) |
74 | { |
75 | fprintf (stderr, format: "%s: " , appname); |
76 | if (fmt) |
77 | fprintf (stderr, format: fmt, arg); |
78 | fprintf (stderr, format: "Try `%s --help' for more information.\n" , appname); |
79 | exit (status: -1); |
80 | } |
81 | |
82 | #define die1(msg) die2("%s", msg) |
83 | |
84 | fribidi_boolean do_break, do_pad, do_mirror, do_reorder_nsm, do_clean; |
85 | fribidi_boolean show_input, show_visual, show_basedir; |
86 | fribidi_boolean show_ltov, show_vtol, show_levels; |
87 | const int default_text_width = 80; |
88 | int text_width; |
89 | const char *char_set; |
90 | const char *bol_text, *eol_text; |
91 | FriBidiParType input_base_direction; |
92 | int char_set_num; |
93 | |
94 | static void |
95 | help ( |
96 | void |
97 | ) |
98 | { |
99 | /* Break help string into little ones, to assure ISO C89 conformance */ |
100 | printf (format: "Usage: " appname " [OPTION]... [FILE]...\n" |
101 | "A command line interface for the " FRIBIDI_NAME " library.\n" |
102 | "Convert a logical string to visual.\n" |
103 | "\n" |
104 | " -h, --help Display this information and exit\n" |
105 | " -V, --version Display version information and exit\n" |
106 | " -v, --verbose Verbose mode, same as --basedir --ltov --vtol\n" |
107 | " --levels\n" ); |
108 | printf (format: " -d, --debug Output debug information\n" |
109 | " -t, --test Test " FRIBIDI_NAME |
110 | ", same as --clean --nobreak\n" |
111 | " --showinput --reordernsm --width %d\n" , |
112 | default_text_width); |
113 | printf (format: " -c, --charset CS Specify character set, default is %s\n" |
114 | " --charsetdesc CS Show descriptions for character set CS and exit\n" |
115 | " --caprtl Old style: set character set to CapRTL\n" , |
116 | char_set); |
117 | printf (format: " --showinput Output the input string too\n" |
118 | " --nopad Do not right justify RTL lines\n" |
119 | " --nobreak Do not break long lines\n" |
120 | " -w, --width W Screen width for padding, default is %d, but if\n" |
121 | " environment variable COLUMNS is defined, its value\n" |
122 | " will be used, --width overrides both of them.\n" , |
123 | default_text_width); |
124 | printf |
125 | (format: " -B, --bol BOL Output string BOL before the visual string\n" |
126 | " -E, --eol EOL Output string EOL after the visual string\n" |
127 | " --rtl Force base direction to RTL\n" |
128 | " --ltr Force base direction to LTR\n" |
129 | " --wrtl Set base direction to RTL if no strong character found\n" ); |
130 | printf |
131 | (format: " --wltr Set base direction to LTR if no strong character found\n" |
132 | " (default)\n" |
133 | " --nomirror Turn mirroring off, to do it later\n" |
134 | " --reordernsm Reorder NSM sequences to follow their base character\n" |
135 | " --clean Remove explicit format codes in visual string\n" |
136 | " output, currently does not affect other outputs\n" |
137 | " --basedir Output Base Direction\n" ); |
138 | printf (format: " --ltov Output Logical to Visual position map\n" |
139 | " --vtol Output Visual to Logical position map\n" |
140 | " --levels Output Embedding Levels\n" |
141 | " --novisual Do not output the visual string, to be used with\n" |
142 | " --basedir, --ltov, --vtol, --levels\n" ); |
143 | printf (format: " All string indexes are zero based\n" "\n" "Output:\n" |
144 | " For each line of input, output something like this:\n" |
145 | " [input-str` => '][BOL][[padding space]visual-str][EOL]\n" |
146 | " [\\n base-dir][\\n ltov-map][\\n vtol-map][\\n levels]\n" ); |
147 | |
148 | { |
149 | int i; |
150 | printf (format: "\n" "Available character sets:\n" ); |
151 | for (i = 1; i <= FRIBIDI_CHAR_SETS_NUM; i++) |
152 | printf (format: " * %-10s: %-25s%1s\n" , |
153 | fribidi_char_set_name (char_set: i), fribidi_char_set_title (char_set: i), |
154 | (fribidi_char_set_desc (char_set: i) ? "X" : "" )); |
155 | printf |
156 | (format: " X: Character set has descriptions, use --charsetdesc to see\n" ); |
157 | } |
158 | |
159 | printf (format: "\nReport bugs online at\n<" FRIBIDI_BUGREPORT ">.\n" ); |
160 | exit (status: 0); |
161 | } |
162 | |
163 | static void |
164 | version ( |
165 | void |
166 | ) |
167 | { |
168 | printf (appname " %s" , fribidi_version_info); |
169 | exit (status: 0); |
170 | } |
171 | |
172 | static char * |
173 | my_fribidi_strdup ( |
174 | char *s |
175 | ) |
176 | { |
177 | char *m; |
178 | |
179 | m = fribidi_malloc (size: strlen (s: s) + 1); |
180 | if (!m) |
181 | return NULL; |
182 | |
183 | strcpy (dest: m, src: s); |
184 | |
185 | return m; |
186 | } |
187 | |
188 | int |
189 | main ( |
190 | int argc, |
191 | char *argv[] |
192 | ) |
193 | { |
194 | int exit_val; |
195 | fribidi_boolean file_found; |
196 | char *s; |
197 | FILE *IN; |
198 | |
199 | text_width = default_text_width; |
200 | do_break = true; |
201 | do_pad = true; |
202 | do_mirror = true; |
203 | do_clean = false; |
204 | do_reorder_nsm = false; |
205 | show_input = false; |
206 | show_visual = true; |
207 | show_basedir = false; |
208 | show_ltov = false; |
209 | show_vtol = false; |
210 | show_levels = false; |
211 | char_set = "UTF-8" ; |
212 | bol_text = NULL; |
213 | eol_text = NULL; |
214 | input_base_direction = FRIBIDI_PAR_ON; |
215 | |
216 | if ((s = (char *) getenv (name: "COLUMNS" ))) |
217 | { |
218 | int i; |
219 | |
220 | i = atoi (nptr: s); |
221 | if (i > 0) |
222 | text_width = i; |
223 | } |
224 | |
225 | #define CHARSETDESC 257 |
226 | #define CAPRTL 258 |
227 | |
228 | /* Parse the command line with getopt library */ |
229 | /* Must set argv[0], getopt uses it to generate error messages */ |
230 | argv[0] = appname; |
231 | while (1) |
232 | { |
233 | int option_index = 0, c; |
234 | static struct option long_options[] = { |
235 | {"help" , 0, 0, 'h'}, |
236 | {"version" , 0, 0, 'V'}, |
237 | {"verbose" , 0, 0, 'v'}, |
238 | {"debug" , 0, 0, 'd'}, |
239 | {"test" , 0, 0, 't'}, |
240 | {"charset" , 1, 0, 'c'}, |
241 | {"charsetdesc" , 1, 0, CHARSETDESC}, |
242 | {"caprtl" , 0, 0, CAPRTL}, |
243 | {"showinput" , 0, (int *) (void *) &show_input, true}, |
244 | {"nopad" , 0, (int *) (void *) &do_pad, false}, |
245 | {"nobreak" , 0, (int *) (void *) &do_break, false}, |
246 | {"width" , 1, 0, 'w'}, |
247 | {"bol" , 1, 0, 'B'}, |
248 | {"eol" , 1, 0, 'E'}, |
249 | {"nomirror" , 0, (int *) (void *) &do_mirror, false}, |
250 | {"reordernsm" , 0, (int *) (void *) &do_reorder_nsm, true}, |
251 | {"clean" , 0, (int *) (void *) &do_clean, true}, |
252 | {"ltr" , 0, (int *) (void *) &input_base_direction, FRIBIDI_PAR_LTR}, |
253 | {"rtl" , 0, (int *) (void *) &input_base_direction, FRIBIDI_PAR_RTL}, |
254 | {"wltr" , 0, (int *) (void *) &input_base_direction, |
255 | FRIBIDI_PAR_WLTR}, |
256 | {"wrtl" , 0, (int *) (void *) &input_base_direction, |
257 | FRIBIDI_PAR_WRTL}, |
258 | {"basedir" , 0, (int *) (void *) &show_basedir, true}, |
259 | {"ltov" , 0, (int *) (void *) &show_ltov, true}, |
260 | {"vtol" , 0, (int *) (void *) &show_vtol, true}, |
261 | {"levels" , 0, (int *) (void *) &show_levels, true}, |
262 | {"novisual" , 0, (int *) (void *) &show_visual, false}, |
263 | {0, 0, 0, 0} |
264 | }; |
265 | |
266 | c = |
267 | getopt_long (argc: argc, argv: argv, shortopts: "hVvdtc:w:B:E:" , longopts: long_options, |
268 | longind: &option_index); |
269 | if (c == -1) |
270 | break; |
271 | |
272 | switch (c) |
273 | { |
274 | case 0: |
275 | break; |
276 | case 'h': |
277 | help (); |
278 | break; |
279 | case 'V': |
280 | version (); |
281 | break; |
282 | case 'v': |
283 | show_basedir = show_ltov = show_vtol = show_levels = true; |
284 | break; |
285 | case 'w': |
286 | text_width = atoi (nptr: optarg); |
287 | if (text_width <= 0) |
288 | die2 (fmt: "invalid screen width `%s'\n" , arg: optarg); |
289 | break; |
290 | case 'B': |
291 | bol_text = optarg; |
292 | break; |
293 | case 'E': |
294 | eol_text = optarg; |
295 | break; |
296 | case 'd': |
297 | if (!fribidi_set_debug (true)) |
298 | die1 |
299 | ("lib" FRIBIDI |
300 | " must be compiled with DEBUG option to enable\nturn debug info on.\n" ); |
301 | break; |
302 | case 't': |
303 | do_clean = show_input = do_reorder_nsm = true; |
304 | do_break = false; |
305 | text_width = default_text_width; |
306 | break; |
307 | case 'c': |
308 | char_set = my_fribidi_strdup (s: optarg); |
309 | if (!char_set) |
310 | die1 ("memory allocation failed for char_set!" ); |
311 | break; |
312 | case CAPRTL: |
313 | char_set = "CapRTL" ; |
314 | break; |
315 | case CHARSETDESC: |
316 | char_set = optarg; |
317 | char_set_num = fribidi_parse_charset (s: char_set); |
318 | if (!char_set_num) |
319 | die2 (fmt: "unrecognized character set `%s'\n" , arg: char_set); |
320 | if (!fribidi_char_set_desc (char_set: char_set_num)) |
321 | die2 (fmt: "no description available for character set `%s'\n" , |
322 | arg: fribidi_char_set_name (char_set: char_set_num)); |
323 | else |
324 | printf (format: "Descriptions for character set %s:\n" |
325 | "\n" "%s" , fribidi_char_set_title (char_set: char_set_num), |
326 | fribidi_char_set_desc (char_set: char_set_num)); |
327 | exit (status: 0); |
328 | break; |
329 | case ':': |
330 | case '?': |
331 | die2 (NULL, NULL); |
332 | break; |
333 | default: |
334 | break; |
335 | } |
336 | } |
337 | |
338 | char_set_num = fribidi_parse_charset (s: char_set); |
339 | |
340 | if (!char_set_num) |
341 | die2 (fmt: "unrecognized character set `%s'\n" , arg: char_set); |
342 | |
343 | FRIBIDI_BEGIN_IGNORE_DEPRECATIONS |
344 | fribidi_set_mirroring (state: do_mirror); |
345 | fribidi_set_reorder_nsm (state: do_reorder_nsm); |
346 | FRIBIDI_END_IGNORE_DEPRECATIONS |
347 | exit_val = 0; |
348 | file_found = false; |
349 | while (optind < argc || !file_found) |
350 | { |
351 | const char *filename; |
352 | |
353 | filename = optind < argc ? argv[optind++] : "-" ; |
354 | file_found = true; |
355 | |
356 | /* Open the infile for reading */ |
357 | if (filename[0] == '-' && !filename[1]) |
358 | { |
359 | IN = stdin; |
360 | } |
361 | else |
362 | { |
363 | IN = fopen (filename: filename, modes: "r" ); |
364 | if (!IN) |
365 | { |
366 | fprintf (stderr, format: "%s: %s: no such file or directory\n" , |
367 | appname, filename); |
368 | exit_val = 1; |
369 | continue; |
370 | } |
371 | } |
372 | |
373 | /* Read and process input one line at a time */ |
374 | { |
375 | char S_[MAX_STR_LEN]; |
376 | int padding_width, break_width; |
377 | |
378 | padding_width = show_input ? (text_width - 10) / 2 : text_width; |
379 | break_width = do_break ? padding_width : 3 * MAX_STR_LEN; |
380 | |
381 | while (fgets (s: S_, n: sizeof (S_) - 1, stream: IN)) |
382 | { |
383 | const char *new_line, *nl_found; |
384 | FriBidiChar logical[MAX_STR_LEN]; |
385 | char outstring[MAX_STR_LEN]; |
386 | FriBidiParType base; |
387 | FriBidiStrIndex len; |
388 | |
389 | nl_found = "" ; |
390 | S_[sizeof (S_) - 1] = 0; |
391 | len = strlen (s: S_); |
392 | /* chop */ |
393 | if (len > 0 && S_[len - 1] == '\n') |
394 | { |
395 | len--; |
396 | S_[len] = '\0'; |
397 | new_line = "\n" ; |
398 | } |
399 | else |
400 | new_line = "" ; |
401 | /* TODO: handle \r */ |
402 | |
403 | len = fribidi_charset_to_unicode (char_set: char_set_num, s: S_, len, us: logical); |
404 | |
405 | { |
406 | FriBidiChar *visual; |
407 | FriBidiStrIndex *ltov, *vtol; |
408 | FriBidiLevel *levels; |
409 | fribidi_boolean log2vis; |
410 | |
411 | visual = show_visual ? ALLOCATE (FriBidiChar, |
412 | len + 1 |
413 | ) : NULL; |
414 | ltov = show_ltov ? ALLOCATE (FriBidiStrIndex, |
415 | len + 1 |
416 | ) : NULL; |
417 | vtol = show_vtol ? ALLOCATE (FriBidiStrIndex, |
418 | len + 1 |
419 | ) : NULL; |
420 | levels = show_levels ? ALLOCATE (FriBidiLevel, |
421 | len + 1 |
422 | ) : NULL; |
423 | |
424 | /* Create a bidi string. */ |
425 | base = input_base_direction; |
426 | |
427 | log2vis = fribidi_log2vis (str: logical, len, pbase_dir: &base, |
428 | /* output */ |
429 | visual_str: visual, positions_L_to_V: ltov, positions_V_to_L: vtol, embedding_levels: levels); |
430 | |
431 | if (log2vis) |
432 | { |
433 | |
434 | if (show_input) |
435 | printf (format: "%-*s => " , padding_width, S_); |
436 | |
437 | /* Remove explicit marks, if asked for. */ |
438 | |
439 | if (do_clean) |
440 | len = |
441 | fribidi_remove_bidi_marks (str: visual, len, positions_to_this: ltov, position_from_this_list: vtol, |
442 | embedding_levels: levels); |
443 | |
444 | if (show_visual) |
445 | { |
446 | printf (format: "%s" , nl_found); |
447 | |
448 | if (bol_text) |
449 | printf (format: "%s" , bol_text); |
450 | |
451 | /* Convert it to input charset and print. */ |
452 | { |
453 | FriBidiStrIndex idx, st; |
454 | for (idx = 0; idx < len;) |
455 | { |
456 | FriBidiStrIndex wid, inlen; |
457 | |
458 | wid = break_width; |
459 | st = idx; |
460 | if (char_set_num != FRIBIDI_CHAR_SET_CAP_RTL) |
461 | while (wid > 0 && idx < len) |
462 | { |
463 | wid -= |
464 | FRIBIDI_IS_EXPLICIT_OR_ISOLATE_OR_BN_OR_NSM |
465 | (fribidi_get_bidi_type (visual[idx])) ? 0 |
466 | : 1; |
467 | idx++; |
468 | } |
469 | else |
470 | while (wid > 0 && idx < len) |
471 | { |
472 | wid--; |
473 | idx++; |
474 | } |
475 | if (wid < 0 && idx - st > 1) |
476 | idx--; |
477 | inlen = idx - st; |
478 | |
479 | fribidi_unicode_to_charset (char_set: char_set_num, |
480 | us: visual + st, len: inlen, |
481 | s: outstring); |
482 | if (FRIBIDI_IS_RTL (base)) |
483 | printf (format: "%*s" , |
484 | (int) (do_pad ? (padding_width + |
485 | strlen (s: outstring) - |
486 | (break_width - |
487 | wid)) : 0), |
488 | outstring); |
489 | else |
490 | printf (format: "%s" , outstring); |
491 | if (idx < len) |
492 | printf (format: "\n" ); |
493 | } |
494 | } |
495 | if (eol_text) |
496 | printf (format: "%s" , eol_text); |
497 | |
498 | nl_found = "\n" ; |
499 | } |
500 | if (show_basedir) |
501 | { |
502 | printf (format: "%s" , nl_found); |
503 | printf (format: "Base direction: %s" , |
504 | (FRIBIDI_DIR_TO_LEVEL (base) ? "R" : "L" )); |
505 | nl_found = "\n" ; |
506 | } |
507 | if (show_ltov) |
508 | { |
509 | FriBidiStrIndex i; |
510 | |
511 | printf (format: "%s" , nl_found); |
512 | for (i = 0; i < len; i++) |
513 | printf (format: "%ld " , (long) ltov[i]); |
514 | nl_found = "\n" ; |
515 | } |
516 | if (show_vtol) |
517 | { |
518 | FriBidiStrIndex i; |
519 | |
520 | printf (format: "%s" , nl_found); |
521 | for (i = 0; i < len; i++) |
522 | printf (format: "%ld " , (long) vtol[i]); |
523 | nl_found = "\n" ; |
524 | } |
525 | if (show_levels) |
526 | { |
527 | FriBidiStrIndex i; |
528 | |
529 | printf (format: "%s" , nl_found); |
530 | for (i = 0; i < len; i++) |
531 | printf (format: "%d " , (int) levels[i]); |
532 | nl_found = "\n" ; |
533 | } |
534 | } |
535 | else |
536 | { |
537 | exit_val = 2; |
538 | } |
539 | |
540 | if (show_visual) |
541 | free (ptr: visual); |
542 | if (show_ltov) |
543 | free (ptr: ltov); |
544 | if (show_vtol) |
545 | free (ptr: vtol); |
546 | if (show_levels) |
547 | free (ptr: levels); |
548 | } |
549 | |
550 | if (*nl_found) |
551 | printf (format: "%s" , new_line); |
552 | } |
553 | } |
554 | } |
555 | |
556 | return exit_val; |
557 | } |
558 | |
559 | /* Editor directions: |
560 | * vim:textwidth=78:tabstop=8:shiftwidth=2:autoindent:cindent |
561 | */ |
562 | |