Imported Upstream version 2.6.7
[platform/upstream/harfbuzz.git] / src / hb-ot-cmap-table.hh
index 9ebd8e4..c42f3fd 100644 (file)
@@ -56,6 +56,18 @@ struct CmapSubtableFormat0
        out->add (i);
   }
 
+  void collect_mapping (hb_set_t *unicodes, /* OUT */
+                        hb_map_t *mapping /* OUT */) const
+  {
+    for (unsigned i = 0; i < 256; i++)
+      if (glyphIdArray[i])
+      {
+        hb_codepoint_t glyph = glyphIdArray[i];
+        unicodes->add (i);
+        mapping->set (i, glyph);
+      }
+  }
+
   bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
@@ -83,18 +95,16 @@ struct CmapSubtableFormat4
     HBUINT16 *endCode = c->start_embed<HBUINT16> ();
     hb_codepoint_t prev_endcp = 0xFFFF;
 
-    + it
-    | hb_apply ([&] (const hb_item_type<Iterator> _)
-               {
-                 if (prev_endcp != 0xFFFF && prev_endcp + 1u != _.first)
-                 {
-                   HBUINT16 end_code;
-                   end_code = prev_endcp;
-                   c->copy<HBUINT16> (end_code);
-                 }
-                 prev_endcp = _.first;
-               })
-    ;
+    for (const hb_item_type<Iterator> _ : +it)
+    {
+      if (prev_endcp != 0xFFFF && prev_endcp + 1u != _.first)
+      {
+       HBUINT16 end_code;
+       end_code = prev_endcp;
+       c->copy<HBUINT16> (end_code);
+      }
+      prev_endcp = _.first;
+    }
 
     {
       // last endCode
@@ -121,19 +131,17 @@ struct CmapSubtableFormat4
     HBUINT16 *startCode = c->start_embed<HBUINT16> ();
     hb_codepoint_t prev_cp = 0xFFFF;
 
-    + it
-    | hb_apply ([&] (const hb_item_type<Iterator> _)
-               {
-                 if (prev_cp == 0xFFFF || prev_cp + 1u != _.first)
-                 {
-                   HBUINT16 start_code;
-                   start_code = _.first;
-                   c->copy<HBUINT16> (start_code);
-                 }
-
-                 prev_cp = _.first;
-               })
-    ;
+    for (const hb_item_type<Iterator> _ : +it)
+    {
+      if (prev_cp == 0xFFFF || prev_cp + 1u != _.first)
+      {
+       HBUINT16 start_code;
+       start_code = _.first;
+       c->copy<HBUINT16> (start_code);
+      }
+
+      prev_cp = _.first;
+    }
 
     // There must be a final entry with end_code == 0xFFFF.
     if (it.len () == 0 || prev_cp != 0xFFFF)
@@ -162,30 +170,28 @@ struct CmapSubtableFormat4
     if ((char *)idDelta - (char *)startCode != (int) segcount * (int) HBINT16::static_size)
       return nullptr;
 
-    + it
-    | hb_apply ([&] (const hb_item_type<Iterator> _)
-               {
-                 if (_.first == startCode[i])
-                 {
-                   use_delta = true;
-                   start_gid = _.second;
-                 }
-                 else if (_.second != last_gid + 1) use_delta = false;
-
-                 if (_.first == endCode[i])
-                 {
-                   HBINT16 delta;
-                   if (use_delta) delta = (int)start_gid - (int)startCode[i];
-                   else delta = 0;
-                   c->copy<HBINT16> (delta);
-
-                   i++;
-                 }
-
-                 last_gid = _.second;
-                 last_cp = _.first;
-               })
-    ;
+    for (const hb_item_type<Iterator> _ : +it)
+    {
+      if (_.first == startCode[i])
+      {
+       use_delta = true;
+       start_gid = _.second;
+      }
+      else if (_.second != last_gid + 1) use_delta = false;
+
+      if (_.first == endCode[i])
+      {
+       HBINT16 delta;
+       if (use_delta) delta = (int)start_gid - (int)startCode[i];
+       else delta = 0;
+       c->copy<HBINT16> (delta);
+
+       i++;
+      }
+
+      last_gid = _.second;
+      last_cp = _.first;
+    }
 
     if (it.len () == 0 || last_cp != 0xFFFF)
     {
@@ -238,12 +244,20 @@ struct CmapSubtableFormat4
   void serialize (hb_serialize_context_t *c,
                  Iterator it)
   {
+    auto format4_iter =
+    + it
+    | hb_filter ([&] (const hb_pair_t<hb_codepoint_t, hb_codepoint_t> _)
+                { return _.first <= 0xFFFF; })
+    ;
+
+    if (format4_iter.len () == 0) return;
+
     unsigned table_initpos = c->length ();
     if (unlikely (!c->extend_min (*this))) return;
     this->format = 4;
 
     //serialize endCode[]
-    HBUINT16 *endCode = serialize_endcode_array (c, it);
+    HBUINT16 *endCode = serialize_endcode_array (c, format4_iter);
     if (unlikely (!endCode)) return;
 
     unsigned segcount = (c->length () - min_size) / HBUINT16::static_size;
@@ -252,14 +266,14 @@ struct CmapSubtableFormat4
     if (unlikely (!c->allocate_size<HBUINT16> (HBUINT16::static_size))) return; // 2 bytes of padding.
 
    // serialize startCode[]
-    HBUINT16 *startCode = serialize_startcode_array (c, it);
+    HBUINT16 *startCode = serialize_startcode_array (c, format4_iter);
     if (unlikely (!startCode)) return;
 
     //serialize idDelta[]
-    HBINT16 *idDelta = serialize_idDelta_array (c, it, endCode, startCode, segcount);
+    HBINT16 *idDelta = serialize_idDelta_array (c, format4_iter, endCode, startCode, segcount);
     if (unlikely (!idDelta)) return;
 
-    HBUINT16 *idRangeOffset = serialize_rangeoffset_glyid (c, it, endCode, startCode, idDelta, segcount);
+    HBUINT16 *idRangeOffset = serialize_rangeoffset_glyid (c, format4_iter, endCode, startCode, idDelta, segcount);
     if (unlikely (!c->check_success (idRangeOffset))) return;
 
     if (unlikely (!c->check_assign(this->length, c->length () - table_initpos))) return;
@@ -291,27 +305,28 @@ struct CmapSubtableFormat4
 
     bool get_glyph (hb_codepoint_t codepoint, hb_codepoint_t *glyph) const
     {
-      /* Custom two-array bsearch. */
-      int min = 0, max = (int) this->segCount - 1;
-      const HBUINT16 *startCount = this->startCount;
-      const HBUINT16 *endCount = this->endCount;
-      unsigned int i;
-      while (min <= max)
+      struct CustomRange
       {
-       int mid = ((unsigned int) min + (unsigned int) max) / 2;
-       if (codepoint < startCount[mid])
-         max = mid - 1;
-       else if (codepoint > endCount[mid])
-         min = mid + 1;
-       else
+       int cmp (hb_codepoint_t k,
+                unsigned distance) const
        {
-         i = mid;
-         goto found;
+         if (k > last) return +1;
+         if (k < (&last)[distance]) return -1;
+         return 0;
        }
-      }
-      return false;
+       HBUINT16 last;
+      };
+
+      const HBUINT16 *found =hb_bsearch (codepoint,
+                                        this->endCount,
+                                        this->segCount,
+                                        2,
+                                        _hb_cmp_method<hb_codepoint_t, CustomRange, unsigned>,
+                                        this->segCount + 1);
+      if (!found)
+       return false;
+      unsigned int i = found - endCount;
 
-    found:
       hb_codepoint_t gid;
       unsigned int rangeOffset = this->idRangeOffset[i];
       if (rangeOffset == 0)
@@ -333,8 +348,10 @@ struct CmapSubtableFormat4
       *glyph = gid;
       return true;
     }
+
     HB_INTERNAL static bool get_glyph_func (const void *obj, hb_codepoint_t codepoint, hb_codepoint_t *glyph)
     { return ((const accelerator_t *) obj)->get_glyph (codepoint, glyph); }
+
     void collect_unicodes (hb_set_t *out) const
     {
       unsigned int count = this->segCount;
@@ -371,6 +388,45 @@ struct CmapSubtableFormat4
       }
     }
 
+    void collect_mapping (hb_set_t *unicodes, /* OUT */
+                          hb_map_t *mapping /* OUT */) const
+    {
+      unsigned count = this->segCount;
+      if (count && this->startCount[count - 1] == 0xFFFFu)
+        count--; /* Skip sentinel segment. */
+      for (unsigned i = 0; i < count; i++)
+      {
+        hb_codepoint_t start = this->startCount[i];
+        hb_codepoint_t end = this->endCount[i];
+        unsigned rangeOffset = this->idRangeOffset[i];
+        if (rangeOffset == 0)
+        {
+          for (hb_codepoint_t codepoint = start; codepoint <= end; codepoint++)
+          {
+            hb_codepoint_t gid = (codepoint + this->idDelta[i]) & 0xFFFFu;
+            if (unlikely (!gid))
+              continue;
+            unicodes->add (codepoint);
+            mapping->set (codepoint, gid);
+          }
+        }
+        else
+        {
+          for (hb_codepoint_t codepoint = start; codepoint <= end; codepoint++)
+          {
+            unsigned index = rangeOffset / 2 + (codepoint - this->startCount[i]) + i - this->segCount;
+            if (unlikely (index >= this->glyphIdArrayLength))
+              break;
+            hb_codepoint_t gid = this->glyphIdArray[index];
+            if (unlikely (!gid))
+              continue;
+            unicodes->add (codepoint);
+            mapping->set (codepoint, gid);
+          }
+        }
+      }
+    }
+
     const HBUINT16 *endCount;
     const HBUINT16 *startCount;
     const HBUINT16 *idDelta;
@@ -391,6 +447,13 @@ struct CmapSubtableFormat4
     accel.collect_unicodes (out);
   }
 
