/work/workdir/UnpackedTarball/harfbuzz/src/hb-subset-cff2-to-cff1.hh
Line | Count | Source |
1 | | /* |
2 | | * Copyright © 2026 Behdad Esfahbod |
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 | | |
25 | | #ifndef HB_SUBSET_CFF2_TO_CFF1_HH |
26 | | #define HB_SUBSET_CFF2_TO_CFF1_HH |
27 | | |
28 | | #include "hb.hh" |
29 | | |
30 | | #ifndef HB_NO_SUBSET_CFF |
31 | | |
32 | | #include "hb-ot-cff1-table.hh" |
33 | | #include "hb-ot-cff2-table.hh" |
34 | | #include "hb-subset-cff-common.hh" |
35 | | |
36 | | namespace OT { |
37 | | // Forward declarations - these are defined in hb-subset-cff2.cc |
38 | | struct cff2_subset_plan; |
39 | | } |
40 | | |
41 | | namespace CFF { |
42 | | |
43 | | // Forward declaration |
44 | | struct cff2_top_dict_values_t; |
45 | | |
46 | | // Default font name for converted CFF1 fonts |
47 | | static constexpr const char CFF1_DEFAULT_FONT_NAME[] = "CFF1Font"; |
48 | | |
49 | | /* |
50 | | * CFF2 to CFF1 Converter |
51 | | * |
52 | | * Converts an instantiated (pinned) CFF2 variable font to CFF1 format. |
53 | | * This is used when instantiating a variable font to a static instance. |
54 | | * |
55 | | * IMPLEMENTATION STATUS: |
56 | | * ✓ CFF1 structure (Header, Name INDEX, String INDEX, Top DICT INDEX) |
57 | | * ✓ ROS operator (makes font CID-keyed: "Adobe-Identity-0") |
58 | | * ✓ FDArray and FDSelect in Top DICT (required for CID fonts) |
59 | | * ✓ FDSelect3 format (compact range-based, 8 bytes for single-FD fonts) |
60 | | * ✓ CID Charset with identity mapping (format 2) |
61 | | * ✓ FontBBox from head table (xMin, yMin, xMax, yMax) |
62 | | * ✓ Width optimization (defaultWidthX/nominalWidthX with O(n) algorithm) |
63 | | * ✓ Width encoding in CharStrings (prepended if != defaultWidthX) |
64 | | * ✓ CharString specialization (h/v operators, combined when possible) |
65 | | * ✓ Stack depth control (generalize→specialize with maxstack=48) |
66 | | * ✓ CharStrings with endchar operators (CFF1 requires, CFF2 doesn't) |
67 | | * ✓ Private DICT instantiation (blend operators evaluated) |
68 | | * ✓ Desubroutinized path (CharStrings are flattened, no subroutines) |
69 | | * ✓ OTS validation passes |
70 | | * ✓ HarfBuzz rendering works |
71 | | * |
72 | | * FUTURE ENHANCEMENTS: |
73 | | * - Curve operator specialization (hhcurveto, vvcurveto, etc.) |
74 | | * - Peephole optimization (minor additional size savings) |
75 | | * |
76 | | * Key conversions: |
77 | | * - Version: 2 -> 1 |
78 | | * - Add Name INDEX (required in CFF1) |
79 | | * - Wrap Top DICT in an INDEX (inline in CFF2, indexed in CFF1) |
80 | | * - Add String INDEX ("Adobe", "Identity" for ROS operator) |
81 | | * - Add ROS operator to Top DICT (makes it CID-keyed) |
82 | | * - Add FDSelect to Top DICT (required in CFF1 even with single FD) |
83 | | * - Add endchar to CharStrings (required in CFF1, optional in CFF2) |
84 | | */ |
85 | | |
86 | | struct cff1_subset_plan_from_cff2_t |
87 | | { |
88 | | // Inherits most data from cff2_subset_plan |
89 | | const OT::cff2_subset_plan *cff2_plan; |
90 | | |
91 | | // CFF1-specific additions |
92 | | hb_vector_t<unsigned char> fontName; // Single font name for Name INDEX |
93 | | |
94 | | bool create (const OT::cff2_subset_plan &cff2_plan_) |
95 | 0 | { |
96 | 0 | cff2_plan = &cff2_plan_; |
97 | 0 |
|
98 | 0 | // Create a simple font name (CFF1 requires a Name INDEX) |
99 | 0 | fontName.resize (strlen (CFF1_DEFAULT_FONT_NAME)); |
100 | 0 | if (fontName.in_error ()) return false; |
101 | 0 | memcpy (fontName.arrayZ, CFF1_DEFAULT_FONT_NAME, strlen (CFF1_DEFAULT_FONT_NAME)); |
102 | 0 |
|
103 | 0 | return true; |
104 | 0 | } |
105 | | }; |
106 | | |
107 | | /* CFF1 Top DICT operator serializer that adds ROS and removes CFF2-specific ops */ |
108 | | struct cff1_from_cff2_top_dict_op_serializer_t : cff_top_dict_op_serializer_t<> |
109 | | { |
110 | | bool serialize (hb_serialize_context_t *c, |
111 | | const op_str_t &opstr, |
112 | | const cff_sub_table_info_t &info) const |
113 | 0 | { |
114 | 0 | TRACE_SERIALIZE (this); |
115 | |
|
116 | 0 | switch (opstr.op) |
117 | 0 | { |
118 | 0 | case OpCode_vstore: |
119 | | // CFF2-only operator, skip it |
120 | 0 | return_trace (true); |
121 | | |
122 | 0 | case OpCode_CharStrings: |
123 | 0 | return_trace (FontDict::serialize_link4_op(c, opstr.op, info.char_strings_link, whence_t::Absolute)); |
124 | | |
125 | 0 | case OpCode_FDArray: |
126 | 0 | case OpCode_FDSelect: |
127 | | // These are explicitly serialized in the main function to ensure they're present |
128 | | // even if CFF2 doesn't have them. Skip them here to avoid duplication. |
129 | 0 | return_trace (true); |
130 | | |
131 | 0 | default: |
132 | 0 | return_trace (copy_opstr (c, opstr)); |
133 | 0 | } |
134 | 0 | } |
135 | | |
136 | | // Serialize ROS operator to make this a CID-keyed font |
137 | | bool serialize_ros (hb_serialize_context_t *c) const |
138 | 0 | { |
139 | 0 | TRACE_SERIALIZE (this); |
140 | | |
141 | | // ROS = Registry-Ordering-Supplement |
142 | | // We use "Adobe", "Identity", 0 for maximum compatibility |
143 | | |
144 | | // Allocate space and encode directly |
145 | | // Registry: SID for "Adobe" (custom string at index 0 = SID 391) |
146 | | // Ordering: SID for "Identity" (custom string at index 1 = SID 392) |
147 | | // Supplement: 0 |
148 | | // Note: CFF standard strings end at SID 390, custom strings start at 391 |
149 | |
|
150 | 0 | str_buff_t buff; |
151 | 0 | str_encoder_t encoder (buff); |
152 | |
|
153 | 0 | encoder.encode_int (391); // Registry SID ("Adobe" in our String INDEX) |
154 | 0 | encoder.encode_int (392); // Ordering SID ("Identity" in our String INDEX) |
155 | 0 | encoder.encode_int (0); // Supplement |
156 | 0 | encoder.encode_op (OpCode_ROS); |
157 | |
|
158 | 0 | if (encoder.in_error ()) |
159 | 0 | return_trace (false); |
160 | | |
161 | 0 | auto bytes = buff.as_bytes (); |
162 | 0 | return_trace (c->embed (bytes.arrayZ, bytes.length)); |
163 | 0 | } |
164 | | }; |
165 | | |
166 | | /* Main serialization function */ |
167 | | HB_INTERNAL bool |
168 | | serialize_cff2_to_cff1 (hb_serialize_context_t *c, |
169 | | OT::cff2_subset_plan &plan, |
170 | | const cff2_top_dict_values_t &cff2_topDict, |
171 | | const OT::cff2::accelerator_subset_t &acc); |
172 | | |
173 | | } /* namespace CFF */ |
174 | | |
175 | | #endif /* HB_NO_SUBSET_CFF */ |
176 | | |
177 | | #endif /* HB_SUBSET_CFF2_TO_CFF1_HH */ |