Subroutine flattener for CFF1
authorMichiharu Ariza <ariza@adobe.com>
Wed, 29 Aug 2018 19:14:30 +0000 (12:14 -0700)
committerMichiharu Ariza <ariza@adobe.com>
Wed, 29 Aug 2018 19:14:30 +0000 (12:14 -0700)
Subr-flattened charstrings are temporarily re-encoded in ByteStrBuff during "plan" phase, then copied to hb_serialize_context_t during "write" phase

CSOpSet may callback opcode processing "virtual" functions via CRTP

Numer struct may store a value as fixed optionally in addition to int and float

src/hb-cff-interp-common-private.hh
src/hb-cff-interp-cs-common-private.hh
src/hb-cff1-interp-cs.hh
src/hb-cff2-interp-cs.hh
src/hb-ot-cff-common-private.hh
src/hb-subset-cff-common-private.hh
src/hb-subset-cff1.cc
src/hb-subset-cff2.cc

index 2d20163..79add12 100644 (file)
@@ -204,23 +204,62 @@ enum OpCode {
 };
 
 inline OpCode Make_OpCode_ESC (unsigned char byte2)  { return (OpCode)(OpCode_ESC_Base + byte2); }
-inline unsigned int OpCode_Size (OpCode op) { return (op >= OpCode_ESC_Base)? 2: 1; }
+inline OpCode Unmake_OpCode_ESC (OpCode op)  { return (OpCode)(op - OpCode_ESC_Base); }
+inline bool Is_OpCode_ESC (OpCode op) { return op >= OpCode_ESC_Base; }
+inline unsigned int OpCode_Size (OpCode op) { return Is_OpCode_ESC (op)? 2: 1; }
 
 struct Number
 {
   inline Number (void) { set_int (0); }
 
-  inline void set_int (int v)       { is_real = false; u.int_val = v; };
-  inline int to_int (void) const    { return is_real? (int)u.real_val: u.int_val; }
-  inline void set_real (float v)    { is_real = true; u.real_val = v; };
-  inline float to_real (void) const { return is_real? u.real_val: (float)u.int_val; }
+  inline void set_int (int v)           { format = NumInt; u.int_val = v; };
+  inline int to_int (void) const        { return is_int ()? u.int_val: (int)to_real (); }
+  inline void set_fixed (int32_t v)     { format = NumFixed; u.fixed_val = v; };
+  inline int32_t to_fixed (void) const
+  {
+    if (is_fixed ())
+      return u.fixed_val;
+    else if (is_real ())
+      return (int32_t)(u.real_val * 65536.0);
+    else
+      return (int32_t)(u.int_val << 16);
+  }
+  inline void set_real (float v)        { format = NumReal; u.real_val = v; };
+  inline float to_real (void) const
+  {
+    if (is_real ())
+      return u.real_val;
+    if (is_fixed ())
+      return u.fixed_val / 65536.0;
+    else
+      return (float)u.int_val;
+  }
+  inline bool in_int_range (void) const
+  {
+    if (is_int ())
+      return true;
+    if (is_fixed () && ((u.fixed_val & 0xFFFF) == 0))
+      return true;
+    else
+      return ((float)(int16_t)to_int () == u.real_val);
+  }
 
 protected:
-  bool is_real;
+  enum NumFormat {
+    NumInt,
+    NumFixed,
+    NumReal
+  };
+  NumFormat  format;
   union {
     int     int_val;
+    int32_t fixed_val;
     float   real_val;
   } u;
+
+  inline bool  is_int (void) const { return format == NumInt; }
+  inline bool  is_fixed (void) const { return format == NumFixed; }
+  inline bool  is_real (void) const { return format == NumReal; }
 };
 
 /* byte string */
@@ -308,7 +347,7 @@ struct SubByteStr
     offset = offset_;
   }
 
-  inline const HBUINT8& operator [] (unsigned int i) const {
+  inline const HBUINT8& operator [] (int i) const {
     return str[offset + i];
   }
 
index 4cf63c2..a8fd8ba 100644 (file)
@@ -125,18 +125,6 @@ struct CSInterpEnv : InterpEnv
       hintmask_size = (hstem_count + vstem_count + 7) >> 3;
       seen_hintmask = true;
     }