+  void collect_mapping (hb_set_t *unicodes, /* OUT */
+                        hb_map_t *mapping /* OUT */) const
+  {
+    accelerator_t accel (this);
+    accel.collect_mapping (unicodes, mapping);
+  }
+
   bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
@@ -493,6 +556,21 @@ struct CmapSubtableTrimmed
        out->add (start + i);
   }
 
+  void collect_mapping (hb_set_t *unicodes, /* OUT */
+                        hb_map_t *mapping /* OUT */) const
+  {
+    hb_codepoint_t start_cp = startCharCode;
+    unsigned count = glyphIdArray.len;
+    for (unsigned i = 0; i < count; i++)
+      if (glyphIdArray[i])
+      {
+        hb_codepoint_t unicode = start_cp + i;
+        hb_codepoint_t glyphid = glyphIdArray[i];
+        unicodes->add (unicode);
+        mapping->set (unicode, glyphid);
+      }
+  }
+
   bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
@@ -528,19 +606,55 @@ struct CmapSubtableLongSegmented
     return true;
   }
 
-  void collect_unicodes (hb_set_t *out) const
+  void collect_unicodes (hb_set_t *out, unsigned int num_glyphs) const
   {
     for (unsigned int i = 0; i < this->groups.len; i++)
     {
       hb_codepoint_t start = this->groups[i].startCharCode;
       hb_codepoint_t end = hb_min ((hb_codepoint_t) this->groups[i].endCharCode,
                                   (hb_codepoint_t) HB_UNICODE_MAX);
-      for (hb_codepoint_t codepoint = start; codepoint <= end; codepoint++)
+      hb_codepoint_t gid = this->groups[i].glyphID;
+      if (!gid)
       {
-       hb_codepoint_t gid = T::group_get_glyph (this->groups[i], codepoint);
-       if (unlikely (!gid))
-         continue;
-       out->add (codepoint);
+       /* Intention is: if (hb_is_same (T, CmapSubtableFormat13)) continue; */
+       if (! T::group_get_glyph (this->groups[i], end)) continue;
+       start++;
+       gid++;
+      }
+      if (unlikely ((unsigned int) gid >= num_glyphs)) continue;
+      if (unlikely ((unsigned int) (gid + end - start) >= num_glyphs))
+       end = start + (hb_codepoint_t) num_glyphs - gid;
+
+      out->add_range (start, end);
+    }
+  }
+
+  void collect_mapping (hb_set_t *unicodes, /* OUT */
+                        hb_map_t *mapping, /* OUT */
+                        unsigned num_glyphs) const
+  {
+    for (unsigned i = 0; i < this->groups.len; i++)
+    {
+      hb_codepoint_t start = this->groups[i].startCharCode;
+      hb_codepoint_t end = hb_min ((hb_codepoint_t) this->groups[i].endCharCode,
+                                   (hb_codepoint_t) HB_UNICODE_MAX);
+      hb_codepoint_t gid = this->groups[i].glyphID;
+      if (!gid)
+      {
+        /* Intention is: if (hb_is_same (T, CmapSubtableFormat13)) continue; */
+        if (! T::group_get_glyph (this->groups[i], end)) continue;
+        start++;
+        gid++;
+      }
+      if (unlikely ((unsigned int) gid >= num_glyphs)) continue;
+      if (unlikely ((unsigned int) (gid + end - start) >= num_glyphs))
+        end = start + (hb_codepoint_t) num_glyphs - gid;
+
+      for (unsigned cp = start; cp <= end; cp++)
+      {
+        unicodes->add (cp);
+        mapping->set (cp, gid);
+        gid++;
       }
     }
   }
