1/*
2 * Copyright © 2012  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 HB_OT_SHAPE_COMPLEX_ARABIC_FALLBACK_HH
28#define HB_OT_SHAPE_COMPLEX_ARABIC_FALLBACK_HH
29
30#include "hb-private.hh"
31
32#include "hb-ot-shape-private.hh"
33#include "hb-ot-layout-gsub-table.hh"
34
35
36/* Features ordered the same as the entries in shaping_table rows,
37 * followed by rlig.  Don't change. */
38static const hb_tag_t arabic_fallback_features[] =
39{
40  HB_TAG('i','n','i','t'),
41  HB_TAG('m','e','d','i'),
42  HB_TAG('f','i','n','a'),
43  HB_TAG('i','s','o','l'),
44  HB_TAG('r','l','i','g'),
45};
46
47static OT::SubstLookup *
48arabic_fallback_synthesize_lookup_single (const hb_ot_shape_plan_t *plan HB_UNUSED,
49					  hb_font_t *font,
50					  unsigned int feature_index)
51{
52  OT::GlyphID glyphs[SHAPING_TABLE_LAST - SHAPING_TABLE_FIRST + 1];
53  OT::GlyphID substitutes[SHAPING_TABLE_LAST - SHAPING_TABLE_FIRST + 1];
54  unsigned int num_glyphs = 0;
55
56  /* Populate arrays */
57  for (hb_codepoint_t u = SHAPING_TABLE_FIRST; u < SHAPING_TABLE_LAST + 1; u++)
58  {
59    hb_codepoint_t s = shaping_table[u - SHAPING_TABLE_FIRST][feature_index];
60    hb_codepoint_t u_glyph, s_glyph;
61
62    if (!s ||
63	!hb_font_get_glyph (font, u, 0, &u_glyph) ||
64	!hb_font_get_glyph (font, s, 0, &s_glyph) ||
65	u_glyph == s_glyph ||
66	u_glyph > 0xFFFFu || s_glyph > 0xFFFFu)
67      continue;
68
69    glyphs[num_glyphs].set (u_glyph);
70    substitutes[num_glyphs].set (s_glyph);
71
72    num_glyphs++;
73  }
74
75  if (!num_glyphs)
76    return NULL;
77
78  /* Bubble-sort!
79   * May not be good-enough for presidential candidate interviews, but good-enough for us... */
80  hb_bubble_sort (&glyphs[0], num_glyphs, OT::GlyphID::cmp, &substitutes[0]);
81
82  OT::Supplier<OT::GlyphID> glyphs_supplier      (glyphs, num_glyphs);
83  OT::Supplier<OT::GlyphID> substitutes_supplier (substitutes, num_glyphs);
84
85  /* Each glyph takes four bytes max, and there's some overhead. */
86  char buf[(SHAPING_TABLE_LAST - SHAPING_TABLE_FIRST + 1) * 4 + 128];
87  OT::hb_serialize_context_t c (buf, sizeof (buf));
88  OT::SubstLookup *lookup = c.start_serialize<OT::SubstLookup> ();
89  bool ret = lookup->serialize_single (&c,
90				       OT::LookupFlag::IgnoreMarks,
91				       glyphs_supplier,
92				       substitutes_supplier,
93				       num_glyphs);
94  c.end_serialize ();
95  /* TODO sanitize the results? */
96
97  return ret ? c.copy<OT::SubstLookup> () : NULL;
98}
99
100static OT::SubstLookup *
101arabic_fallback_synthesize_lookup_ligature (const hb_ot_shape_plan_t *plan HB_UNUSED,
102					    hb_font_t *font)
103{
104  OT::GlyphID first_glyphs[ARRAY_LENGTH_CONST (ligature_table)];
105  unsigned int first_glyphs_indirection[ARRAY_LENGTH_CONST (ligature_table)];
106  unsigned int ligature_per_first_glyph_count_list[ARRAY_LENGTH_CONST (first_glyphs)];
107  unsigned int num_first_glyphs = 0;
108
109  /* We know that all our ligatures are 2-component */
110  OT::GlyphID ligature_list[ARRAY_LENGTH_CONST (first_glyphs) * ARRAY_LENGTH_CONST(ligature_table[0].ligatures)];
111  unsigned int component_count_list[ARRAY_LENGTH_CONST (ligature_list)];
112  OT::GlyphID component_list[ARRAY_LENGTH_CONST (ligature_list) * 1/* One extra component per ligature */];
113  unsigned int num_ligatures = 0;
114
115  /* Populate arrays */
116
117  /* Sort out the first-glyphs */
118  for (unsigned int first_glyph_idx = 0; first_glyph_idx < ARRAY_LENGTH (first_glyphs); first_glyph_idx++)
119  {
120    hb_codepoint_t first_u = ligature_table[first_glyph_idx].first;
121    hb_codepoint_t first_glyph;
122    if (!hb_font_get_glyph (font, first_u, 0, &first_glyph))
123      continue;
124    first_glyphs[num_first_glyphs].set (first_glyph);
125    ligature_per_first_glyph_count_list[num_first_glyphs] = 0;
126    first_glyphs_indirection[num_first_glyphs] = first_glyph_idx;
127    num_first_glyphs++;
128  }
129  hb_bubble_sort (&first_glyphs[0], num_first_glyphs, OT::GlyphID::cmp, &first_glyphs_indirection[0]);
130
131  /* Now that the first-glyphs are sorted, walk again, populate ligatures. */
132  for (unsigned int i = 0; i < num_first_glyphs; i++)
133  {
134    unsigned int first_glyph_idx = first_glyphs_indirection[i];
135
136    for (unsigned int second_glyph_idx = 0; second_glyph_idx < ARRAY_LENGTH (ligature_table[0].ligatures); second_glyph_idx++)
137    {
138      hb_codepoint_t second_u   = ligature_table[first_glyph_idx].ligatures[second_glyph_idx].second;
139      hb_codepoint_t ligature_u = ligature_table[first_glyph_idx].ligatures[second_glyph_idx].ligature;
140      hb_codepoint_t second_glyph, ligature_glyph;
141      if (!second_u ||
142	  !hb_font_get_glyph (font, second_u,   0, &second_glyph) ||
143	  !hb_font_get_glyph (font, ligature_u, 0, &ligature_glyph))
144	continue;
145
146      ligature_per_first_glyph_count_list[i]++;
147
148      ligature_list[num_ligatures].set (ligature_glyph);
149      component_count_list[num_ligatures] = 2;
150      component_list[num_ligatures].set (second_glyph);
151      num_ligatures++;
152    }
153  }
154
155  if (!num_ligatures)
156    return NULL;
157
158  OT::Supplier<OT::GlyphID>   first_glyphs_supplier                      (first_glyphs, num_first_glyphs);
159  OT::Supplier<unsigned int > ligature_per_first_glyph_count_supplier    (ligature_per_first_glyph_count_list, num_first_glyphs);
160  OT::Supplier<OT::GlyphID>   ligatures_supplier                         (ligature_list, num_ligatures);
161  OT::Supplier<unsigned int > component_count_supplier                   (component_count_list, num_ligatures);
162  OT::Supplier<OT::GlyphID>   component_supplier                         (component_list, num_ligatures);
163
164  /* 16 bytes per ligature ought to be enough... */
165  char buf[ARRAY_LENGTH_CONST (ligature_list) * 16 + 128];
166  OT::hb_serialize_context_t c (buf, sizeof (buf));
167  OT::SubstLookup *lookup = c.start_serialize<OT::SubstLookup> ();
168  bool ret = lookup->serialize_ligature (&c,
169					 OT::LookupFlag::IgnoreMarks,
170					 first_glyphs_supplier,
171					 ligature_per_first_glyph_count_supplier,
172					 num_first_glyphs,
173					 ligatures_supplier,
174					 component_count_supplier,
175					 component_supplier);
176
177  c.end_serialize ();
178  /* TODO sanitize the results? */
179
180  return ret ? c.copy<OT::SubstLookup> () : NULL;
181}
182
183static OT::SubstLookup *
184arabic_fallback_synthesize_lookup (const hb_ot_shape_plan_t *plan,
185				   hb_font_t *font,
186				   unsigned int feature_index)
187{
188  if (feature_index < 4)
189    return arabic_fallback_synthesize_lookup_single (plan, font, feature_index);
190  else
191    return arabic_fallback_synthesize_lookup_ligature (plan, font);
192}
193
194#define ARABIC_FALLBACK_MAX_LOOKUPS 5
195
196struct arabic_fallback_plan_t
197{
198  ASSERT_POD ();
199
200  unsigned int num_lookups;
201  bool free_lookups;
202
203  hb_mask_t mask_array[ARABIC_FALLBACK_MAX_LOOKUPS];
204  OT::SubstLookup *lookup_array[ARABIC_FALLBACK_MAX_LOOKUPS];
205  hb_ot_layout_lookup_accelerator_t accel_array[ARABIC_FALLBACK_MAX_LOOKUPS];
206};
207
208static const arabic_fallback_plan_t arabic_fallback_plan_nil = {};
209
210#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(HB_NO_WIN1256)
211#define HB_WITH_WIN1256
212#endif
213
214#ifdef HB_WITH_WIN1256
215#include "hb-ot-shape-complex-arabic-win1256.hh"
216#endif
217
218struct ManifestLookup {
219  OT::Tag tag;
220  OT::OffsetTo<OT::SubstLookup> lookupOffset;
221};
222typedef OT::ArrayOf<ManifestLookup> Manifest;
223
224static bool
225arabic_fallback_plan_init_win1256 (arabic_fallback_plan_t *fallback_plan,
226				   const hb_ot_shape_plan_t *plan,
227				   hb_font_t *font)
228{
229#ifdef HB_WITH_WIN1256
230  /* Does this font look like it's Windows-1256-encoded? */
231  hb_codepoint_t g;
232  if (!(hb_font_get_glyph (font, 0x0627u, 0, &g) && g == 199 /* ALEF */ &&
233	hb_font_get_glyph (font, 0x0644u, 0, &g) && g == 225 /* LAM */ &&
234	hb_font_get_glyph (font, 0x0649u, 0, &g) && g == 236 /* ALEF MAKSURA */ &&
235	hb_font_get_glyph (font, 0x064Au, 0, &g) && g == 237 /* YEH */ &&
236	hb_font_get_glyph (font, 0x0652u, 0, &g) && g == 250 /* SUKUN */))
237    return false;
238
239  const Manifest &manifest = reinterpret_cast<const Manifest&> (arabic_win1256_gsub_lookups.manifest);
240  ASSERT_STATIC (sizeof (arabic_win1256_gsub_lookups.manifestData) / sizeof (ManifestLookup)
241		 <= ARABIC_FALLBACK_MAX_LOOKUPS);
242  /* TODO sanitize the table? */
243
244  unsigned j = 0;
245  unsigned int count = manifest.len;
246  for (unsigned int i = 0; i < count; i++)
247  {
248    fallback_plan->mask_array[j] = plan->map.get_1_mask (manifest[i].tag);
249    if (fallback_plan->mask_array[j])
250    {
251      fallback_plan->lookup_array[j] = const_cast<OT::SubstLookup*> (&(&manifest+manifest[i].lookupOffset));
252      if (fallback_plan->lookup_array[j])
253      {
254	fallback_plan->accel_array[j].init (*fallback_plan->lookup_array[j]);
255	j++;
256      }
257    }
258  }
259
260  fallback_plan->num_lookups = j;
261  fallback_plan->free_lookups = false;
262
263  return j > 0;
264#else
265  return false;
266#endif
267}
268
269static bool
270arabic_fallback_plan_init_unicode (arabic_fallback_plan_t *fallback_plan,
271				   const hb_ot_shape_plan_t *plan,
272				   hb_font_t *font)
273{
274  ASSERT_STATIC (ARRAY_LENGTH_CONST(arabic_fallback_features) <= ARABIC_FALLBACK_MAX_LOOKUPS);
275  unsigned int j = 0;
276  for (unsigned int i = 0; i < ARRAY_LENGTH(arabic_fallback_features) ; i++)
277  {
278    fallback_plan->mask_array[j] = plan->map.get_1_mask (arabic_fallback_features[i]);
279    if (fallback_plan->mask_array[j])
280    {
281      fallback_plan->lookup_array[j] = arabic_fallback_synthesize_lookup (plan, font, i);
282      if (fallback_plan->lookup_array[j])
283      {
284	fallback_plan->accel_array[j].init (*fallback_plan->lookup_array[j]);
285	j++;
286      }
287    }
288  }
289
290  fallback_plan->num_lookups = j;
291  fallback_plan->free_lookups = true;
292
293  return j > 0;
294}
295
296static arabic_fallback_plan_t *
297arabic_fallback_plan_create (const hb_ot_shape_plan_t *plan,
298			     hb_font_t *font)
299{
300  arabic_fallback_plan_t *fallback_plan = (arabic_fallback_plan_t *) calloc (1, sizeof (arabic_fallback_plan_t));
301  if (unlikely (!fallback_plan))
302    return const_cast<arabic_fallback_plan_t *> (&arabic_fallback_plan_nil);
303
304  fallback_plan->num_lookups = 0;
305  fallback_plan->free_lookups = false;
306
307  /* Try synthesizing GSUB table using Unicode Arabic Presentation Forms,
308   * in case the font has cmap entries for the presentation-forms characters. */
309  if (arabic_fallback_plan_init_unicode (fallback_plan, plan, font))
310    return fallback_plan;
311
312  /* See if this looks like a Windows-1256-encoded font.  If it does, use a
313   * hand-coded GSUB table. */
314  if (arabic_fallback_plan_init_win1256 (fallback_plan, plan, font))
315    return fallback_plan;
316
317  free (fallback_plan);
318  return const_cast<arabic_fallback_plan_t *> (&arabic_fallback_plan_nil);
319}
320
321static void
322arabic_fallback_plan_destroy (arabic_fallback_plan_t *fallback_plan)
323{
324  if (!fallback_plan || fallback_plan == &arabic_fallback_plan_nil)
325    return;
326
327  for (unsigned int i = 0; i < fallback_plan->num_lookups; i++)
328    if (fallback_plan->lookup_array[i])
329    {
330      fallback_plan->accel_array[i].fini (fallback_plan->lookup_array[i]);
331      if (fallback_plan->free_lookups)
332	free (fallback_plan->lookup_array[i]);
333    }
334
335  free (fallback_plan);
336}
337
338static void
339arabic_fallback_plan_shape (arabic_fallback_plan_t *fallback_plan,
340			    hb_font_t *font,
341			    hb_buffer_t *buffer)
342{
343  OT::hb_apply_context_t c (0, font, buffer);
344  for (unsigned int i = 0; i < fallback_plan->num_lookups; i++)
345    if (fallback_plan->lookup_array[i]) {
346      c.set_lookup_mask (fallback_plan->mask_array[i]);
347      hb_ot_layout_substitute_lookup (&c,
348				      *fallback_plan->lookup_array[i],
349				      fallback_plan->accel_array[i]);
350    }
351}
352
353
354#endif /* HB_OT_SHAPE_COMPLEX_ARABIC_FALLBACK_HH */
355