Source code

Revision control

Copy as Markdown

Other Tools

/*
* 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<unsigned char> 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 */