@@ -582,33 +696,29 @@ struct CmapSubtableFormat12 : CmapSubtableLongSegmented<CmapSubtableFormat12>
     hb_codepoint_t startCharCode = 0xFFFF, endCharCode = 0xFFFF;
     hb_codepoint_t glyphID = 0;
 
-    + it
-    | hb_apply ([&] (const hb_item_type<Iterator> _)
-             {
-               if (startCharCode == 0xFFFF)
-               {
-                 startCharCode = _.first;
-                 endCharCode = _.first;
-                 glyphID = _.second;
-               }
-               else if (!_is_gid_consecutive (endCharCode, startCharCode, glyphID, _.first, _.second))
-               {
-                 CmapSubtableLongGroup  grouprecord;
-                 grouprecord.startCharCode = startCharCode;
-                 grouprecord.endCharCode = endCharCode;
-                 grouprecord.glyphID = glyphID;
-                 c->copy<CmapSubtableLongGroup> (grouprecord);
-
-                 startCharCode = _.first;
-                 endCharCode = _.first;
-                 glyphID = _.second;
-               }
-               else
-               {
-                 endCharCode = _.first;
-               }
-             })
-    ;
+    for (const hb_item_type<Iterator> _ : +it)
+    {
+      if (startCharCode == 0xFFFF)
+      {
+       startCharCode = _.first;
+       endCharCode = _.first;
+       glyphID = _.second;
+      }
+      else if (!_is_gid_consecutive (endCharCode, startCharCode, glyphID, _.first, _.second))
+      {
+       CmapSubtableLongGroup  grouprecord;
+       grouprecord.startCharCode = startCharCode;
+       grouprecord.endCharCode = endCharCode;
+       grouprecord.glyphID = glyphID;
+       c->copy<CmapSubtableLongGroup> (grouprecord);
+
+       startCharCode = _.first;
+       endCharCode = _.first;
+       glyphID = _.second;
+      }
+      else
+       endCharCode = _.first;
+    }
 
     CmapSubtableLongGroup record;
     record.startCharCode = startCharCode;