-    clear_stack ();
-  }
-
-  inline void process_moveto (void)
-  {
-    clear_stack ();
-
-    if (!seen_moveto)
-    {
-      determine_hintmask_size ();
-      seen_moveto = true;
-    }
   }
 
   inline void clear_stack (void)
@@ -149,13 +137,12 @@ struct CSInterpEnv : InterpEnv
   inline bool is_endchar (void) const { return endchar_flag; }
   inline bool is_stack_cleared (void) const { return stack_cleared; }
 
-  protected:
+  public:
   bool          endchar_flag;
   bool          stack_cleared;
   bool          seen_moveto;
   bool          seen_hintmask;
 
-  public:
   unsigned int  hstem_count;
   unsigned int  vstem_count;
   unsigned int  hintmask_size;
@@ -164,11 +151,11 @@ struct CSInterpEnv : InterpEnv
   BiasedSubrs<SUBRS>   localSubrs;
 };
 
-template <typename SUBRS, typename PARAM>
+template <typename OPSET, typename ENV, typename PARAM>
 struct CSOpSet : OpSet
 {
-  static inline bool process_op (OpCode op, CSInterpEnv<SUBRS> &env, PARAM& param)
-  {
+  static inline bool process_op (OpCode op, ENV &env, PARAM& param)
+  {  
     switch (op) {
 
       case OpCode_return:
@@ -188,17 +175,16 @@ struct CSOpSet : OpSet
 
       case OpCode_hstem:
       case OpCode_hstemhm:
-        env.hstem_count += env.argStack.size / 2;
-        env.clear_stack ();
+        OPSET::process_hstem (env, param);
         break;
       case OpCode_vstem:
       case OpCode_vstemhm:
-        env.vstem_count += env.argStack.size / 2;
-        env.clear_stack ();
+        OPSET::process_vstem (env, param);
         break;
       case OpCode_hintmask:
       case OpCode_cntrmask:
         env.determine_hintmask_size ();
+        OPSET::flush_stack (env, param);
         if (unlikely (!env.substr.avail (env.hintmask_size)))
           return false;
         env.substr.inc (env.hintmask_size);
@@ -210,7 +196,7 @@ struct CSOpSet : OpSet
       case OpCode_vlineto:
       case OpCode_rmoveto:
       case OpCode_hmoveto:
-        env.process_moveto ();
+        OPSET::process_moveto (env, param);
         break;
       case OpCode_rrcurveto:
       case OpCode_rcurveline:
@@ -223,7 +209,7 @@ struct CSOpSet : OpSet
       case OpCode_flex:
       case OpCode_hflex1:
       case OpCode_flex1:
-        env.clear_stack ();
+        OPSET::flush_stack (env, param);
         break;
 
       default:
@@ -231,6 +217,83 @@ struct CSOpSet : OpSet
     }
     return true;
   }
+
+  static inline void process_hstem (ENV &env, PARAM& param)
+  {
+    env.hstem_count += env.argStack.size / 2;
+    OPSET::flush_stack (env, param);
+  }
+
+  static inline void process_vstem (ENV &env, PARAM& param)
+  {
+    env.vstem_count += env.argStack.size / 2;
+    OPSET::flush_stack (env, param);
+  }
+
+  static inline void process_moveto (ENV &env, PARAM& param)
+  {
+    if (!env.seen_moveto)
+    {
+      env.determine_hintmask_size ();
+      env.seen_moveto = true;
+    }
+    OPSET::flush_stack (env, param);
+  }
+
+  static inline void flush_stack (ENV &env, PARAM& param)
+  {
+    env.clear_stack ();
+  }
+
+  /* numeric / logical / arithmetic operators */
+  static inline bool is_arg_op (OpCode op)
+  {
+    switch (op)
+    {
+      case OpCode_shortint:
+      case OpCode_TwoBytePosInt0: case OpCode_TwoBytePosInt1:
+      case OpCode_TwoBytePosInt2: case OpCode_TwoBytePosInt3:
+      case OpCode_TwoByteNegInt0: case OpCode_TwoByteNegInt1:
+      case OpCode_TwoByteNegInt2: case OpCode_TwoByteNegInt3:
+      case OpCode_fixedcs:
+      case OpCode_and:
+      case OpCode_or:
+      case OpCode_not:
+      case OpCode_abs:
+      case OpCode_add:
+      case OpCode_sub:
+      case OpCode_div:
+      case OpCode_neg:
+      case OpCode_eq:
+      case OpCode_drop:
+      case OpCode_put:
+      case OpCode_get:
+      case OpCode_ifelse:
+      case OpCode_random:
+      case OpCode_mul:
+      case OpCode_sqrt:
+      case OpCode_dup:
+      case OpCode_exch:
+      case OpCode_index:
+      case OpCode_roll:
+        return true;
+      default:
+        return (OpCode_OneByteIntFirst <= op) && (op <= OpCode_OneByteIntLast);
+    }
+  }
+
+  static inline bool is_subr_op (OpCode op)
+  {
+    switch (op)
+    {
+      case OpCode_callsubr:
+      case OpCode_callgsubr:
+      case OpCode_return:
+        return true;
+      default:
+        return false;
+    }
+  }
 };
 
 template <typename ENV, typename OPSET, typename PARAM>
index c76a788..ba93454 100644 (file)
@@ -38,6 +38,8 @@ struct CFF1CSInterpEnv : CSInterpEnv<CFF1Subrs>
   inline void init (const ByteStr &str, const CFF1Subrs &globalSubrs, const CFF1Subrs &localSubrs)
   {
     CSInterpEnv<CFF1Subrs>::init (str, globalSubrs, localSubrs);
+    processed_width = false;
+    has_width = false;
     for (unsigned int i = 0; i < kTransientArraySize; i++)
       transient_array[i].set_int (0);
   }
@@ -45,25 +47,29 @@ struct CFF1CSInterpEnv : CSInterpEnv<CFF1Subrs>
   bool check_transient_array_index (unsigned int i) const
   { return i < kTransientArraySize; }
 
-  inline void process_width (void)
+  inline void check_width (void)
   {
-    if (!seen_width && (argStack.size > 0))
+    if (!processed_width)
     {
-      assert (argStack.size == 1);
-      width = argStack.pop ();
-      seen_width = true;
+      if ((this->argStack.size & 1) != 0)
+      {
+        width = this->argStack.elements[0];
+        has_width = true;
+      }
+      processed_width = true;
     }
   }
 
-  bool          seen_width;
+  bool          processed_width;
+  bool          has_width;
   Number        width;
 
   static const unsigned int kTransientArraySize = 32;
   Number  transient_array[kTransientArraySize];
 };
 
