• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 SHAPE_OPTIONS_HH
28 #define SHAPE_OPTIONS_HH
29 
30 #include "options.hh"
31 
32 struct shape_options_t
33 {
~shape_options_tshape_options_t34   ~shape_options_t ()
35   {
36     g_free (direction);
37     g_free (language);
38     g_free (script);
39     free (features);
40     g_strfreev (shapers);
41   }
42 
43   void add_options (option_parser_t *parser);
44 
setup_buffershape_options_t45   void setup_buffer (hb_buffer_t *buffer)
46   {
47     hb_buffer_set_direction (buffer, hb_direction_from_string (direction, -1));
48     hb_buffer_set_script (buffer, hb_script_from_string (script, -1));
49     hb_buffer_set_language (buffer, hb_language_from_string (language, -1));
50     hb_buffer_set_flags (buffer, (hb_buffer_flags_t)
51 				 (HB_BUFFER_FLAG_DEFAULT |
52 				  (bot ? HB_BUFFER_FLAG_BOT : 0) |
53 				  (eot ? HB_BUFFER_FLAG_EOT : 0) |
54 				  (preserve_default_ignorables ? HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES : 0) |
55 				  (remove_default_ignorables ? HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES : 0) |
56 				  0));
57     hb_buffer_set_invisible_glyph (buffer, invisible_glyph);
58     hb_buffer_set_not_found_glyph (buffer, not_found_glyph);
59     hb_buffer_set_cluster_level (buffer, cluster_level);
60     hb_buffer_guess_segment_properties (buffer);
61   }
62 
copy_buffer_propertiesshape_options_t63   static void copy_buffer_properties (hb_buffer_t *dst, hb_buffer_t *src)
64   {
65     hb_segment_properties_t props;
66     hb_buffer_get_segment_properties (src, &props);
67     hb_buffer_set_segment_properties (dst, &props);
68     hb_buffer_set_flags (dst, hb_buffer_get_flags (src));
69     hb_buffer_set_cluster_level (dst, hb_buffer_get_cluster_level (src));
70   }
71 
populate_buffershape_options_t72   void populate_buffer (hb_buffer_t *buffer, const char *text, int text_len,
73 			const char *text_before, const char *text_after)
74   {
75     hb_buffer_clear_contents (buffer);
76     if (text_before) {
77       unsigned int len = strlen (text_before);
78       hb_buffer_add_utf8 (buffer, text_before, len, len, 0);
79     }
80     hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len);
81     if (text_after) {
82       hb_buffer_add_utf8 (buffer, text_after, -1, 0, 0);
83     }
84 
85     if (!utf8_clusters) {
86       /* Reset cluster values to refer to Unicode character index
87        * instead of UTF-8 index. */
88       unsigned int num_glyphs = hb_buffer_get_length (buffer);
89       hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, nullptr);
90       for (unsigned int i = 0; i < num_glyphs; i++)
91       {
92 	info->cluster = i;
93 	info++;
94       }
95     }
96 
97     setup_buffer (buffer);
98   }
99 
shapeshape_options_t100   hb_bool_t shape (hb_font_t *font, hb_buffer_t *buffer, const char **error=nullptr)
101   {
102     hb_buffer_t *text_buffer = nullptr;
103     if (verify)
104     {
105       text_buffer = hb_buffer_create ();
106       hb_buffer_append (text_buffer, buffer, 0, -1);
107     }
108 
109     if (!hb_shape_full (font, buffer, features, num_features, shapers))
110     {
111       if (error)
112 	*error = "All shapers failed.";
113       goto fail;
114     }
115 
116     if (normalize_glyphs)
117       hb_buffer_normalize_glyphs (buffer);
118 
119     if (verify && !verify_buffer (buffer, text_buffer, font, error))
120       goto fail;
121 
122     if (text_buffer)
123       hb_buffer_destroy (text_buffer);
124 
125     return true;
126 
127   fail:
128     if (text_buffer)
129       hb_buffer_destroy (text_buffer);
130 
131     return false;
132   }
133 
verify_buffershape_options_t134   bool verify_buffer (hb_buffer_t  *buffer,
135 		      hb_buffer_t  *text_buffer,
136 		      hb_font_t    *font,
137 		      const char  **error=nullptr)
138   {
139     if (!verify_buffer_monotone (buffer, error))
140       return false;
141     if (!verify_buffer_safe_to_break (buffer, text_buffer, font, error))
142       return false;
143     return true;
144   }
145 
verify_buffer_monotoneshape_options_t146   bool verify_buffer_monotone (hb_buffer_t *buffer, const char **error=nullptr)
147   {
148     /* Check that clusters are monotone. */
149     if (cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES ||
150 	cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS)
151     {
152       bool is_forward = HB_DIRECTION_IS_FORWARD (hb_buffer_get_direction (buffer));
153 
154       unsigned int num_glyphs;
155       hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &num_glyphs);
156 
157       for (unsigned int i = 1; i < num_glyphs; i++)
158 	if (info[i-1].cluster != info[i].cluster &&
159 	    (info[i-1].cluster < info[i].cluster) != is_forward)
160 	{
161 	  if (error)
162 	    *error = "clusters are not monotone.";
163 	  return false;
164 	}
165     }
166 
167     return true;
168   }
169 
verify_buffer_safe_to_breakshape_options_t170   bool verify_buffer_safe_to_break (hb_buffer_t  *buffer,
171 				    hb_buffer_t  *text_buffer,
172 				    hb_font_t    *font,
173 				    const char  **error=nullptr)
174   {
175     if (cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES &&
176 	cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS)
177     {
178       /* Cannot perform this check without monotone clusters.
179        * Then again, unsafe-to-break flag is much harder to use without
180        * monotone clusters. */
181       return true;
182     }
183 
184     /* Check that breaking up shaping at safe-to-break is indeed safe. */
185 
186     hb_buffer_t *fragment = hb_buffer_create ();
187     hb_buffer_t *reconstruction = hb_buffer_create ();
188     copy_buffer_properties (reconstruction, buffer);
189 
190     unsigned int num_glyphs;
191     hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &num_glyphs);
192 
193     unsigned int num_chars;
194     hb_glyph_info_t *text = hb_buffer_get_glyph_infos (text_buffer, &num_chars);
195 
196     /* Chop text and shape fragments. */
197     bool forward = HB_DIRECTION_IS_FORWARD (hb_buffer_get_direction (buffer));
198     unsigned int start = 0;
199     unsigned int text_start = forward ? 0 : num_chars;
200     unsigned int text_end = text_start;
201     for (unsigned int end = 1; end < num_glyphs + 1; end++)
202     {
203       if (end < num_glyphs &&
204 	  (info[end].cluster == info[end-1].cluster ||
205 	   info[end-(forward?0:1)].mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK))
206 	  continue;
207 
208       /* Shape segment corresponding to glyphs start..end. */
209       if (end == num_glyphs)
210       {
211 	if (forward)
212 	  text_end = num_chars;
213 	else
214 	  text_start = 0;
215       }
216       else
217       {
218 	if (forward)
219 	{
220 	  unsigned int cluster = info[end].cluster;
221 	  while (text_end < num_chars && text[text_end].cluster < cluster)
222 	    text_end++;
223 	}
224 	else
225 	{
226 	  unsigned int cluster = info[end - 1].cluster;
227 	  while (text_start && text[text_start - 1].cluster >= cluster)
228 	    text_start--;
229 	}
230       }
231       assert (text_start < text_end);
232 
233       if (0)
234 	printf("start %d end %d text start %d end %d\n", start, end, text_start, text_end);
235 
236       hb_buffer_clear_contents (fragment);
237       copy_buffer_properties (fragment, buffer);
238 
239       hb_buffer_flags_t flags = hb_buffer_get_flags (fragment);
240       if (0 < text_start)
241 	flags = (hb_buffer_flags_t) (flags & ~HB_BUFFER_FLAG_BOT);
242       if (text_end < num_chars)
243 	flags = (hb_buffer_flags_t) (flags & ~HB_BUFFER_FLAG_EOT);
244       hb_buffer_set_flags (fragment, flags);
245 
246       hb_buffer_append (fragment, text_buffer, text_start, text_end);
247       if (!hb_shape_full (font, fragment, features, num_features, shapers))
248       {
249 	if (error)
250 	  *error = "All shapers failed while shaping fragment.";
251 	hb_buffer_destroy (reconstruction);
252 	hb_buffer_destroy (fragment);
253 	return false;
254       }
255       hb_buffer_append (reconstruction, fragment, 0, -1);
256 
257       start = end;
258       if (forward)
259 	text_start = text_end;
260       else
261 	text_end = text_start;
262     }
263 
264     bool ret = true;
265     hb_buffer_diff_flags_t diff = hb_buffer_diff (reconstruction, buffer, (hb_codepoint_t) -1, 0);
266     if (diff)
267     {
268       if (error)
269 	*error = "Safe-to-break test failed.";
270       ret = false;
271 
272       /* Return the reconstructed result instead so it can be inspected. */
273       hb_buffer_set_length (buffer, 0);
274       hb_buffer_append (buffer, reconstruction, 0, -1);
275     }
276 
277     hb_buffer_destroy (reconstruction);
278     hb_buffer_destroy (fragment);
279 
280     return ret;
281   }
282 
shape_closureshape_options_t283   void shape_closure (const char *text, int text_len,
284 		      hb_font_t *font, hb_buffer_t *buffer,
285 		      hb_set_t *glyphs)
286   {
287     hb_buffer_reset (buffer);
288     hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len);
289     setup_buffer (buffer);
290     hb_ot_shape_glyphs_closure (font, buffer, features, num_features, glyphs);
291   }
292 
293   /* Buffer properties */
294   char *direction = nullptr;
295   char *language = nullptr;
296   char *script = nullptr;
297 
298   /* Buffer flags */
299   hb_bool_t bot = false;
300   hb_bool_t eot = false;
301   hb_bool_t preserve_default_ignorables = false;
302   hb_bool_t remove_default_ignorables = false;
303 
304   hb_feature_t *features = nullptr;
305   unsigned int num_features = 0;
306   char **shapers = nullptr;
307   hb_bool_t utf8_clusters = false;
308   hb_codepoint_t invisible_glyph = 0;
309   hb_codepoint_t not_found_glyph = 0;
310   hb_buffer_cluster_level_t cluster_level = HB_BUFFER_CLUSTER_LEVEL_DEFAULT;
311   hb_bool_t normalize_glyphs = false;
312   hb_bool_t verify = false;
313   unsigned int num_iterations = 1;
314 };
315 
316 
317 static gboolean
parse_shapers(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error)318 parse_shapers (const char *name G_GNUC_UNUSED,
319 	       const char *arg,
320 	       gpointer    data,
321 	       GError    **error)
322 {
323   shape_options_t *shape_opts = (shape_options_t *) data;
324   char **shapers = g_strsplit (arg, ",", 0);
325 
326   for (char **shaper = shapers; *shaper; shaper++)
327   {
328     bool found = false;
329     for (const char **hb_shaper = hb_shape_list_shapers (); *hb_shaper; hb_shaper++) {
330       if (strcmp (*shaper, *hb_shaper) == 0)
331       {
332 	found = true;
333 	break;
334       }
335     }
336     if (!found)
337     {
338       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
339 		   "Unknown or unsupported shaper: %s", *shaper);
340       g_strfreev (shapers);
341       return false;
342     }
343   }
344 
345   g_strfreev (shape_opts->shapers);
346   shape_opts->shapers = shapers;
347   return true;
348 }
349 
350 static G_GNUC_NORETURN gboolean
list_shapers(const char * name G_GNUC_UNUSED,const char * arg G_GNUC_UNUSED,gpointer data G_GNUC_UNUSED,GError ** error G_GNUC_UNUSED)351 list_shapers (const char *name G_GNUC_UNUSED,
352 	      const char *arg G_GNUC_UNUSED,
353 	      gpointer    data G_GNUC_UNUSED,
354 	      GError    **error G_GNUC_UNUSED)
355 {
356   for (const char **shaper = hb_shape_list_shapers (); *shaper; shaper++)
357     g_printf ("%s\n", *shaper);
358 
359   exit(0);
360 }
361 
362 
363 static gboolean
parse_features(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)364 parse_features (const char *name G_GNUC_UNUSED,
365 		const char *arg,
366 		gpointer    data,
367 		GError    **error G_GNUC_UNUSED)
368 {
369   shape_options_t *shape_opts = (shape_options_t *) data;
370   char *s = (char *) arg;
371   size_t l = strlen (s);
372   char *p;
373 
374   shape_opts->num_features = 0;
375   g_free (shape_opts->features);
376   shape_opts->features = nullptr;
377 
378   /* if the string is quoted, strip the quotes */
379   if (s[0] == s[l - 1] && (s[0] == '\"' || s[0] == '\''))
380   {
381     s[l - 1] = '\0';
382     s++;
383   }
384 
385   if (!*s)
386     return true;
387 
388   /* count the features first, so we can allocate memory */
389   p = s;
390   do {
391     shape_opts->num_features++;
392     p = strchr (p, ',');
393     if (p)
394       p++;
395   } while (p);
396 
397   shape_opts->features = (hb_feature_t *) calloc (shape_opts->num_features, sizeof (*shape_opts->features));
398   if (!shape_opts->features)
399     return false;
400 
401   /* now do the actual parsing */
402   p = s;
403   shape_opts->num_features = 0;
404   while (p && *p) {
405     char *end = strchr (p, ',');
406     if (hb_feature_from_string (p, end ? end - p : -1, &shape_opts->features[shape_opts->num_features]))
407       shape_opts->num_features++;
408     p = end ? end + 1 : nullptr;
409   }
410 
411   return true;
412 }
413 
414 void
add_options(option_parser_t * parser)415 shape_options_t::add_options (option_parser_t *parser)
416 {
417   GOptionEntry entries[] =
418   {
419     {"list-shapers",	0, G_OPTION_FLAG_NO_ARG,
420 			      G_OPTION_ARG_CALLBACK,	(gpointer) &list_shapers,	"List available shapers and quit",	nullptr},
421     {"shaper",		0, G_OPTION_FLAG_HIDDEN,
422 			      G_OPTION_ARG_CALLBACK,	(gpointer) &parse_shapers,	"Hidden duplicate of --shapers",	nullptr},
423     {"shapers",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_shapers,	"Set comma-separated list of shapers to try","list"},
424     {"direction",	0, 0, G_OPTION_ARG_STRING,	&this->direction,		"Set text direction (default: auto)",	"ltr/rtl/ttb/btt"},
425     {"language",	0, 0, G_OPTION_ARG_STRING,	&this->language,		"Set text language (default: $LANG)",	"BCP 47 tag"},
426     {"script",		0, 0, G_OPTION_ARG_STRING,	&this->script,			"Set text script (default: auto)",	"ISO-15924 tag"},
427     {"bot",		0, 0, G_OPTION_ARG_NONE,	&this->bot,			"Treat text as beginning-of-paragraph",	nullptr},
428     {"eot",		0, 0, G_OPTION_ARG_NONE,	&this->eot,			"Treat text as end-of-paragraph",	nullptr},
429     {"preserve-default-ignorables",0, 0, G_OPTION_ARG_NONE,	&this->preserve_default_ignorables,	"Preserve Default-Ignorable characters",	nullptr},
430     {"remove-default-ignorables",0, 0, G_OPTION_ARG_NONE,	&this->remove_default_ignorables,	"Remove Default-Ignorable characters",	nullptr},
431     {"invisible-glyph",	0, 0, G_OPTION_ARG_INT,		&this->invisible_glyph,		"Glyph value to replace Default-Ignorables with",	nullptr},
432     {"not-found-glyph",	0, 0, G_OPTION_ARG_INT,		&this->not_found_glyph,		"Glyph value to replace not-found characters with",	nullptr},
433     {"utf8-clusters",	0, 0, G_OPTION_ARG_NONE,	&this->utf8_clusters,		"Use UTF8 byte indices, not char indices",	nullptr},
434     {"cluster-level",	0, 0, G_OPTION_ARG_INT,		&this->cluster_level,		"Cluster merging level (default: 0)",	"0/1/2"},
435     {"normalize-glyphs",0, 0, G_OPTION_ARG_NONE,	&this->normalize_glyphs,	"Rearrange glyph clusters in nominal order",	nullptr},
436     {"verify",		0, 0, G_OPTION_ARG_NONE,	&this->verify,			"Perform sanity checks on shaping results",	nullptr},
437     {"num-iterations", 'n', G_OPTION_FLAG_IN_MAIN,
438 			      G_OPTION_ARG_INT,		&this->num_iterations,		"Run shaper N times (default: 1)",	"N"},
439     {nullptr}
440   };
441   parser->add_group (entries,
442 		     "shape",
443 		     "Shape options:",
444 		     "Options for the shaping process",
445 		     this);
446 
447   const gchar *features_help = "Comma-separated list of font features\n"
448     "\n"
449     "    Features can be enabled or disabled, either globally or limited to\n"
450     "    specific character ranges.  The format for specifying feature settings\n"
451     "    follows.  All valid CSS font-feature-settings values other than 'normal'\n"
452     "    and the global values are also accepted, though not documented below.\n"
453     "    CSS string escapes are not supported."
454     "\n"
455     "    The range indices refer to the positions between Unicode characters,\n"
456     "    unless the --utf8-clusters is provided, in which case range indices\n"
457     "    refer to UTF-8 byte indices. The position before the first character\n"
458     "    is always 0.\n"
459     "\n"
460     "    The format is Python-esque.  Here is how it all works:\n"
461     "\n"
462     "      Syntax:       Value:    Start:    End:\n"
463     "\n"
464     "    Setting value:\n"
465     "      \"kern\"        1         0         ∞         # Turn feature on\n"
466     "      \"+kern\"       1         0         ∞         # Turn feature on\n"
467     "      \"-kern\"       0         0         ∞         # Turn feature off\n"
468     "      \"kern=0\"      0         0         ∞         # Turn feature off\n"
469     "      \"kern=1\"      1         0         ∞         # Turn feature on\n"
470     "      \"aalt=2\"      2         0         ∞         # Choose 2nd alternate\n"
471     "\n"
472     "    Setting index:\n"
473     "      \"kern[]\"      1         0         ∞         # Turn feature on\n"
474     "      \"kern[:]\"     1         0         ∞         # Turn feature on\n"
475     "      \"kern[5:]\"    1         5         ∞         # Turn feature on, partial\n"
476     "      \"kern[:5]\"    1         0         5         # Turn feature on, partial\n"
477     "      \"kern[3:5]\"   1         3         5         # Turn feature on, range\n"
478     "      \"kern[3]\"     1         3         3+1       # Turn feature on, single char\n"
479     "\n"
480     "    Mixing it all:\n"
481     "\n"
482     "      \"aalt[3:5]=2\" 2         3         5         # Turn 2nd alternate on for range";
483 
484   GOptionEntry entries2[] =
485   {
486     {"features",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_features,	features_help,	"list"},
487     {nullptr}
488   };
489   parser->add_group (entries2,
490 		     "features",
491 		     "Features options:",
492 		     "Options for font features used",
493 		     this);
494 }
495 
496 #endif
497