@@ -772,7 +882,20 @@ struct NonDefaultUVS : SortedArrayOf<UVSMapping, HBUINT32>
   {
     unsigned int count = len;
     for (unsigned int i = 0; i < count; i++)
-      out->add (arrayZ[i].glyphID);
+      out->add (arrayZ[i].unicodeValue);
+  }
+
+  void collect_mapping (hb_set_t *unicodes, /* OUT */
+                       hb_map_t *mapping /* OUT */) const
+  {
+    unsigned count = len;
+    for (unsigned i = 0; i < count; i++)
+    {
+      hb_codepoint_t unicode = arrayZ[i].unicodeValue;
+      hb_codepoint_t glyphid = arrayZ[i].glyphID;
+      unicodes->add (unicode);
+      mapping->set (unicode, glyphid);
+    }
   }
 
   void closure_glyphs (const hb_set_t      *unicodes,
@@ -787,7 +910,7 @@ struct NonDefaultUVS : SortedArrayOf<UVSMapping, HBUINT32>
 
   NonDefaultUVS* copy (hb_serialize_context_t *c,
                       const hb_set_t *unicodes,
-                      const hb_set_t *glyphs,
+                      const hb_set_t *glyphs_requested,
                       const hb_map_t *glyph_map) const
   {
     NonDefaultUVS *out = c->start_embed<NonDefaultUVS> ();
@@ -797,7 +920,7 @@ struct NonDefaultUVS : SortedArrayOf<UVSMapping, HBUINT32>
     + as_array ()
     | hb_filter ([&] (const UVSMapping& _)
                 {
-                  return unicodes->has (_.unicodeValue) || glyphs->has (_.glyphID);
+                  return unicodes->has (_.unicodeValue) || glyphs_requested->has (_.glyphID);
                 })
     ;
 
@@ -839,12 +962,34 @@ struct VariationSelectorRecord
     return GLYPH_VARIANT_NOT_FOUND;
   }
 
+  VariationSelectorRecord(const VariationSelectorRecord& other)
+  {
+    *this = other;
+  }
+
+  void operator= (const VariationSelectorRecord& other)
+  {
+    varSelector = other.varSelector;
+    HBUINT32 offset = other.defaultUVS;
+    defaultUVS = offset;
+    offset = other.nonDefaultUVS;
+    nonDefaultUVS = offset;
+  }
+
   void collect_unicodes (hb_set_t *out, const void *base) const
   {
     (base+defaultUVS).collect_unicodes (out);
     (base+nonDefaultUVS).collect_unicodes (out);
   }
 
+  void collect_mapping (const void *base,
+                       hb_set_t *unicodes, /* OUT */
+                       hb_map_t *mapping /* OUT */) const
+  {
+    (base+defaultUVS).collect_unicodes (unicodes);
+    (base+nonDefaultUVS).collect_mapping (unicodes, mapping);
+  }
+
   int cmp (const hb_codepoint_t &variation_selector) const
   { return varSelector.cmp (variation_selector); }
 
@@ -856,50 +1001,43 @@ struct VariationSelectorRecord
                  nonDefaultUVS.sanitize (c, base));
   }
 