-template <typename PARAM>
-struct CFF1CSOpSet : CSOpSet<CFF1Subrs, PARAM>
+template <typename OPSET, typename PARAM>
+struct CFF1CSOpSet : CSOpSet<OPSET, CFF1CSInterpEnv, PARAM>
 {
   static inline bool process_op (OpCode op, CFF1CSInterpEnv &env, PARAM& param)
   {
@@ -133,7 +139,7 @@ struct CFF1CSOpSet : CSOpSet<CFF1Subrs, PARAM>
         break;
       case OpCode_random:
           if (unlikely (!env.argStack.check_overflow (1))) return false;
-          env.argStack.push_real (((float)rand() + 1) / ((float)RAND_MAX + 1));
+          env.argStack.push_int (1);  /* we can't deal with random behavior; make it constant */
       case OpCode_mul:
         if (unlikely (!env.argStack.check_pop_num2 (n1, n2))) return false;
         env.argStack.push_real (n1.to_real() * n2.to_real());
@@ -181,14 +187,21 @@ struct CFF1CSOpSet : CSOpSet<CFF1Subrs, PARAM>
         }
         break;
       default:
-        typedef CSOpSet<CFF1Subrs, PARAM>  SUPER;
         if (unlikely (!SUPER::process_op (op, env, param)))
           return false;
-        env.process_width ();
         break;
     }
     return true;
   }
+
+  static inline void flush_stack (CFF1CSInterpEnv &env, PARAM& param)
+  {
+    env.check_width ();
+    SUPER::flush_stack (env, param);
+  }
+
+  private:
+  typedef CSOpSet<OPSET, CFF1CSInterpEnv, PARAM>  SUPER;
 };
 
 template <typename OPSET, typename PARAM>
