1 /*
2 * Copyright © 2011 Google, Inc.
3 *
4 * This is part of HarfBuzz, a text shaping library.
5 *
6 * Permission is hereby granted, without written agreement and without
7 * license or royalty fees, to use, copy, modify, and distribute this
8 * software and its documentation for any purpose, provided that the
9 * above copyright notice and the following two paragraphs appear in
10 * all copies of this software.
11 *
12 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
16 * DAMAGE.
17 *
18 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
21 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23 *
24 * Google Author(s): Behdad Esfahbod
25 */
26
27 #ifndef TEXT_OPTIONS_HH
28 #define TEXT_OPTIONS_HH
29
30 #include "options.hh"
31
32 struct text_options_t
33 {
text_options_ttext_options_t34 text_options_t ()
35 : gs (g_string_new (nullptr))
36 {}
~text_options_ttext_options_t37 ~text_options_t ()
38 {
39 g_free (text);
40 g_free (text_file);
41 if (gs)
42 g_string_free (gs, true);
43 if (in_fp && in_fp != stdin)
44 fclose (in_fp);
45 }
46
47 void add_options (option_parser_t *parser);
48
post_parsetext_options_t49 void post_parse (GError **error G_GNUC_UNUSED)
50 {
51 if (!text && !text_file)
52 text_file = g_strdup ("-");
53
54 if (text && text_file)
55 {
56 g_set_error (error,
57 G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
58 "Only one of text and text-file can be set");
59 return;
60 }
61
62 if (text_file)
63 {
64 if (0 != strcmp (text_file, "-"))
65 in_fp = fopen (text_file, "r");
66 else
67 in_fp = stdin;
68
69 if (!in_fp)
70 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
71 "Failed opening text file `%s': %s",
72 text_file, strerror (errno));
73 }
74 }
75
76 const char *get_line (unsigned int *len);
77
78 int text_len = -1;
79 char *text = nullptr;
80 char *text_file = nullptr;
81
82 private:
83 FILE *in_fp = nullptr;
84 GString *gs = nullptr;
85 char *line = nullptr;
86 unsigned line_len = UINT_MAX;
87 hb_bool_t single_par = false;
88 };
89
90 struct shape_text_options_t : text_options_t
91 {
~shape_text_options_tshape_text_options_t92 ~shape_text_options_t ()
93 {
94 g_free (text_before);
95 g_free (text_after);
96 }
97
98 void add_options (option_parser_t *parser);
99
100 char *text_before = nullptr;
101 char *text_after = nullptr;
102 };
103
104
105 static gboolean
parse_text(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)106 parse_text (const char *name G_GNUC_UNUSED,
107 const char *arg,
108 gpointer data,
109 GError **error G_GNUC_UNUSED)
110 {
111 text_options_t *text_opts = (text_options_t *) data;
112
113 if (text_opts->text)
114 {
115 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
116 "Either --text or --unicodes can be provided but not both");
117 return false;
118 }
119
120 text_opts->text_len = -1;
121 text_opts->text = g_strdup (arg);
122 return true;
123 }
124
125 static bool
encode_unicodes(const char * unicodes,GString * gs,GError ** error)126 encode_unicodes (const char *unicodes,
127 GString *gs,
128 GError **error)
129 {
130 #define DELIMITERS "<+-|>{},;&#\\xXuUnNiI\n\t\v\f\r "
131
132 char *s = (char *) unicodes;
133 char *p;
134
135 while (s && *s)
136 {
137 while (*s && strchr (DELIMITERS, *s))
138 s++;
139 if (!*s)
140 break;
141
142 errno = 0;
143 hb_codepoint_t u = strtoul (s, &p, 16);
144 if (errno || s == p)
145 {
146 g_string_free (gs, TRUE);
147 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
148 "Failed parsing Unicode value at: '%s'", s);
149 return false;
150 }
151
152 g_string_append_unichar (gs, u);
153
154 s = p;
155 }
156
157 #undef DELIMITERS
158
159 return true;
160 }
161
162 static gboolean
parse_unicodes(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)163 parse_unicodes (const char *name G_GNUC_UNUSED,
164 const char *arg,
165 gpointer data,
166 GError **error G_GNUC_UNUSED)
167 {
168 text_options_t *text_opts = (text_options_t *) data;
169
170 if (text_opts->text)
171 {
172 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
173 "Either --text or --unicodes can be provided but not both");
174 return false;
175 }
176
177 GString *gs = g_string_new (nullptr);
178 if (0 == strcmp (arg, "*"))
179 g_string_append_c (gs, '*');
180 else
181 if (!encode_unicodes (arg, gs, error))
182 return false;
183
184 text_opts->text_len = gs->len;
185 text_opts->text = g_string_free (gs, FALSE);
186 return true;
187 }
188
189 static gboolean
parse_text_before(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error)190 parse_text_before (const char *name G_GNUC_UNUSED,
191 const char *arg,
192 gpointer data,
193 GError **error)
194 {
195 auto *opts = (shape_text_options_t *) data;
196
197 if (opts->text_before)
198 {
199 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
200 "Either --text-before or --unicodes-before can be provided but not both");
201 return false;
202 }
203
204 opts->text_before = g_strdup (arg);
205 fprintf(stderr, "%s\n", opts->text_before);
206 return true;
207 }
208
209 static gboolean
parse_unicodes_before(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error)210 parse_unicodes_before (const char *name G_GNUC_UNUSED,
211 const char *arg,
212 gpointer data,
213 GError **error)
214 {
215 auto *opts = (shape_text_options_t *) data;
216
217 if (opts->text_before)
218 {
219 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
220 "Either --text-before or --unicodes-before can be provided but not both");
221 return false;
222 }
223
224 GString *gs = g_string_new (nullptr);
225 if (!encode_unicodes (arg, gs, error))
226 return false;
227
228 opts->text_before = g_string_free (gs, FALSE);
229 return true;
230 }
231
232 static gboolean
parse_text_after(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error)233 parse_text_after (const char *name G_GNUC_UNUSED,
234 const char *arg,
235 gpointer data,
236 GError **error)
237 {
238 auto *opts = (shape_text_options_t *) data;
239
240 if (opts->text_after)
241 {
242 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
243 "Either --text-after or --unicodes-after can be provided but not both");
244 return false;
245 }
246
247 opts->text_after = g_strdup (arg);
248 return true;
249 }
250
251 static gboolean
parse_unicodes_after(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error)252 parse_unicodes_after (const char *name G_GNUC_UNUSED,
253 const char *arg,
254 gpointer data,
255 GError **error)
256 {
257 auto *opts = (shape_text_options_t *) data;
258
259 if (opts->text_after)
260 {
261 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
262 "Either --text-after or --unicodes-after can be provided but not both");
263 return false;
264 }
265
266 GString *gs = g_string_new (nullptr);
267 if (!encode_unicodes (arg, gs, error))
268 return false;
269
270 opts->text_after = g_string_free (gs, FALSE);
271 return true;
272 }
273
274 const char *
get_line(unsigned int * len)275 text_options_t::get_line (unsigned int *len)
276 {
277 if (text)
278 {
279 if (!line)
280 {
281 line = text;
282 line_len = text_len;
283 }
284 if (line_len == UINT_MAX)
285 line_len = strlen (line);
286
287 if (!line_len)
288 {
289 *len = 0;
290 return nullptr;
291 }
292
293 const char *ret = line;
294 const char *p = single_par ? nullptr : (const char *) memchr (line, '\n', line_len);
295 unsigned int ret_len;
296 if (!p)
297 {
298 ret_len = line_len;
299 line += ret_len;
300 line_len = 0;
301 }
302 else
303 {
304 ret_len = p - ret;
305 line += ret_len + 1;
306 line_len -= ret_len + 1;
307 }
308
309 *len = ret_len;
310 return ret;
311 }
312
313 g_string_set_size (gs, 0);
314 char buf[BUFSIZ];
315 while (fgets (buf, sizeof (buf), in_fp))
316 {
317 unsigned bytes = strlen (buf);
318 if (!single_par && bytes && buf[bytes - 1] == '\n')
319 {
320 bytes--;
321 g_string_append_len (gs, buf, bytes);
322 break;
323 }
324 g_string_append_len (gs, buf, bytes);
325 }
326 if (ferror (in_fp))
327 fail (false, "Failed reading text: %s", strerror (errno));
328 *len = gs->len;
329 return !*len && feof (in_fp) ? nullptr : gs->str;
330 }
331
332 void
add_options(option_parser_t * parser)333 text_options_t::add_options (option_parser_t *parser)
334 {
335 GOptionEntry entries[] =
336 {
337 {"text", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text, "Set input text", "string"},
338 {"text-file", 0, 0, G_OPTION_ARG_STRING, &this->text_file, "Set input text file-name", "filename"},
339 {"unicodes", 'u', 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes, "Set input Unicode codepoints", "list of hex numbers"},
340 {"single-par", 0, 0, G_OPTION_ARG_NONE, &this->single_par, "Treat text as single paragraph", nullptr},
341 {nullptr}
342 };
343 parser->add_group (entries,
344 "text",
345 "Text options:\n\nIf no text is provided, standard input is used for input.\n",
346 "Options for the input text",
347 this);
348 }
349
350 void
add_options(option_parser_t * parser)351 shape_text_options_t::add_options (option_parser_t *parser)
352 {
353 text_options_t::add_options (parser);
354
355 GOptionEntry entries[] =
356 {
357 {"text-before", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text_before, "Set text context before each line", "string"},
358 {"text-after", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text_after, "Set text context after each line", "string"},
359 {"unicodes-before", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes_before, "Set Unicode codepoints context before each line", "list of hex numbers"},
360 {"unicodes-after", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes_after, "Set Unicode codepoints context after each line", "list of hex numbers"},
361 {nullptr}
362 };
363 parser->add_group (entries,
364 "text-context",
365 "Textual context options:",
366 "Options for the input context text",
367 this);
368 }
369
370 #endif
371