-  VariationSelectorRecord* copy (hb_serialize_context_t *c,
-                                const hb_set_t *unicodes,
-                                const hb_set_t *glyphs,
-                                const hb_map_t *glyph_map,
-                                const void *src_base,
-                                const void *dst_base) const
+  hb_pair_t<unsigned, unsigned>
+  copy (hb_serialize_context_t *c,
+       const hb_set_t *unicodes,
+       const hb_set_t *glyphs_requested,
+       const hb_map_t *glyph_map,
+       const void *base) const
   {
     auto snap = c->snapshot ();
     auto *out = c->embed<VariationSelectorRecord> (*this);
-    if (unlikely (!out)) return nullptr;
+    if (unlikely (!out)) return hb_pair (0, 0);
 
     out->defaultUVS = 0;
     out->nonDefaultUVS = 0;
 
-    bool drop = true;
-
-    if (defaultUVS != 0)
+    unsigned non_default_uvs_objidx = 0;
+    if (nonDefaultUVS != 0)
     {
       c->push ();
-      if (c->copy (src_base+defaultUVS, unicodes))
-      {
-       c->add_link (out->defaultUVS, c->pop_pack (), dst_base);
-       drop = false;
-      }
+      if (c->copy (base+nonDefaultUVS, unicodes, glyphs_requested, glyph_map))
+       non_default_uvs_objidx = c->pop_pack ();
       else c->pop_discard ();
     }
 
-    if (nonDefaultUVS != 0)
+    unsigned default_uvs_objidx = 0;
+    if (defaultUVS != 0)
     {
       c->push ();
-      if (c->copy (src_base+nonDefaultUVS, unicodes, glyphs, glyph_map))
-      {
-       c->add_link (out->nonDefaultUVS, c->pop_pack (), dst_base);
-       drop = false;
-      }
+      if (c->copy (base+defaultUVS, unicodes))
+       default_uvs_objidx = c->pop_pack ();
       else c->pop_discard ();
     }
 
-    if (drop)
-    {
+
+    if (!default_uvs_objidx && !non_default_uvs_objidx)
       c->revert (snap);
-      return nullptr;
-    }
-    else return out;
+
+    return hb_pair (default_uvs_objidx, non_default_uvs_objidx);
   }
 
   HBUINT24     varSelector;    /* Variation selector. */
