/* * Copyright © 2026 Behdad Esfahbod * * This is part of HarfBuzz, a text shaping library. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the * above copyright notice and the following two paragraphs appear in * all copies of this software. * * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */ #ifndef HB_SUBSET_CFF2_TO_CFF1_HH #define HB_SUBSET_CFF2_TO_CFF1_HH #include "hb.hh" #ifndef HB_NO_SUBSET_CFF #include "hb-ot-cff1-table.hh" #include "hb-ot-cff2-table.hh" #include "hb-subset-cff-common.hh" namespace OT { // Forward declarations - these are defined in hb-subset-cff2.cc struct cff2_subset_plan; } namespace CFF { // Forward declaration struct cff2_top_dict_values_t; // Default font name for converted CFF1 fonts static constexpr const char CFF1_DEFAULT_FONT_NAME[] = "CFF1Font"; /* * CFF2 to CFF1 Converter * * Converts an instantiated (pinned) CFF2 variable font to CFF1 format. * This is used when instantiating a variable font to a static instance. * * IMPLEMENTATION STATUS: * ✓ CFF1 structure (Header, Name INDEX, String INDEX, Top DICT INDEX) * ✓ ROS operator (makes font CID-keyed: "Adobe-Identity-0") * ✓ FDArray and FDSelect in Top DICT (required for CID fonts) * ✓ FDSelect3 format (compact range-based, 8 bytes for single-FD fonts) * ✓ CID Charset with identity mapping (format 2) * ✓ FontBBox from head table (xMin, yMin, xMax, yMax) * ✓ Width optimization (defaultWidthX/nominalWidthX with O(n) algorithm) * ✓ Width encoding in CharStrings (prepended if != defaultWidthX) * ✓ CharString specialization (h/v operators, combined when possible) * ✓ Stack depth control (generalize→specialize with maxstack=48) * ✓ CharStrings with endchar operators (CFF1 requires, CFF2 doesn't) * ✓ Private DICT instantiation (blend operators evaluated) * ✓ Desubroutinized path (CharStrings are flattened, no subroutines) * ✓ OTS validation passes * ✓ HarfBuzz rendering works * * FUTURE ENHANCEMENTS: * - Curve operator specialization (hhcurveto, vvcurveto, etc.) * - Peephole optimization (minor additional size savings) * * Key conversions: * - Version: 2 -> 1 * - Add Name INDEX (required in CFF1) * - Wrap Top DICT in an INDEX (inline in CFF2, indexed in CFF1) * - Add String INDEX ("Adobe", "Identity" for ROS operator) * - Add ROS operator to Top DICT (makes it CID-keyed) * - Add FDSelect to Top DICT (required in CFF1 even with single FD) * - Add endchar to CharStrings (required in CFF1, optional in CFF2) */ struct cff1_subset_plan_from_cff2_t { // Inherits most data from cff2_subset_plan const OT::cff2_subset_plan *cff2_plan; // CFF1-specific additions hb_vector_t fontName; // Single font name for Name INDEX bool create (const OT::cff2_subset_plan &cff2_plan_) { cff2_plan = &cff2_plan_; // Create a simple font name (CFF1 requires a Name INDEX) fontName.resize (strlen (CFF1_DEFAULT_FONT_NAME)); if (fontName.in_error ()) return false; memcpy (fontName.arrayZ, CFF1_DEFAULT_FONT_NAME, strlen (CFF1_DEFAULT_FONT_NAME)); return true; } }; /* CFF1 Top DICT operator serializer that adds ROS and removes CFF2-specific ops */ struct cff1_from_cff2_top_dict_op_serializer_t : cff_top_dict_op_serializer_t<> { bool serialize (hb_serialize_context_t *c, const op_str_t &opstr, const cff_sub_table_info_t &info) const { TRACE_SERIALIZE (this); switch (opstr.op) { case OpCode_vstore: // CFF2-only operator, skip it return_trace (true); case OpCode_CharStrings: return_trace (FontDict::serialize_link4_op(c, opstr.op, info.char_strings_link, whence_t::Absolute)); case OpCode_FDArray: case OpCode_FDSelect: // These are explicitly serialized in the main function to ensure they're present // even if CFF2 doesn't have them. Skip them here to avoid duplication. return_trace (true); default: return_trace (copy_opstr (c, opstr)); } } // Serialize ROS operator to make this a CID-keyed font bool serialize_ros (hb_serialize_context_t *c) const { TRACE_SERIALIZE (this); // ROS = Registry-Ordering-Supplement // We use "Adobe", "Identity", 0 for maximum compatibility // Allocate space and encode directly // Registry: SID for "Adobe" (custom string at index 0 = SID 391) // Ordering: SID for "Identity" (custom string at index 1 = SID 392) // Supplement: 0 // Note: CFF standard strings end at SID 390, custom strings start at 391 str_buff_t buff; str_encoder_t encoder (buff); encoder.encode_int (391); // Registry SID ("Adobe" in our String INDEX) encoder.encode_int (392); // Ordering SID ("Identity" in our String INDEX) encoder.encode_int (0); // Supplement encoder.encode_op (OpCode_ROS); if (encoder.in_error ()) return_trace (false); auto bytes = buff.as_bytes (); return_trace (c->embed (bytes.arrayZ, bytes.length)); } }; /* Main serialization function */ HB_INTERNAL bool serialize_cff2_to_cff1 (hb_serialize_context_t *c, OT::cff2_subset_plan &plan, const cff2_top_dict_values_t &cff2_topDict, const OT::cff2::accelerator_subset_t &acc); } /* namespace CFF */ #endif /* HB_NO_SUBSET_CFF */ #endif /* HB_SUBSET_CFF2_TO_CFF1_HH */