index 519b5fc..96e209b 100644 (file)
@@ -43,11 +43,11 @@ struct CFF2CSInterpEnv : CSInterpEnv<CFF2Subrs>
 
   inline bool fetch_op (OpCode &op)
   {
-    if (unlikely (substr.avail ()))
+    if (unlikely (this->substr.avail ()))
       return CSInterpEnv<CFF2Subrs>::fetch_op (op);
 
     /* make up return or endchar op */
-    if (callStack.check_underflow ())
+    if (this->callStack.check_underflow ())
       op = OpCode_return;
     else
       op = OpCode_endchar;
@@ -61,26 +61,26 @@ struct CFF2CSInterpEnv : CSInterpEnv<CFF2Subrs>
   unsigned int  ivs;
 };
 
-template <typename PARAM>
-struct CFF2CSOpSet : CSOpSet<CFF2Subrs, PARAM>
+template <typename OPSET, typename PARAM>
+struct CFF2CSOpSet : CSOpSet<OPSET, CFF2CSInterpEnv, PARAM>
 {
   static inline bool process_op (OpCode op, CFF2CSInterpEnv &env, PARAM& param)
   {
     switch (op) {
 
       case OpCode_blendcs:
-        env.clear_stack (); // XXX: TODO
+        //env.flush_stack (); // XXX: TODO
         break;
       case OpCode_vsindexcs:
         {
           unsigned int ivs;
           if (unlikely (!env.argStack.check_pop_uint (ivs))) return false;
           env.set_ivs (ivs);
-          env.clear_stack ();
+          //env.flush_stack ();
         }
         break;
       default:
-        typedef CSOpSet<CFF2Subrs, PARAM>  SUPER;
+        typedef CSOpSet<OPSET, CFF2CSInterpEnv, PARAM>  SUPER;
         if (unlikely (!SUPER::process_op (op, env, param)))
           return false;
         break;
index 83f6a1a..09bcc22 100644 (file)
@@ -299,12 +299,12 @@ struct Dict : UnsizedByteStr
 
     TRACE_SERIALIZE (this);
     /* serialize the opcode */
-    HBUINT8 *p = c->allocate_size<HBUINT8> ((op >= OpCode_ESC_Base)? 2: 1);
+    HBUINT8 *p = c->allocate_size<HBUINT8> (OpCode_Size (op));
     if (unlikely (p == nullptr)) return_trace (false);
-    if (op >= OpCode_ESC_Base)
+    if (Is_OpCode_ESC (op))
     {
       p->set (OpCode_escape);
-      op = (OpCode)(op - OpCode_ESC_Base);
+      op = Unmake_OpCode_ESC (op);
       p++;
     }
     p->set (op);
@@ -344,7 +344,7 @@ struct FDMap : hb_vector_t<hb_codepoint_t>
   inline bool fullset (void) const
   {
     for (unsigned int i = 0; i < len; i++)
-      if ((*this)[i] == HB_SET_VALUE_INVALID)
+      if (hb_vector_t<hb_codepoint_t>::operator[] (i) == HB_SET_VALUE_INVALID)
         return false;
     return true;
   }
@@ -574,7 +574,7 @@ struct Subrs : CFFIndex<COUNT>
     TRACE_SERIALIZE (this);
     if (&subrs == &Null(Subrs<COUNT>))
       return_trace (true);
-    if ((subrs.count == 0) || (hb_set_get_population (set) == 0))
+    if ((subrs.count == 0) || (set == nullptr) || (hb_set_is_empty (set)))
     {
       if (!unlikely (c->allocate_size<COUNT> (COUNT::static_size)))
         return_trace (false);
index 124bb65..4013c32 100644 (file)
 
 namespace CFF {
 
+/* Used for writing a temporary charstring */
+struct ByteStrBuff : hb_vector_t<char, 1>
+{
+  inline bool encode_byte (unsigned char b)
+  {
+    return (push ((const char)b) != &Crap(char));
+  }
+
+  inline bool encode_num (const Number& n)
+  {
+    if (n.in_int_range ())
+    {
+      int v = n.to_int ();
+      if ((-1131 <= v) && (v <= 1131))
+      {
+        if ((-107 <= v) && (v <= 107))
+          return encode_byte (v + 139);
+        else if (v > 0)
+        {
+          v -= 108;
+          return encode_byte ((v >> 8) + OpCode_TwoBytePosInt0) && encode_byte (v & 0xFF);
+        }
+        else
+        {
+          v = -v - 108;
+          return encode_byte ((v >> 8) + OpCode_TwoByteNegInt0) && encode_byte (v & 0xFF);
+        }
+      }
+      assert ((v & ~0xFFFF) == 0);
+      return encode_byte (OpCode_shortint) &&
+             encode_byte ((v >> 8) & 0xFF) &&
+             encode_byte (v & 0xFF);
+    }
+    else
+    {
+      int32_t v = n.to_fixed ();
+      return encode_byte (OpCode_fixedcs) &&
+             encode_byte ((v >> 24) & 0xFF) &&
+             encode_byte ((v >> 16) & 0xFF) &&
+             encode_byte ((v >> 8) & 0xFF) &&
+             encode_byte (v & 0xFF);
+    }
+  }
+
+  inline bool encode_op (OpCode op)
+  {
+    if (Is_OpCode_ESC (op))
+      return encode_byte (OpCode_escape) &&
+             encode_byte (Unmake_OpCode_ESC (op));
+    else
+      return encode_byte (op);
+  }
+};
+
+struct ByteStrBuffArray : hb_vector_t<ByteStrBuff, 1>
+{
+  inline void fini (void)
+  {
+    for (unsigned int i = 0; i < len; i++)
+      hb_vector_t<ByteStrBuff, 1>::operator[] (i).fini ();
+    hb_vector_t<ByteStrBuff, 1>::fini ();
+  }
+};
+
+
+template <typename ACCESSOR, typename ENV, typename OPSET>
+struct SubrFlattener
+{
+  inline SubrFlattener (const ACCESSOR &acc_, const hb_vector_t<hb_codepoint_t> &glyphs_)
+    : acc (acc_),
+      glyphs (glyphs_)
+  {}
+
+  inline bool flatten (ByteStrBuffArray &flat_charstrings)
+  {
+    if (!flat_charstrings.resize (glyphs.len))
+      return false;
+    for (unsigned int i = 0; i < glyphs.len; i++)
+      flat_charstrings[i].init ();
+    for (unsigned int i = 0; i < glyphs.len; i++)
+    {
+      hb_codepoint_t  glyph = glyphs[i];
+      const ByteStr str = (*acc.charStrings)[glyph];
+      unsigned int fd = acc.fdSelect->get_fd (glyph);
+      CSInterpreter<ENV, OPSET, ByteStrBuff> interp;
+      interp.env.init (str, *acc.globalSubrs, *acc.privateDicts[fd].localSubrs);
+      if (unlikely (!interp.interpret (flat_charstrings[i])))
+        return false;
+    }
+    return true;
+  }
+  
+  const ACCESSOR &acc;
+  const hb_vector_t<hb_codepoint_t> &glyphs;
+};
+
 struct SubrRefMaps
 {
-  inline SubrRefMaps (void)
-    : valid (false),
-      global_map (nullptr)
+  inline void init (void)
   {
+    valid = false;
+    global_map = nullptr;
     local_maps.init ();
   }
 
index 0622c25..8e647ea 100644 (file)
@@ -167,49 +167,84 @@ struct CFF1FontDict_OpSerializer : OpSerializer
 
 struct CFF1PrivateDict_OpSerializer : OpSerializer
 {
+  inline CFF1PrivateDict_OpSerializer (bool drop_hints_=false, bool flatten_subrs_=false)
+    : drop_hints (drop_hints_), flatten_subrs (flatten_subrs_) {}
+
   inline bool serialize (hb_serialize_context_t *c,
                          const OpStr &opstr,
                          const unsigned int subrsOffset) const
   {
     TRACE_SERIALIZE (this);
 
+    if (drop_hints && DictOpSet::is_hint_op (opstr.op))
+      return true;
     if (opstr.op == OpCode_Subrs)
-      return_trace (FontDict::serialize_offset2_op(c, OpCode_Subrs, subrsOffset));
+    {
+      if (flatten_subrs)
+        return_trace (true);
+      else
+        return_trace (FontDict::serialize_offset2_op(c, OpCode_Subrs, subrsOffset));
+    }
     else
       return_trace (copy_opstr (c, opstr));
   }
 
   inline unsigned int calculate_serialized_size (const OpStr &opstr) const
   {
+    if (drop_hints && DictOpSet::is_hint_op (opstr.op))
+      return 0;
     if (opstr.op == OpCode_Subrs)
-      return OpCode_Size (OpCode_shortint) + 2 + OpCode_Size (OpCode_Subrs);
+    {
+      if (flatten_subrs)
+        return 0;
+      else
+        return OpCode_Size (OpCode_shortint) + 2 + OpCode_Size (OpCode_Subrs);
+    }
     else
       return opstr.str.len;
   }
+
+  protected:
+  const bool  drop_hints;
+  const bool  flatten_subrs;
 };
 
-struct CFF1PrivateDict_OpSerializer_DropHints : CFF1PrivateDict_OpSerializer
+struct CFF1CSOpSet_Flatten : CFF1CSOpSet<CFF1CSOpSet_Flatten, ByteStrBuff>
 {
-  inline bool serialize (hb_serialize_context_t *c,
-                         const OpStr &opstr,
-                         const unsigned int subrsOffset) const
+  static inline bool process_op (OpCode op, CFF1CSInterpEnv &env, ByteStrBuff& flatStr)
   {
-    if (DictOpSet::is_hint_op (opstr.op))
-      return true;
-    else
-      return CFF1PrivateDict_OpSerializer::serialize (c, opstr, subrsOffset);
+    if (unlikely (!SUPER::process_op (op, env, flatStr)))
+      return false;
+    switch (op)
+    {
+      case OpCode_hintmask:
+      case OpCode_cntrmask:
+        if (unlikely (!flatStr.encode_op (op)))
+          return false;
+        for (int i = -env.hintmask_size; i < 0; i++)
+          if (unlikely (!flatStr.encode_byte (env.substr[i])))
+            return false;
+        break;
+      default:
+        if (!CSOpSet::is_subr_op (op) &&
+            !CSOpSet::is_arg_op (op))
+          return flatStr.encode_op (op);
+    }
+    return true;
   }
 
-  inline unsigned int calculate_serialized_size (const OpStr &opstr) const
+  static inline void flush_stack (CFF1CSInterpEnv &env, ByteStrBuff& flatStr)
   {
-    if (DictOpSet::is_hint_op (opstr.op))
-      return 0;
-    else
-      return CFF1PrivateDict_OpSerializer::calculate_serialized_size (opstr);
+    for (unsigned int i = 0; i < env.argStack.size; i++)
+      flatStr.encode_num (env.argStack.elements[i]);
+    SUPER::flush_stack (env, flatStr);
   }
+
+  private:
+  typedef CFF1CSOpSet<CFF1CSOpSet_Flatten, ByteStrBuff> SUPER;
 };
 
-struct CFF1CSOpSet_SubrSubset : CFF1CSOpSet<SubrRefMapPair>
+struct CFF1CSOpSet_SubsetSubrs : CFF1CSOpSet<CFF1CSOpSet_SubsetSubrs, SubrRefMapPair>
 {
   static inline bool process_op (OpCode op, CFF1CSInterpEnv &env, SubrRefMapPair& refMapPair)
   {
@@ -230,7 +265,7 @@ struct CFF1CSOpSet_SubrSubset : CFF1CSOpSet<SubrRefMapPair>
       default:
         break;
     }
-    return CFF1CSOpSet<SubrRefMapPair>::process_op (op, env, refMapPair);
+    return CFF1CSOpSet<CFF1CSOpSet_SubsetSubrs, SubrRefMapPair>::process_op (op, env, refMapPair);
   }
 };
 
@@ -238,16 +273,20 @@ struct cff_subset_plan {
   inline cff_subset_plan (void)
     : final_size (0),
       orig_fdcount (0),
-      subst_fdcount(1),
+      subst_fdcount (1),
       subst_fdselect_format (0),
-      offsets()
+      offsets (),
+      flatten_subrs (true),
+      drop_hints (false)
   {
     topdict_sizes.init ();
     topdict_sizes.resize (1);
     subst_fdselect_first_glyphs.init ();
     fdmap.init ();
     subset_charstrings.init ();
+    flat_charstrings.init ();
     privateDictInfos.init ();
+    subrRefMaps.init ();
   }
 
   inline ~cff_subset_plan (void)
@@ -256,6 +295,7 @@ struct cff_subset_plan {
     subst_fdselect_first_glyphs.fini ();
     fdmap.fini ();
     subset_charstrings.fini ();
+    flat_charstrings.fini ();
     privateDictInfos.fini ();
     subrRefMaps.fini ();
   }
@@ -287,9 +327,20 @@ struct cff_subset_plan {
     offsets.stringIndexOffset = final_size;
     final_size += acc.stringIndex->get_size ();
     
-    /* Subset global & local subrs */
+    if (flatten_subrs)
+    {
+      /* Flatten global & local subrs */
+      SubrFlattener<const OT::cff1::accelerator_subset_t, CFF1CSInterpEnv, CFF1CSOpSet_Flatten> flattener(acc, plan->glyphs);
+      if (!flattener.flatten (flat_charstrings))
+        return false;
+      
+      /* no global/local subroutines */
+      offsets.globalSubrsInfo.size = HBUINT16::static_size; /* count 0 only */
+    }
+    else
     {
-      SubrSubsetter<const OT::cff1::accelerator_subset_t, CFF1CSInterpEnv, CFF1CSOpSet_SubrSubset> subsetter(acc, plan->glyphs);
+      /* Subset global & local subrs */
+      SubrSubsetter<const OT::cff1::accelerator_subset_t, CFF1CSInterpEnv, CFF1CSOpSet_SubsetSubrs> subsetter(acc, plan->glyphs);
       if (!subsetter.collect_refs (subrRefMaps))
         return false;
       
@@ -299,7 +350,6 @@ struct cff_subset_plan {
       for (unsigned int i = 0; i < orig_fdcount; i++)
         offsets.localSubrsInfos[i].size = acc.privateDicts[i].localSubrs->calculate_serialized_size (offsets.localSubrsInfos[i].offSize, subrRefMaps.local_maps[i], 1);
     }
-    
     /* global subrs */
     offsets.globalSubrsInfo.offset = final_size;
     final_size += offsets.globalSubrsInfo.size;
@@ -346,9 +396,19 @@ struct cff_subset_plan {
       unsigned int dataSize = 0;
       for (unsigned int i = 0; i < plan->glyphs.len; i++)
       {
-        const ByteStr str = (*acc.charStrings)[plan->glyphs[i]];
-        subset_charstrings.push (str);
-        dataSize += str.len;
+        if (flatten_subrs)
+        {
+          ByteStrBuff &flatstr = flat_charstrings[i];
+          ByteStr str (&flatstr[0], flatstr.len);
+          subset_charstrings.push (str);
+          dataSize += flatstr.len;
+        }
+        else
+        {
+          const ByteStr str = (*acc.charStrings)[plan->glyphs[i]];
+          subset_charstrings.push (str);
+          dataSize += str.len;
+        }
       }
       offsets.charStringsInfo.offSize = calcOffSize (dataSize + 1);
       final_size += CFF1CharStrings::calculate_serialized_size (offsets.charStringsInfo.offSize, plan->glyphs.len, dataSize);
@@ -361,26 +421,21 @@ struct cff_subset_plan {
       if (!fdmap.excludes (i))
       {
         unsigned int  priv_size;
-        if (plan->drop_hints)
-        {
-          CFF1PrivateDict_OpSerializer_DropHints privSzr_drop;
-          priv_size = PrivateDict::calculate_serialized_size (acc.privateDicts[i], privSzr_drop);
-        }
-        else
-        {
-          CFF1PrivateDict_OpSerializer privSzr;
-          priv_size = PrivateDict::calculate_serialized_size (acc.privateDicts[i], privSzr);
-        }
+        CFF1PrivateDict_OpSerializer privSzr (plan->drop_hints, flatten_subrs);
+        priv_size = PrivateDict::calculate_serialized_size (acc.privateDicts[i], privSzr);
         TableInfo  privInfo = { final_size, priv_size, 0 };
         privateDictInfos.push (privInfo);
-        final_size += privInfo.size + offsets.localSubrsInfos[i].size;
+        final_size += privInfo.size;
+        if (!flatten_subrs)
+          final_size += offsets.localSubrsInfos[i].size;
       }
     }
 
     if (!acc.is_CID ())
       offsets.privateDictInfo = privateDictInfos[0];
 
-    return true;
+    return ((subset_charstrings.len == plan->glyphs.len) &&
+            (privateDictInfos.len == subst_fdcount));
   }
 
   inline unsigned int get_final_size (void) const  { return final_size; }
@@ -399,11 +454,13 @@ struct cff_subset_plan {
    * set to HB_SET_VALUE_INVALID if excluded from subset */
   FDMap   fdmap;
 
-  hb_vector_t<ByteStr> subset_charstrings;
-  hb_vector_t<TableInfo> privateDictInfos;
+  hb_vector_t<ByteStr>    subset_charstrings;
+  ByteStrBuffArray        flat_charstrings;
+  hb_vector_t<TableInfo>  privateDictInfos;
 
   SubrRefMaps             subrRefMaps;
 
+  bool            flatten_subrs;
   bool            drop_hints;
 };
 
@@ -467,6 +524,7 @@ static inline bool _write_cff1 (const cff_subset_plan &plan,
 
   /* global subrs */
   {
+    assert (plan.offsets.globalSubrsInfo.offset != 0);
     assert (plan.offsets.globalSubrsInfo.offset == c.head - c.start);
     CFF1Subrs *dest = c.start_embed<CFF1Subrs> ();
     if (unlikely (dest == nullptr)) return false;
@@ -565,25 +623,17 @@ static inline bool _write_cff1 (const cff_subset_plan &plan,
     {
       PrivateDict  *pd = c.start_embed<PrivateDict> ();
       if (unlikely (pd == nullptr)) return false;
-      unsigned int priv_size = plan.privateDictInfos[plan.fdmap[i]].size;
+      unsigned int priv_size = plan.flatten_subrs? 0: plan.privateDictInfos[plan.fdmap[i]].size;
       bool result;
+      CFF1PrivateDict_OpSerializer privSzr (plan.drop_hints, plan.flatten_subrs);
       /* N.B. local subrs immediately follows its corresponding private dict. i.e., subr offset == private dict size */
-      if (plan.drop_hints)
-      {
-        CFF1PrivateDict_OpSerializer_DropHints privSzr_drop;
-        result = pd->serialize (&c, acc.privateDicts[i], privSzr_drop, priv_size);
-      }
-      else
-      {
-        CFF1PrivateDict_OpSerializer privSzr;
-        result = pd->serialize (&c, acc.privateDicts[i], privSzr, priv_size);
-      }
+      result = pd->serialize (&c, acc.privateDicts[i], privSzr, priv_size);
       if (unlikely (!result))
       {
         DEBUG_MSG (SUBSET, nullptr, "failed to serialize CFF Private Dict[%d]", i);
         return false;
       }
-      if (acc.privateDicts[i].subrsOffset != 0)
+      if (!plan.flatten_subrs && (acc.privateDicts[i].subrsOffset != 0))
       {
         CFF1Subrs *subrs = c.start_embed<CFF1Subrs> ();
         if (unlikely (subrs == nullptr) || acc.privateDicts[i].localSubrs == &Null(CFF1Subrs))
index e76a438..8d45b00 100644 (file)
@@ -187,7 +187,7 @@ struct CFF2PrivateDict_OpSerializer_DropHints : CFF2PrivateDict_OpSerializer
   }
 };
 
-struct CFF2CSOpSet_SubrSubset : CFF2CSOpSet<SubrRefMapPair>
+struct CFF2CSOpSet_SubsetSubrs : CFF2CSOpSet<CFF2CSOpSet_SubsetSubrs, SubrRefMapPair>
 {
   static inline bool process_op (OpCode op, CFF2CSInterpEnv &env, SubrRefMapPair& refMapPair)
   {
@@ -208,7 +208,7 @@ struct CFF2CSOpSet_SubrSubset : CFF2CSOpSet<SubrRefMapPair>
       default:
         break;
     }
-    return CFF2CSOpSet<SubrRefMapPair>::process_op (op, env, refMapPair);
+    return CFF2CSOpSet<CFF2CSOpSet_SubsetSubrs, SubrRefMapPair>::process_op (op, env, refMapPair);
   }
 };
 
@@ -223,6 +223,7 @@ struct cff2_subset_plan {
     fdmap.init ();
     subset_charstrings.init ();
     privateDictInfos.init ();
+    subrRefMaps.init ();
   }
 
   inline ~cff2_subset_plan (void)
@@ -254,7 +255,7 @@ struct cff2_subset_plan {
 
     /* Subset global & local subrs */
     {
-      SubrSubsetter<const OT::cff2::accelerator_subset_t, CFF2CSInterpEnv, CFF2CSOpSet_SubrSubset> subsetter(acc, plan->glyphs);
+      SubrSubsetter<const OT::cff2::accelerator_subset_t, CFF2CSInterpEnv, CFF2CSOpSet_SubsetSubrs> subsetter(acc, plan->glyphs);
       if (!subsetter.collect_refs (subrRefMaps))
         return false;