@@ -930,9 +1068,9 @@ struct CmapSubtableFormat14
 
   void serialize (hb_serialize_context_t *c,
                  const hb_set_t *unicodes,
-                 const hb_set_t *glyphs,
+                 const hb_set_t *glyphs_requested,
                  const hb_map_t *glyph_map,
-                 const void *src_base)
+                 const void *base)
   {
     auto snap = c->snapshot ();
     unsigned table_initpos = c->length ();
@@ -941,17 +1079,67 @@ struct CmapSubtableFormat14
     if (unlikely (!c->extend_min (*this))) return;
     this->format = 14;
 
-    const CmapSubtableFormat14 *src_tbl = reinterpret_cast<const CmapSubtableFormat14*> (src_base);
-    for (const VariationSelectorRecord& _ : src_tbl->record)
-      c->copy (_, unicodes, glyphs, glyph_map, src_base, this);
+    auto src_tbl = reinterpret_cast<const CmapSubtableFormat14*> (base);
+
+    /*
+     * Some versions of OTS require that offsets are in order. Due to the use
+     * of push()/pop_pack() serializing the variation records in order results
+     * in the offsets being in reverse order (first record has the largest
+     * offset). While this is perfectly valid, it will cause some versions of
+     * OTS to consider this table bad.
+     *
+     * So to prevent this issue we serialize the variation records in reverse
+     * order, so that the offsets are ordered from small to large. Since
+     * variation records are supposed to be in increasing order of varSelector
+     * we then have to reverse the order of the written variation selector
+     * records after everything is finalized.
+     */
+    hb_vector_t<hb_pair_t<unsigned, unsigned>> obj_indices;
+    for (int i = src_tbl->record.len - 1; i >= 0; i--)
+    {
+      hb_pair_t<unsigned, unsigned> result = src_tbl->record[i].copy (c, unicodes, glyphs_requested, glyph_map, base);
+      if (result.first || result.second)
+       obj_indices.push (result);
+    }
 
     if (c->length () - table_initpos == CmapSubtableFormat14::min_size)
+    {
       c->revert (snap);
-    else
+      return;
+    }
+
+    int tail_len = init_tail - c->tail;
+    c->check_assign (this->length, c->length () - table_initpos + tail_len);
+    c->check_assign (this->record.len,
+                    (c->length () - table_initpos - CmapSubtableFormat14::min_size) /
+                    VariationSelectorRecord::static_size);
+
+    /* Correct the incorrect write order by reversing the order of the variation
+       records array. */
+    _reverse_variation_records ();
+
+    /* Now that records are in the right order, we can set up the offsets. */
+    _add_links_to_variation_records (c, obj_indices);
+  }
+
+  void _reverse_variation_records ()
+  {
+    record.as_array ().reverse ();
+  }
+
+  void _add_links_to_variation_records (hb_serialize_context_t *c,
+                                       const hb_vector_t<hb_pair_t<unsigned, unsigned>>& obj_indices)
+  {
+    for (unsigned i = 0; i < obj_indices.length; i++)
     {
-      int tail_len = init_tail - c->tail;
-      c->check_assign (this->length, c->length () - table_initpos + tail_len);
-      c->check_assign (this->record.len, (c->length () - table_initpos - CmapSubtableFormat14::min_size) / VariationSelectorRecord::static_size);
+      /*
+       * Since the record array has been reversed (see comments in copy())
+       * but obj_indices has not been, the indices at obj_indices[i]
+       * are for the variation record at record[j].
+       */
+      int j = obj_indices.length - 1 - i;
+      c->add_link (record[j].defaultUVS, obj_indices[i].first);
+      c->add_link (record[j].nonDefaultUVS, obj_indices[i].second);
     }
   }
 
@@ -966,6 +1154,19 @@ struct CmapSubtableFormat14
     ;
   }
 
+  void collect_unicodes (hb_set_t *out) const
+  {
+    for (const VariationSelectorRecord& _ : record)
+      _.collect_unicodes (out, this);
+  }
+
+  void collect_mapping (hb_set_t *unicodes, /* OUT */
+                       hb_map_t *mapping /* OUT */) const
+  {
+    for (const VariationSelectorRecord& _ : record)
+      _.collect_mapping (this, unicodes, mapping);
+  }
+
   bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
@@ -1001,15 +1202,31 @@ struct CmapSubtable
     default: return false;
     }
   }
-  void collect_unicodes (hb_set_t *out) const
+  void collect_unicodes (hb_set_t *out, unsigned int num_glyphs = UINT_MAX) const
   {
     switch (u.format) {
     case  0: u.format0 .collect_unicodes (out); return;
     case  4: u.format4 .collect_unicodes (out); return;
     case  6: u.format6 .collect_unicodes (out); return;
     case 10: u.format10.collect_unicodes (out); return;
-    case 12: u.format12.collect_unicodes (out); return;
-    case 13: u.format13.collect_unicodes (out); return;
+    case 12: u.format12.collect_unicodes (out, num_glyphs); return;
+    case 13: u.format13.collect_unicodes (out, num_glyphs); return;
+    case 14:
+    default: return;
+    }
+  }
+
+  void collect_mapping (hb_set_t *unicodes, /* OUT */
+                       hb_map_t *mapping, /* OUT */
+                       unsigned num_glyphs = UINT_MAX) const
+  {
+    switch (u.format) {
+    case  0: u.format0 .collect_mapping (unicodes, mapping); return;
+    case  4: u.format4 .collect_mapping (unicodes, mapping); return;
+    case  6: u.format6 .collect_mapping (unicodes, mapping); return;
+    case 10: u.format10.collect_mapping (unicodes, mapping); return;
+    case 12: u.format12.collect_mapping (unicodes, mapping, num_glyphs); return;
+    case 13: u.format13.collect_mapping (unicodes, mapping, num_glyphs); return;
     case 14:
     default: return;
     }
@@ -1021,12 +1238,12 @@ struct CmapSubtable
                  Iterator it,
                  unsigned format,
                  const hb_subset_plan_t *plan,
-                 const void *src_base)
+                 const void *base)
   {
     switch (format) {
-    case  4: u.format4.serialize (c, it);  return;
-    case 12: u.format12.serialize (c, it); return;
-    case 14: u.format14.serialize (c, plan->unicodes, plan->_glyphset, plan->glyph_map, src_base); return;
+    case  4: return u.format4.serialize (c, it);
+    case 12: return u.format12.serialize (c, it);
+    case 14: return u.format14.serialize (c, plan->unicodes, plan->glyphs_requested, plan->glyph_map, base);
     default: return;
     }
   }
@@ -1087,8 +1304,7 @@ struct EncodingRecord
   EncodingRecord* copy (hb_serialize_context_t *c,
                        Iterator it,
                        unsigned format,
-                       const void *src_base,
-                       const void *dst_base,
+                       const void *base,
                        const hb_subset_plan_t *plan,
                        /* INOUT */ unsigned *objidx) const
   {
@@ -1102,7 +1318,7 @@ struct EncodingRecord
     {
       CmapSubtable *cmapsubtable = c->push<CmapSubtable> ();
       unsigned origin_length = c->length ();
-      cmapsubtable->serialize (c, it, format, plan, &(src_base+subtable));
+      cmapsubtable->serialize (c, it, format, plan, &(base+subtable));
       if (c->length () - origin_length > 0) *objidx = c->pop_pack ();
       else c->pop_discard ();
     }
@@ -1113,7 +1329,7 @@ struct EncodingRecord
       return_trace (nullptr);
     }
 
-    c->add_link (out->subtable, *objidx, dst_base);
+    c->add_link (out->subtable, *objidx);
     return_trace (out);
   }
 
@@ -1130,11 +1346,11 @@ struct cmap
   static constexpr hb_tag_t tableTag = HB_OT_TAG_cmap;
 
   template<typename Iterator, typename EncodingRecIter,
-          hb_requires (hb_is_iterator (Iterator))>
+          hb_requires (hb_is_iterator (EncodingRecIter))>
   void serialize (hb_serialize_context_t *c,
                  Iterator it,
                  EncodingRecIter encodingrec_iter,
-                 const void *src_base,
+                 const void *base,
                  const hb_subset_plan_t *plan)
   {
     if (unlikely (!c->extend_min ((*this))))  return;
@@ -1144,11 +1360,39 @@ struct cmap
 
     for (const EncodingRecord& _ : encodingrec_iter)
     {
-      unsigned format = (src_base+_.subtable).u.format;
+      hb_set_t unicodes_set;
+      hb_map_t cp_glyphid_map;
+      (base+_.subtable).collect_mapping (&unicodes_set, &cp_glyphid_map);
 
-      if (format == 4) c->copy (_, it, 4u, src_base, this, plan, &format4objidx);
-      else if (format == 12) c->copy (_, it, 12u, src_base, this, plan, &format12objidx);
-      else if (format == 14) c->copy (_, it, 14u, src_base, this, plan, &format14objidx);
+      unsigned format = (base+_.subtable).u.format;
+      if (!plan->glyphs_requested->is_empty ())
+      {
+        auto table_iter =
+        + hb_zip (unicodes_set.iter(), unicodes_set.iter() | hb_map(cp_glyphid_map))
+        | hb_filter (plan->_glyphset, hb_second)
+        | hb_filter ([plan] (const hb_pair_t<hb_codepoint_t, hb_codepoint_t>& p)
+                     {
+                       return plan->unicodes->has (p.first) ||
+                              plan->glyphs_requested->has (p.second);
+                     })
+        | hb_map ([plan] (const hb_pair_t<hb_codepoint_t, hb_codepoint_t>& p_org)
+                  {
+                    return hb_pair_t<hb_codepoint_t, hb_codepoint_t> (p_org.first, plan->glyph_map->get(p_org.second));
+                  })
+        ;
+  
+        if (format == 4) c->copy (_, table_iter, 4u, base, plan, &format4objidx);
+        else if (format == 12) c->copy (_, table_iter, 12u, base, plan, &format12objidx);
+        else if (format == 14) c->copy (_, table_iter, 14u, base, plan, &format14objidx);
+      }
+      /* when --gids option is not used, we iterate input unicodes instead of
+       * all codepoints in each subtable, which is more efficient */
+      else
+      {
+        if (format == 4) c->copy (_, + it | hb_filter (unicodes_set, hb_first), 4u, base, plan, &format4objidx);
+        else if (format == 12) c->copy (_, + it | hb_filter (unicodes_set, hb_first), 12u, base, plan, &format12objidx);
+        else if (format == 14) c->copy (_, it, 14u, base, plan, &format14objidx);
+      }
     }
 
     c->check_assign(this->encodingRecord.len, (c->length () - cmap::min_size)/EncodingRecord::static_size);
@@ -1187,7 +1431,6 @@ struct cmap
                })
     ;
 
-
     if (unlikely (!encodingrec_iter.len ())) return_trace (false);
 
     const EncodingRecord *unicode_bmp= nullptr, *unicode_ucs4 = nullptr, *ms_bmp = nullptr, *ms_ucs4 = nullptr;
@@ -1205,7 +1448,7 @@ struct cmap
       else if (_.platformID == 3 && _.encodingID == 10) ms_ucs4 = table;
     }
 
-    if (unlikely (!unicode_bmp && !ms_bmp)) return_trace (false);
+    if (unlikely (!has_format12 && !unicode_bmp && !ms_bmp)) return_trace (false);
     if (unlikely (has_format12 && (!unicode_ucs4 && !ms_ucs4))) return_trace (false);
 
     auto it =
@@ -1219,7 +1462,6 @@ struct cmap
     | hb_filter ([&] (const hb_pair_t<hb_codepoint_t, hb_codepoint_t> _)
                 { return (_.second != HB_MAP_VALUE_INVALID); })
     ;
-
     cmap_prime->serialize (c->serializer, it, encodingrec_iter, this, c->plan);
     return_trace (true);
   }
@@ -1339,8 +1581,11 @@ struct cmap
       return get_nominal_glyph (unicode, glyph);
     }
 
-    void collect_unicodes (hb_set_t *out) const
-    { subtable->collect_unicodes (out); }
+    void collect_unicodes (hb_set_t *out, unsigned int num_glyphs) const
+    { subtable->collect_unicodes (out, num_glyphs); }
+    void collect_mapping (hb_set_t *unicodes, hb_map_t *mapping,
+                         unsigned num_glyphs = UINT_MAX) const
+    { subtable->collect_mapping (unicodes, mapping, num_glyphs); }
     void collect_variation_selectors (hb_set_t *out) const
     { subtable_uvs->collect_variation_selectors (out); }
     void collect_variation_unicodes (hb_codepoint_t variation_selector,
@@ -1413,7 +1658,7 @@ struct cmap
   }
 
   const EncodingRecord *find_encodingrec (unsigned int platform_id,
-                                   unsigned int encoding_id) const
+                                         unsigned int encoding_id) const
   {
     EncodingRecord key;
     key.platformID = platform_id;
@@ -1445,9 +1690,9 @@ struct cmap
   }
 
   protected:
-  HBUINT16             version;        /* Table version number (0). */
+  HBUINT16     version;        /* Table version number (0). */
   SortedArrayOf<EncodingRecord>
-                       encodingRecord; /* Encoding tables. */
+               encodingRecord; /* Encoding tables. */
   public:
   DEFINE_SIZE_ARRAY (4, encodingRecord);
 };