/work/workdir/UnpackedTarball/harfbuzz/src/hb-subset-cff2.cc
Line | Count | Source |
1 | | /* |
2 | | * Copyright © 2018 Adobe Inc. |
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 | | * Adobe Author(s): Michiharu Ariza |
25 | | */ |
26 | | |
27 | | #include "hb.hh" |
28 | | |
29 | | #ifndef HB_NO_SUBSET_CFF |
30 | | |
31 | | #include "hb-open-type.hh" |
32 | | #include "hb-ot-cff2-table.hh" |
33 | | #include "hb-ot-var-common.hh" |
34 | | #include "hb-set.h" |
35 | | #include "hb-subset-plan.hh" |
36 | | #include "hb-subset-cff-common.hh" |
37 | | #include "hb-cff2-interp-cs.hh" |
38 | | #include "hb-subset-cff2-round.hh" |
39 | | #include "hb-subset-cff2-to-cff1.hh" |
40 | | |
41 | | using namespace CFF; |
42 | | |
43 | | namespace CFF { |
44 | | |
45 | | /* |
46 | | * Partial instancing (some axes pinned or limited, others kept). |
47 | | * |
48 | | * This is a port of fontTools' instantiateCFF2 (fonttools/fonttools#3506), |
49 | | * with one simplification: the variation-store instancing solver acts on |
50 | | * regions independently of the per-blend delta values, so instead of lifting |
51 | | * every blend's deltas into the store, instancing it, and reading the rows |
52 | | * back, we instance each VarData once with identity delta rows. The |
53 | | * instantiated tuples then give, per VarData, a fixed linear map from old |
54 | | * per-region deltas to new per-region deltas. The contribution that folds |
55 | | * into each blend's default value is the old store's region scalars |
56 | | * evaluated at the new default location (plan->normalized_coords); the |
57 | | * surviving rebased regions all evaluate to zero there. |
58 | | * |
59 | | * Use: create (), then note_use () for every blend the retained charstrings |
60 | | * and private dicts hold, then finalize (); only VarDatas that are both |
61 | | * still variable and actually used survive into the new store. |
62 | | */ |
63 | | struct cff2_instancing_plan_t |
64 | | { |
65 | | struct vardata_transform_t |
66 | | { |
67 | | /* new_deltas[j] = Σ_i matrix[j][i] * old_deltas[i] */ |
68 | | hb_vector_t<hb_vector_t<float>> matrix; |
69 | | /* Default-value fold-in scalars, one per old region: the old store's |
70 | | * region scalars at the new default location. */ |
71 | | hb_vector_t<float> gains; |
72 | | /* Surviving regions, parallel to matrix rows. */ |
73 | | hb_vector_t<hb_hashmap_t<hb_tag_t, Triple>> regions; |
74 | | /* Index into the new global region list, per new region (finalize). */ |
75 | | hb_vector_t<unsigned> region_indices; |
76 | | unsigned new_major = 0; |
77 | | bool alive_ = false; |
78 | 0 | bool alive () const { return alive_; } |
79 | | |
80 | | /* One value's delta for new region j, from its old per-region deltas. */ |
81 | | double transform_delta (unsigned j, hb_array_t<const number_t> deltas) const |
82 | 0 | { |
83 | 0 | const hb_vector_t<float> &col = matrix.arrayZ[j]; |
84 | 0 | double d = 0.; |
85 | 0 | unsigned count = hb_min (col.length, deltas.length); |
86 | 0 | for (unsigned c = 0; c < count; c++) |
87 | 0 | d += (double) col.arrayZ[c] * deltas.arrayZ[c].to_real (); |
88 | 0 | return d; |
89 | 0 | } |
90 | | }; |
91 | | |
92 | | hb_vector_t<vardata_transform_t> transforms; /* per old VarData (vsindex) */ |
93 | | hb_vector_t<hb_hashmap_t<hb_tag_t, Triple>> new_regions; /* global region list */ |
94 | | hb_set_t used_majors; |
95 | | unsigned new_major_count = 0; |
96 | | |
97 | 0 | bool has_variations () const { return new_major_count; } |
98 | | |
99 | | const vardata_transform_t *get_transform (unsigned major) const |
100 | 0 | { return major < transforms.length ? &transforms.arrayZ[major] : nullptr; } |
101 | | |
102 | | /* Record that a retained blend references this VarData. */ |
103 | 0 | void note_use (unsigned major) { used_majors.add (major); } |
104 | | |
105 | | /* The new vsindex a private dict's (or charstring's) old ivs maps to; |
106 | | * dead VarDatas fall back to 0, their blends having become static. */ |
107 | | unsigned remap_ivs (unsigned old_ivs) const |
108 | 0 | { |
109 | 0 | const vardata_transform_t *t = get_transform (old_ivs); |
110 | 0 | return (t && t->alive ()) ? t->new_major : 0; |
111 | 0 | } |
112 | | |
113 | | double fold_deltas (unsigned old_ivs, hb_array_t<const number_t> deltas) const |
114 | 0 | { |
115 | 0 | const vardata_transform_t *t = get_transform (old_ivs); |
116 | 0 | if (unlikely (!t || t->gains.length != deltas.length)) return 0.; |
117 | 0 | double v = 0.; |
118 | 0 | for (unsigned i = 0; i < deltas.length; i++) |
119 | 0 | v += (double) t->gains.arrayZ[i] * deltas.arrayZ[i].to_real (); |
120 | 0 | return v; |
121 | 0 | } |
122 | | |
123 | | bool create (const CFF2ItemVariationStore *cff2VarStore, |
124 | | const hb_subset_plan_t *plan) |
125 | 0 | { |
126 | 0 | const OT::ItemVariationStore &varStore = cff2VarStore->varStore; |
127 | 0 | unsigned major_count = varStore.get_sub_table_count (); |
128 | 0 | if (!transforms.resize (major_count)) return false; |
129 | | |
130 | 0 | if (!varStore.get_region_list ().get_var_regions (plan->axes_old_index_tag_map, orig_regions)) |
131 | 0 | return false; |
132 | | |
133 | | /* Instance each VarData with identity delta rows; the surviving tuples' |
134 | | * delta columns are the old-region → new-region transform. */ |
135 | 0 | OT::optimize_scratch_t scratch; |
136 | 0 | for (unsigned m = 0; m < major_count; m++) |
137 | 0 | { |
138 | 0 | const OT::VarData &var_data = varStore.get_sub_table (m); |
139 | 0 | unsigned k = var_data.get_region_index_count (); |
140 | 0 | vardata_transform_t &t = transforms.arrayZ[m]; |
141 | |
|
142 | 0 | if (!t.gains.resize_exact (k)) return false; |
143 | 0 | varStore.get_region_scalars (m, plan->normalized_coords.arrayZ, |
144 | 0 | plan->normalized_coords.length, |
145 | 0 | t.gains.arrayZ, k); |
146 | |
|
147 | 0 | OT::tuple_variations_t tuples; |
148 | 0 | for (unsigned r = 0; r < k; r++) |
149 | 0 | { |
150 | 0 | OT::tuple_delta_t tuple; |
151 | 0 | if (!tuple.deltas_x.resize (k) || |
152 | 0 | !tuple.indices.resize (k)) |
153 | 0 | return false; |
154 | 0 | for (unsigned i = 0; i < k; i++) |
155 | 0 | tuple.indices.arrayZ[i] = true; |
156 | 0 | tuple.deltas_x.arrayZ[r] = 1.f; |
157 | 0 | unsigned region_index = var_data.get_region_index (r); |
158 | 0 | if (unlikely (region_index >= orig_regions.length)) return false; |
159 | 0 | tuple.axis_tuples = orig_regions.arrayZ[region_index]; |
160 | 0 | if (unlikely (tuple.axis_tuples.in_error ())) return false; |
161 | 0 | tuples.tuple_vars.push (std::move (tuple)); |
162 | 0 | } |
163 | 0 | if (unlikely (tuples.tuple_vars.in_error ())) return false; |
164 | | |
165 | 0 | if (!tuples.instantiate (plan->axes_location, plan->axes_triple_distances, scratch)) |
166 | 0 | return false; |
167 | | |
168 | 0 | for (auto &tuple : tuples.tuple_vars) |
169 | 0 | { |
170 | 0 | if (unlikely (tuple.deltas_x.length != k)) return false; |
171 | 0 | hb_vector_t<float> col; |
172 | 0 | if (unlikely (!col.resize_exact (k))) return false; |
173 | 0 | for (unsigned i = 0; i < k; i++) |
174 | 0 | col.arrayZ[i] = tuple.deltas_x.arrayZ[i]; |
175 | 0 | t.matrix.push (std::move (col)); |
176 | 0 | t.regions.push (std::move (tuple.axis_tuples)); |
177 | 0 | } |
178 | 0 | if (unlikely (t.matrix.in_error () || t.regions.in_error ())) return false; |
179 | 0 | } |
180 | | |
181 | 0 | return true; |
182 | 0 | } |
183 | | |
184 | | /* Decide which VarDatas survive, number them, and build the new global |
185 | | * region list: surviving original regions first, in their original order, |
186 | | * then solver-split new regions in order of appearance (fontTools' |
187 | | * rebuildRegions + prune_regions). */ |
188 | | bool finalize () |
189 | 0 | { |
190 | 0 | for (unsigned m = 0; m < transforms.length; m++) |
191 | 0 | { |
192 | 0 | vardata_transform_t &t = transforms.arrayZ[m]; |
193 | 0 | t.alive_ = t.matrix.length && used_majors.has (m); |
194 | 0 | if (t.alive_) |
195 | 0 | t.new_major = new_major_count++; |
196 | 0 | } |
197 | | |
198 | | /* Keys hash by content, so pointers into different owners compare fine. */ |
199 | 0 | hb_hashmap_t<const hb_hashmap_t<hb_tag_t, Triple>*, unsigned> region_idx_map; |
200 | 0 | hb_vector_t<const hb_hashmap_t<hb_tag_t, Triple>*> region_ptrs; |
201 | 0 | for (const vardata_transform_t &t : transforms) |
202 | 0 | { |
203 | 0 | if (!t.alive ()) continue; |
204 | 0 | for (const auto ®ion : t.regions) |
205 | 0 | if (!region_idx_map.has (®ion)) |
206 | 0 | if (unlikely (!region_idx_map.set (®ion, (unsigned) -1))) |
207 | 0 | return false; |
208 | 0 | } |
209 | 0 | for (const auto &r : orig_regions) |
210 | 0 | { |
211 | 0 | unsigned *idx; |
212 | 0 | if (region_idx_map.has (&r, &idx) && *idx == (unsigned) -1) |
213 | 0 | { |
214 | 0 | if (unlikely (!region_idx_map.set (&r, region_ptrs.length))) return false; |
215 | 0 | region_ptrs.push (&r); |
216 | 0 | } |
217 | 0 | } |
218 | 0 | for (const vardata_transform_t &t : transforms) |
219 | 0 | { |
220 | 0 | if (!t.alive ()) continue; |
221 | 0 | for (const auto ®ion : t.regions) |
222 | 0 | { |
223 | 0 | unsigned *idx; |
224 | 0 | if (region_idx_map.has (®ion, &idx) && *idx == (unsigned) -1) |
225 | 0 | { |
226 | 0 | if (unlikely (!region_idx_map.set (®ion, region_ptrs.length))) return false; |
227 | 0 | region_ptrs.push (®ion); |
228 | 0 | } |
229 | 0 | } |
230 | 0 | } |
231 | 0 | if (unlikely (region_ptrs.in_error ())) return false; |
232 | | |
233 | 0 | for (vardata_transform_t &t : transforms) |
234 | 0 | { |
235 | 0 | if (!t.alive ()) continue; |
236 | 0 | for (const auto ®ion : t.regions) |
237 | 0 | { |
238 | 0 | unsigned *idx; |
239 | 0 | if (unlikely (!region_idx_map.has (®ion, &idx))) return false; |
240 | 0 | t.region_indices.push (*idx); |
241 | 0 | } |
242 | 0 | if (unlikely (t.region_indices.in_error ())) return false; |
243 | 0 | } |
244 | | |
245 | 0 | if (!new_regions.resize (region_ptrs.length)) return false; |
246 | 0 | for (unsigned i = 0; i < region_ptrs.length; i++) |
247 | 0 | { |
248 | 0 | new_regions.arrayZ[i] = *region_ptrs.arrayZ[i]; |
249 | 0 | if (unlikely (new_regions.arrayZ[i].in_error ())) return false; |
250 | 0 | } |
251 | | |
252 | 0 | return true; |
253 | 0 | } |
254 | | |
255 | | /* Serialize the instanced store: a CFF2ItemVariationStore whose VarDatas |
256 | | * carry no items, just region indexes (blend deltas live in charstrings). |
257 | | * Laid out manually since all sizes are known up front. */ |
258 | | bool serialize_var_store (hb_serialize_context_t *c, |
259 | | const hb_vector_t<hb_tag_t> &axis_tags) const |
260 | 0 | { |
261 | 0 | unsigned data_count = new_major_count; |
262 | 0 | unsigned header_size = 2 + 4 + 2 + 4 * data_count; |
263 | |
|
264 | 0 | uint64_t offset = header_size; |
265 | 0 | hb_vector_t<unsigned> data_offsets; |
266 | 0 | for (const vardata_transform_t &t : transforms) |
267 | 0 | { |
268 | 0 | if (!t.alive ()) continue; |
269 | 0 | data_offsets.push ((unsigned) offset); |
270 | 0 | offset += 6 + 2 * (uint64_t) t.region_indices.length; |
271 | 0 | } |
272 | 0 | if (unlikely (data_offsets.in_error () || data_offsets.length != data_count)) |
273 | 0 | return false; |
274 | 0 | unsigned regions_offset = (unsigned) offset; |
275 | 0 | uint64_t total = offset + 4 + (uint64_t) new_regions.length * axis_tags.length * 6; |
276 | 0 | if (unlikely (total > 0xFFFFFFFFu)) return false; |
277 | | |
278 | 0 | HBUINT16 *size_field = c->allocate_size<HBUINT16> (HBUINT16::static_size); |
279 | 0 | if (unlikely (!size_field)) return false; |
280 | | /* OFF 2026 5.3.3.11.2 permits larger stores with length=65535. */ |
281 | 0 | *size_field = hb_min (total, (uint64_t) 0xFFFFu); |
282 | |
|
283 | 0 | char *header = c->allocate_size<char> (header_size); |
284 | 0 | if (unlikely (!header)) return false; |
285 | 0 | * (HBUINT16 *) (header + 0) = 1; /* format */ |
286 | 0 | * (HBUINT32 *) (header + 2) = regions_offset; |
287 | 0 | * (HBUINT16 *) (header + 6) = data_count; |
288 | 0 | for (unsigned i = 0; i < data_count; i++) |
289 | 0 | * (HBUINT32 *) (header + 8 + 4 * i) = data_offsets.arrayZ[i]; |
290 | |
|
291 | 0 | for (const vardata_transform_t &t : transforms) |
292 | 0 | { |
293 | 0 | if (!t.alive ()) continue; |
294 | 0 | unsigned k = t.region_indices.length; |
295 | 0 | HBUINT16 *v = (HBUINT16 *) c->allocate_size<char> (6 + 2 * k); |
296 | 0 | if (unlikely (!v)) return false; |
297 | 0 | v[0] = 0; /* itemCount */ |
298 | 0 | v[1] = 0; /* wordSizeCount */ |
299 | 0 | v[2] = k; /* regionIndices.len */ |
300 | 0 | for (unsigned j = 0; j < k; j++) |
301 | 0 | v[3 + j] = t.region_indices.arrayZ[j]; |
302 | 0 | } |
303 | | |
304 | 0 | hb_vector_t<const hb_hashmap_t<hb_tag_t, Triple>*> region_ptrs; |
305 | 0 | if (unlikely (!region_ptrs.alloc_exact (new_regions.length))) return false; |
306 | 0 | for (const auto &r : new_regions) |
307 | 0 | region_ptrs.push (&r); |
308 | 0 | auto *region_list = c->start_embed<OT::VarRegionList> (); |
309 | 0 | return region_list->serialize (c, axis_tags, region_ptrs); |
310 | 0 | } |
311 | | |
312 | | private: |
313 | | hb_vector_t<hb_hashmap_t<hb_tag_t, Triple>> orig_regions; |
314 | | }; |
315 | | |
316 | | /* Usage-collection pass for partial instancing: record which VarDatas the |
317 | | * retained charstrings' and private dicts' blends reference. Mirrors the |
318 | | * emission pass: blends consumed by dropped hint ops don't count. */ |
319 | | struct cff2_usage_param_t |
320 | | { |
321 | 0 | void init () {} |
322 | | |
323 | | cff2_instancing_plan_t *instancing = nullptr; |
324 | | bool drop_hints = false; |
325 | | bool has_blend = false; |
326 | | bool has_flex1 = false; |
327 | | unsigned ivs = 0; /* dict-side current vsindex */ |
328 | | }; |
329 | | |
330 | | struct cff2_cs_opset_usage_t : cff2_cs_opset_t<cff2_cs_opset_usage_t, cff2_usage_param_t, blend_arg_t> |
331 | | { |
332 | | static void flush_args_and_op (op_code_t op, cff2_cs_interp_env_t<blend_arg_t> &env, cff2_usage_param_t& param) |
333 | 0 | { |
334 | 0 | param.has_flex1 |= op == OpCode_flex1; |
335 | 0 | bool counts = true; |
336 | 0 | switch (op) |
337 | 0 | { |
338 | 0 | case OpCode_return: |
339 | 0 | case OpCode_endchar: |
340 | 0 | counts = false; |
341 | 0 | break; |
342 | | |
343 | 0 | case OpCode_hstem: |
344 | 0 | case OpCode_hstemhm: |
345 | 0 | case OpCode_vstem: |
346 | 0 | case OpCode_vstemhm: |
347 | 0 | case OpCode_hintmask: |
348 | 0 | case OpCode_cntrmask: |
349 | 0 | if (param.drop_hints) |
350 | 0 | counts = false; |
351 | 0 | break; |
352 | | |
353 | 0 | default: |
354 | 0 | break; |
355 | 0 | } |
356 | | |
357 | 0 | if (counts) |
358 | 0 | for (unsigned int i = 0; i < env.argStack.get_count (); i++) |
359 | 0 | if (env.argStack[i].blending ()) |
360 | 0 | { |
361 | 0 | param.has_blend = true; |
362 | 0 | if (param.instancing) param.instancing->note_use (env.get_ivs ()); |
363 | 0 | break; |
364 | 0 | } |
365 | |
|
366 | 0 | env.clear_args (); |
367 | 0 | } |
368 | | }; |
369 | | |
370 | | struct cff2_private_dict_usage_opset_t : dict_opset_t |
371 | | { |
372 | | static void process_op (op_code_t op, cff2_priv_dict_interp_env_t& env, cff2_usage_param_t& param) |
373 | 0 | { |
374 | 0 | switch (op) { |
375 | 0 | case OpCode_vsindexdict: |
376 | 0 | env.process_vsindex (); |
377 | 0 | param.ivs = env.get_ivs (); |
378 | 0 | env.clear_args (); |
379 | 0 | return; |
380 | 0 | case OpCode_blenddict: |
381 | 0 | param.instancing->note_use (param.ivs); |
382 | 0 | env.clear_args (); |
383 | 0 | return; |
384 | 0 | default: |
385 | 0 | dict_opset_t::process_op (op, env); |
386 | 0 | if (!env.argStack.is_empty ()) return; |
387 | 0 | env.clear_args (); |
388 | 0 | return; |
389 | 0 | } |
390 | 0 | } |
391 | | }; |
392 | | |
393 | | } /* namespace CFF */ |
394 | | |
395 | | struct cff2_sub_table_info_t : cff_sub_table_info_t |
396 | | { |
397 | | cff2_sub_table_info_t () |
398 | 0 | : cff_sub_table_info_t (), |
399 | 0 | var_store_link (0) |
400 | 0 | {} |
401 | | |
402 | | objidx_t var_store_link; |
403 | | }; |
404 | | |
405 | | struct cff2_top_dict_op_serializer_t : cff_top_dict_op_serializer_t<> |
406 | | { |
407 | | bool serialize (hb_serialize_context_t *c, |
408 | | const op_str_t &opstr, |
409 | | const cff2_sub_table_info_t &info) const |
410 | 0 | { |
411 | 0 | TRACE_SERIALIZE (this); |
412 | |
|
413 | 0 | switch (opstr.op) |
414 | 0 | { |
415 | 0 | case OpCode_vstore: |
416 | 0 | if (info.var_store_link) |
417 | 0 | return_trace (FontDict::serialize_link4_op(c, opstr.op, info.var_store_link)); |
418 | 0 | else |
419 | 0 | return_trace (true); |
420 | | |
421 | 0 | default: |
422 | 0 | return_trace (cff_top_dict_op_serializer_t<>::serialize (c, opstr, info)); |
423 | 0 | } |
424 | 0 | } |
425 | | }; |
426 | | |
427 | | struct cff2_cs_opset_flatten_t : cff2_cs_opset_t<cff2_cs_opset_flatten_t, flatten_param_t, blend_arg_t> |
428 | | { |
429 | | static void flush_args_and_op (op_code_t op, cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param) |
430 | 0 | { |
431 | 0 | bool skip_command = false; |
432 | 0 | switch (op) |
433 | 0 | { |
434 | 0 | case OpCode_return: |
435 | 0 | case OpCode_endchar: |
436 | 0 | skip_command = true; |
437 | 0 | break; |
438 | | |
439 | 0 | case OpCode_hstem: |
440 | 0 | case OpCode_hstemhm: |
441 | 0 | case OpCode_vstem: |
442 | 0 | case OpCode_vstemhm: |
443 | 0 | case OpCode_hintmask: |
444 | 0 | case OpCode_cntrmask: |
445 | 0 | skip_command = param.drop_hints; |
446 | 0 | break; |
447 | | |
448 | 0 | default: |
449 | 0 | break; |
450 | 0 | } |
451 | | |
452 | 0 | if (!skip_command && param.rounding && unlikely (!round_args (op, env, param))) |
453 | 0 | { |
454 | 0 | env.set_error (); |
455 | 0 | return; |
456 | 0 | } |
457 | | |
458 | | /* Capture resolved commands for CFF2-to-CFF1 specialization. */ |
459 | 0 | if (param.commands && !skip_command) |
460 | 0 | { |
461 | 0 | cs_command_t cmd (op); |
462 | 0 | for (unsigned int i = 0; i < env.argStack.get_count ();) |
463 | 0 | { |
464 | 0 | const blend_arg_t &arg = env.argStack[i]; |
465 | 0 | if (arg.blending ()) |
466 | 0 | { |
467 | 0 | cmd.args.push (arg); |
468 | 0 | i += arg.numValues; |
469 | 0 | } |
470 | 0 | else |
471 | 0 | { |
472 | 0 | cmd.args.push (arg); |
473 | 0 | i++; |
474 | 0 | } |
475 | 0 | } |
476 | 0 | param.commands->push (cmd); |
477 | 0 | } |
478 | |
|
479 | 0 | switch (op) |
480 | 0 | { |
481 | 0 | case OpCode_return: |
482 | 0 | case OpCode_endchar: |
483 | | /* dummy opcodes in CFF2. ignore */ |
484 | 0 | if (op == OpCode_endchar && param.instancer) |
485 | 0 | emit_vsindex_prefix (env, param); |
486 | 0 | break; |
487 | | |
488 | 0 | case OpCode_hstem: |
489 | 0 | case OpCode_hstemhm: |
490 | 0 | case OpCode_vstem: |
491 | 0 | case OpCode_vstemhm: |
492 | 0 | case OpCode_hintmask: |
493 | 0 | case OpCode_cntrmask: |
494 | 0 | if (param.drop_hints) |
495 | 0 | { |
496 | 0 | env.clear_args (); |
497 | 0 | return; |
498 | 0 | } |
499 | 0 | HB_FALLTHROUGH; |
500 | |
|
501 | 0 | default: |
502 | 0 | SUPER::flush_args_and_op (op, env, param); |
503 | 0 | break; |
504 | 0 | } |
505 | 0 | } |
506 | | |
507 | | static void flush_args (cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param) |
508 | 0 | { |
509 | 0 | if (param.rounding) |
510 | 0 | { |
511 | 0 | encode_rounded_args (env, param); |
512 | 0 | SUPER::flush_args (env, param); |
513 | 0 | return; |
514 | 0 | } |
515 | 0 | for (unsigned int i = 0; i < env.argStack.get_count ();) |
516 | 0 | { |
517 | 0 | const blend_arg_t &arg = env.argStack[i]; |
518 | 0 | if (arg.blending ()) |
519 | 0 | { |
520 | 0 | if (unlikely (!((arg.numValues > 0) && (env.argStack.get_count () >= arg.numValues)))) |
521 | 0 | { |
522 | 0 | env.set_error (); |
523 | 0 | return; |
524 | 0 | } |
525 | 0 | if (param.instancer) |
526 | 0 | rewrite_blends (arg, i, env, param); |
527 | 0 | else |
528 | 0 | flatten_blends (arg, i, env, param); |
529 | 0 | i += arg.numValues; |
530 | 0 | } |
531 | 0 | else |
532 | 0 | { |
533 | 0 | str_encoder_t encoder (param.flatStr); |
534 | 0 | encoder.encode_num_cs (arg); |
535 | 0 | i++; |
536 | 0 | } |
537 | 0 | } |
538 | 0 | SUPER::flush_args (env, param); |
539 | 0 | } |
540 | | |
541 | | static bool round_args (op_code_t op, cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param) |
542 | 0 | { |
543 | 0 | unsigned k = 0; |
544 | 0 | if (param.instancer) |
545 | 0 | { |
546 | 0 | unsigned ivs = env.get_ivs (); |
547 | 0 | const auto *t = param.instancer->get_transform (ivs); |
548 | 0 | k = t && t->alive () ? t->matrix.length : 0; |
549 | 0 | for (unsigned i = 0; i < env.argStack.get_count (); i++) |
550 | 0 | { |
551 | 0 | auto &arg = env.argStack[i]; |
552 | 0 | if (!arg.blending ()) continue; |
553 | 0 | if (unlikely (!t || arg.deltas.length != t->gains.length)) return false; |
554 | 0 | double value = arg.to_real () + param.instancer->fold_deltas (ivs, arg.deltas.as_array ()); |
555 | 0 | hb_vector_t<number_t> deltas = std::move (arg.deltas); |
556 | 0 | arg.set_real (value); |
557 | 0 | if (unlikely (!arg.deltas.resize (k))) return false; |
558 | 0 | for (unsigned r = 0; r < k; r++) |
559 | 0 | arg.deltas.arrayZ[r].set_real (t->transform_delta (r, deltas.as_array ())); |
560 | 0 | } |
561 | 0 | } |
562 | 0 | return param.rounding->round (op, hb_array (env.argStack.arrayZ, env.argStack.get_count ()), k); |
563 | 0 | } |
564 | | |
565 | | static void encode_rounded_args (cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param) |
566 | 0 | { |
567 | 0 | str_encoder_t encoder (param.flatStr); |
568 | 0 | unsigned count = env.argStack.get_count (); |
569 | 0 | for (unsigned i = 0; i < count;) |
570 | 0 | { |
571 | 0 | const auto &arg = env.argStack[i]; |
572 | 0 | if (!arg.blending ()) |
573 | 0 | { |
574 | 0 | encoder.encode_num_cs (arg); |
575 | 0 | i++; |
576 | 0 | continue; |
577 | 0 | } |
578 | 0 | unsigned k = arg.deltas.length; |
579 | 0 | unsigned n = 1; |
580 | 0 | while (i + n < count && env.argStack[i + n].deltas.length == k) n++; |
581 | | /* Previously emitted operands remain on the stack during each blend. */ |
582 | 0 | unsigned chunk = hb_min (n, (512 - i) / (k + 1)); |
583 | 0 | if (unlikely (!chunk)) |
584 | 0 | { |
585 | 0 | env.set_error (); |
586 | 0 | return; |
587 | 0 | } |
588 | 0 | for (unsigned j = 0; j < chunk; j++) |
589 | 0 | encoder.encode_num_cs (env.argStack[i + j]); |
590 | 0 | for (unsigned j = 0; j < chunk; j++) |
591 | 0 | for (const auto &d : env.argStack[i + j].deltas) |
592 | 0 | encoder.encode_num_cs (d); |
593 | 0 | encoder.encode_int (chunk); |
594 | 0 | encoder.encode_op (OpCode_blendcs); |
595 | 0 | param.emitted_blend = true; |
596 | 0 | i += chunk; |
597 | 0 | } |
598 | 0 | } |
599 | | |
600 | | static void flatten_blends (const blend_arg_t &arg, unsigned int i, cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param) |
601 | 0 | { |
602 | | /* flatten the default values */ |
603 | 0 | str_encoder_t encoder (param.flatStr); |
604 | 0 | for (unsigned int j = 0; j < arg.numValues; j++) |
605 | 0 | { |
606 | 0 | const blend_arg_t &arg1 = env.argStack[i + j]; |
607 | 0 | if (unlikely (!((arg1.blending () && (arg.numValues == arg1.numValues) && (arg1.valueIndex == j) && |
608 | 0 | (arg1.deltas.length == env.get_region_count ()))))) |
609 | 0 | { |
610 | 0 | env.set_error (); |
611 | 0 | return; |
612 | 0 | } |
613 | 0 | encoder.encode_num_cs (arg1); |
614 | 0 | } |
615 | | /* flatten deltas for each value */ |
616 | 0 | for (unsigned int j = 0; j < arg.numValues; j++) |
617 | 0 | { |
618 | 0 | const blend_arg_t &arg1 = env.argStack[i + j]; |
619 | 0 | for (unsigned int k = 0; k < arg1.deltas.length; k++) |
620 | 0 | encoder.encode_num_cs (arg1.deltas[k]); |
621 | 0 | } |
622 | | /* flatten the number of values followed by blend operator */ |
623 | 0 | encoder.encode_int (arg.numValues); |
624 | 0 | encoder.encode_op (OpCode_blendcs); |
625 | 0 | } |
626 | | |
627 | | /* Partial instancing: rewrite one blend group against the instanced store. |
628 | | * Each value's default absorbs the folded (pinned-away) contribution, and |
629 | | * its deltas are mapped onto the new region list. */ |
630 | | static void rewrite_blends (const blend_arg_t &arg, unsigned int i, cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param) |
631 | 0 | { |
632 | 0 | const cff2_instancing_plan_t &instancer = *param.instancer; |
633 | 0 | unsigned ivs = env.get_ivs (); |
634 | 0 | const cff2_instancing_plan_t::vardata_transform_t *t = instancer.get_transform (ivs); |
635 | 0 | if (unlikely (!t)) |
636 | 0 | { |
637 | 0 | env.set_error (); |
638 | 0 | return; |
639 | 0 | } |
640 | | |
641 | 0 | unsigned n = arg.numValues; |
642 | 0 | for (unsigned int j = 0; j < n; j++) |
643 | 0 | { |
644 | 0 | const blend_arg_t &arg1 = env.argStack[i + j]; |
645 | 0 | if (unlikely (!(arg1.blending () && (arg1.numValues == n) && (arg1.valueIndex == j) && |
646 | 0 | (arg1.deltas.length == env.get_region_count ()) && |
647 | 0 | (arg1.deltas.length == t->gains.length)))) |
648 | 0 | { |
649 | 0 | env.set_error (); |
650 | 0 | return; |
651 | 0 | } |
652 | 0 | } |
653 | | |
654 | 0 | str_encoder_t encoder (param.flatStr); |
655 | 0 | unsigned k_new = t->alive () ? t->matrix.length : 0; |
656 | |
|
657 | 0 | if (!k_new) |
658 | 0 | { |
659 | | /* All of this VarData's regions died; blends dissolve into statics. */ |
660 | 0 | for (unsigned int j = 0; j < n; j++) |
661 | 0 | { |
662 | 0 | const blend_arg_t &arg1 = env.argStack[i + j]; |
663 | 0 | number_t v; |
664 | 0 | v.set_real (arg1.to_real () + round (instancer.fold_deltas (ivs, arg1.deltas.as_array ()))); |
665 | 0 | encoder.encode_num_cs (v); |
666 | 0 | } |
667 | 0 | return; |
668 | 0 | } |
669 | | |
670 | | /* Emit in chunks so the output charstring stays within the argument |
671 | | * stack limit: values already emitted for this op, plus this chunk's |
672 | | * defaults, deltas and count, must not exceed 513. */ |
673 | 0 | unsigned done = 0; |
674 | 0 | while (done < n) |
675 | 0 | { |
676 | 0 | unsigned avail = 512 > (i + done) ? 512 - (i + done) : 0; |
677 | 0 | unsigned chunk = hb_min (avail / (k_new + 1), n - done); |
678 | 0 | if (unlikely (!chunk)) |
679 | 0 | { |
680 | 0 | env.set_error (); |
681 | 0 | return; |
682 | 0 | } |
683 | | |
684 | 0 | for (unsigned int j = done; j < done + chunk; j++) |
685 | 0 | { |
686 | 0 | const blend_arg_t &arg1 = env.argStack[i + j]; |
687 | 0 | number_t v; |
688 | 0 | v.set_real (arg1.to_real () + round (instancer.fold_deltas (ivs, arg1.deltas.as_array ()))); |
689 | 0 | encoder.encode_num_cs (v); |
690 | 0 | } |
691 | 0 | for (unsigned int j = done; j < done + chunk; j++) |
692 | 0 | { |
693 | 0 | const blend_arg_t &arg1 = env.argStack[i + j]; |
694 | 0 | for (unsigned r = 0; r < k_new; r++) |
695 | 0 | { |
696 | 0 | number_t v; |
697 | 0 | v.set_int (hb_clamp_to<int> (round (t->transform_delta (r, arg1.deltas.as_array ())))); |
698 | 0 | encoder.encode_num_cs (v); |
699 | 0 | } |
700 | 0 | } |
701 | 0 | encoder.encode_int (chunk); |
702 | 0 | encoder.encode_op (OpCode_blendcs); |
703 | 0 | done += chunk; |
704 | 0 | } |
705 | 0 | param.emitted_blend = true; |
706 | 0 | } |
707 | | |
708 | | /* Partial instancing: prepend a vsindex op when the charstring's |
709 | | * (remapped) ivs differs from its FD's (remapped) private-dict ivs. */ |
710 | | static void emit_vsindex_prefix (cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param) |
711 | 0 | { |
712 | 0 | if (!param.emitted_blend) return; |
713 | 0 | const cff2_instancing_plan_t &instancer = *param.instancer; |
714 | 0 | unsigned new_ivs = instancer.remap_ivs (env.get_ivs ()); |
715 | 0 | unsigned fd_ivs = instancer.remap_ivs (env.get_orig_ivs ()); |
716 | 0 | if (new_ivs == fd_ivs) return; |
717 | | |
718 | 0 | str_buff_t prefix; |
719 | 0 | str_encoder_t enc (prefix); |
720 | 0 | enc.encode_int ((int) new_ivs); |
721 | 0 | enc.encode_op (OpCode_vsindexcs); |
722 | |
|
723 | 0 | str_buff_t &buf = param.flatStr; |
724 | 0 | unsigned old_len = buf.length; |
725 | 0 | if (unlikely (prefix.in_error () || !buf.resize (old_len + prefix.length))) |
726 | 0 | { |
727 | 0 | env.set_error (); |
728 | 0 | return; |
729 | 0 | } |
730 | 0 | memmove (buf.arrayZ + prefix.length, buf.arrayZ, old_len); |
731 | 0 | hb_memcpy (buf.arrayZ, prefix.arrayZ, prefix.length); |
732 | 0 | } |
733 | | |
734 | | static void flush_op (op_code_t op, cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param) |
735 | 0 | { |
736 | 0 | switch (op) |
737 | 0 | { |
738 | 0 | case OpCode_return: |
739 | 0 | case OpCode_endchar: |
740 | 0 | return; |
741 | 0 | default: |
742 | 0 | str_encoder_t encoder (param.flatStr); |
743 | 0 | encoder.encode_op (op); |
744 | 0 | } |
745 | 0 | } |
746 | | |
747 | | static void flush_hintmask (op_code_t op, cff2_cs_interp_env_t<blend_arg_t> &env, flatten_param_t& param) |
748 | 0 | { |
749 | 0 | SUPER::flush_hintmask (op, env, param); |
750 | | /* Preserve hintmask payload in captured commands for specializer re-encoding. */ |
751 | 0 | if (param.commands && !param.drop_hints && param.commands->length > 0) |
752 | 0 | { |
753 | 0 | auto &cmd = param.commands->tail (); |
754 | 0 | if (cmd.op == op) |
755 | 0 | { |
756 | 0 | cmd.mask_bytes.resize (env.hintmask_size); |
757 | 0 | if (unlikely (cmd.mask_bytes.in_error ())) |
758 | 0 | { |
759 | 0 | env.set_error (); |
760 | 0 | return; |
761 | 0 | } |
762 | 0 | for (unsigned int i = 0; i < env.hintmask_size; i++) |
763 | 0 | cmd.mask_bytes[i] = env.str_ref[i]; |
764 | 0 | } |
765 | 0 | } |
766 | 0 | if (!param.drop_hints) |
767 | 0 | { |
768 | 0 | str_encoder_t encoder (param.flatStr); |
769 | 0 | for (unsigned int i = 0; i < env.hintmask_size; i++) |
770 | 0 | encoder.encode_byte (env.str_ref[i]); |
771 | 0 | } |
772 | 0 | } |
773 | | |
774 | | private: |
775 | | typedef cff2_cs_opset_t<cff2_cs_opset_flatten_t, flatten_param_t, blend_arg_t> SUPER; |
776 | | typedef cs_opset_t<blend_arg_t, cff2_cs_opset_flatten_t, cff2_cs_opset_flatten_t, cff2_cs_interp_env_t<blend_arg_t>, flatten_param_t> CSOPSET; |
777 | | }; |
778 | | |
779 | | struct cff2_cs_opset_subr_subset_t : cff2_cs_opset_t<cff2_cs_opset_subr_subset_t, subr_subset_param_t, blend_arg_t> |
780 | | { |
781 | | static void process_op (op_code_t op, cff2_cs_interp_env_t<blend_arg_t> &env, subr_subset_param_t& param) |
782 | 0 | { |
783 | 0 | switch (op) { |
784 | | |
785 | 0 | case OpCode_return: |
786 | 0 | param.current_parsed_str->flush_coalesced (env.str_ref); |
787 | 0 | param.current_parsed_str->set_parsed (); |
788 | 0 | env.return_from_subr (); |
789 | 0 | param.set_current_str (env, false); |
790 | 0 | break; |
791 | | |
792 | 0 | case OpCode_endchar: |
793 | 0 | param.current_parsed_str->flush_coalesced (env.str_ref); |
794 | 0 | param.current_parsed_str->set_parsed (); |
795 | 0 | SUPER::process_op (op, env, param); |
796 | 0 | break; |
797 | | |
798 | 0 | case OpCode_callsubr: |
799 | 0 | process_call_subr (op, CSType_LocalSubr, env, param, env.localSubrs, param.local_closure); |
800 | 0 | break; |
801 | | |
802 | 0 | case OpCode_callgsubr: |
803 | 0 | process_call_subr (op, CSType_GlobalSubr, env, param, env.globalSubrs, param.global_closure); |
804 | 0 | break; |
805 | | |
806 | 0 | default: |
807 | 0 | SUPER::process_op (op, env, param); |
808 | 0 | param.current_parsed_str->add_op (op, env.str_ref); |
809 | 0 | break; |
810 | 0 | } |
811 | 0 | } |
812 | | |
813 | | protected: |
814 | | static void process_call_subr (op_code_t op, cs_type_t type, |
815 | | cff2_cs_interp_env_t<blend_arg_t> &env, subr_subset_param_t& param, |
816 | | cff2_biased_subrs_t& subrs, hb_set_t *closure) |
817 | 0 | { |
818 | 0 | if (unlikely (!param.current_parsed_str->has_immediate_subr_number ())) |
819 | 0 | { |
820 | 0 | env.set_error (); |
821 | 0 | return; |
822 | 0 | } |
823 | 0 | byte_str_ref_t str_ref = env.str_ref; |
824 | 0 | env.call_subr (subrs, type); |
825 | 0 | param.current_parsed_str->add_call_op (op, str_ref, env.context.subr_num); |
826 | 0 | closure->add (env.context.subr_num); |
827 | 0 | param.set_current_str (env, true); |
828 | 0 | } |
829 | | |
830 | | private: |
831 | | typedef cff2_cs_opset_t<cff2_cs_opset_subr_subset_t, subr_subset_param_t, blend_arg_t> SUPER; |
832 | | }; |
833 | | |
834 | | struct cff2_subr_subsetter_t : subr_subsetter_t<cff2_subr_subsetter_t, CFF2Subrs, const OT::cff2::accelerator_subset_t, cff2_cs_interp_env_t<blend_arg_t>, cff2_cs_opset_subr_subset_t> |
835 | | { |
836 | | cff2_subr_subsetter_t (const OT::cff2::accelerator_subset_t &acc_, const hb_subset_plan_t *plan_) |
837 | 0 | : subr_subsetter_t (acc_, plan_) {} |
838 | | |
839 | | static void complete_parsed_str (cff2_cs_interp_env_t<blend_arg_t> &env, subr_subset_param_t& param, parsed_cs_str_t &charstring) |
840 | 0 | { |
841 | | /* vsindex is inserted at the beginning of the charstring as necessary */ |
842 | 0 | if (env.seen_vsindex ()) |
843 | 0 | { |
844 | 0 | number_t ivs; |
845 | 0 | ivs.set_int ((int)env.get_ivs ()); |
846 | 0 | charstring.set_prefix (ivs, OpCode_vsindexcs); |
847 | 0 | } |
848 | 0 | } |
849 | | }; |
850 | | |
851 | | struct cff2_private_blend_encoder_param_t |
852 | | { |
853 | | cff2_private_blend_encoder_param_t (hb_serialize_context_t *c, |
854 | | const CFF2ItemVariationStore *varStore, |
855 | | hb_array_t<int> normalized_coords) : |
856 | 0 | c (c), varStore (varStore), normalized_coords (normalized_coords) {} |
857 | | |
858 | 0 | void init () {} |
859 | | |
860 | | void process_blend () |
861 | 0 | { |
862 | 0 | if (!seen_blend) |
863 | 0 | { |
864 | 0 | region_count = varStore->varStore.get_region_index_count (ivs); |
865 | 0 | if (unlikely (!scalars.resize_exact (region_count))) |
866 | 0 | { |
867 | 0 | if (c) c->err (HB_SERIALIZE_ERROR_OTHER); |
868 | 0 | seen_blend = true; |
869 | 0 | return; |
870 | 0 | } |
871 | 0 | varStore->varStore.get_region_scalars (ivs, normalized_coords.arrayZ, normalized_coords.length, |
872 | 0 | &scalars[0], region_count); |
873 | 0 | seen_blend = true; |
874 | 0 | } |
875 | 0 | } |
876 | | |
877 | | double blend_deltas (hb_array_t<const number_t> deltas) const |
878 | 0 | { |
879 | 0 | double v = 0; |
880 | 0 | if (likely (scalars.length == deltas.length)) |
881 | 0 | { |
882 | 0 | unsigned count = scalars.length; |
883 | 0 | for (unsigned i = 0; i < count; i++) |
884 | 0 | v += (double) scalars.arrayZ[i] * deltas.arrayZ[i].to_real (); |
885 | 0 | } |
886 | 0 | return v; |
887 | 0 | } |
888 | | |
889 | | |
890 | | hb_serialize_context_t *c = nullptr; |
891 | | bool seen_blend = false; |
892 | | unsigned ivs = 0; |
893 | | unsigned region_count = 0; |
894 | | hb_vector_t<float> scalars; |
895 | | const CFF2ItemVariationStore *varStore = nullptr; |
896 | | hb_array_t<int> normalized_coords; |
897 | | |
898 | | /* CFF2 partial instancing: when set, blends are rewritten against the |
899 | | * instanced variation store instead of being flattened. */ |
900 | | const cff2_instancing_plan_t *instancer = nullptr; |
901 | | /* Error-compensated fold rounding state, per dict operator. */ |
902 | | double fold_exact = 0.; |
903 | | double fold_emitted = 0.; |
904 | | }; |
905 | | |
906 | | struct cff2_private_dict_blend_opset_t : dict_opset_t |
907 | | { |
908 | | static void process_arg_blend (cff2_private_blend_encoder_param_t& param, |
909 | | number_t &arg, |
910 | | const hb_array_t<const number_t> blends, |
911 | | unsigned n, unsigned i) |
912 | 0 | { |
913 | 0 | arg.set_int (round (arg.to_real () + param.blend_deltas (blends))); |
914 | 0 | } |
915 | | |
916 | | /* The fold added to a value, error-compensated across the operator: most |
917 | | * blended private-dict values (BlueValues, StemSnap*, ...) are |
918 | | * delta-encoded, so independently rounded folds would accumulate in the |
919 | | * decoded absolutes; keeping the emitted cumulative fold equal to the |
920 | | * rounded exact cumulative fold bounds every absolute within ±0.5. */ |
921 | | static double fold (cff2_private_blend_encoder_param_t& param, |
922 | | const hb_array_t<const number_t> blends) |
923 | 0 | { |
924 | 0 | double f = param.blend_deltas (blends); |
925 | 0 | double emit = round (param.fold_exact + f) - param.fold_emitted; |
926 | 0 | param.fold_exact += f; |
927 | 0 | param.fold_emitted += emit; |
928 | 0 | return emit; |
929 | 0 | } |
930 | | |
931 | | /* Partial instancing: emit the args preceding this blend group, then the |
932 | | * group itself rewritten against the instanced store, and clear the |
933 | | * stack. The op's remaining args are flushed at process_op time. */ |
934 | | static void rewrite_blends (cff2_priv_dict_interp_env_t& env, |
935 | | cff2_private_blend_encoder_param_t& param, |
936 | | unsigned start, unsigned n, unsigned k) |
937 | 0 | { |
938 | 0 | const cff2_instancing_plan_t &instancer = *param.instancer; |
939 | 0 | const cff2_instancing_plan_t::vardata_transform_t *t = instancer.get_transform (param.ivs); |
940 | 0 | if (unlikely (!t || t->gains.length != k)) |
941 | 0 | { |
942 | 0 | env.set_error (); |
943 | 0 | return; |
944 | 0 | } |
945 | | |
946 | 0 | str_buff_t str; |
947 | 0 | str_encoder_t encoder (str); |
948 | |
|
949 | 0 | for (unsigned idx = 0; idx < start; idx++) |
950 | 0 | encoder.encode_num_tp (env.argStack[idx]); |
951 | |
|
952 | 0 | unsigned k_new = t->alive () ? t->matrix.length : 0; |
953 | 0 | if (!k_new) |
954 | 0 | { |
955 | | /* This VarData died (or is unused by any retained charstring, which |
956 | | * can happen for FDs kept only for retain-gids holes); blends |
957 | | * dissolve into statics. */ |
958 | 0 | for (unsigned j = 0; j < n; j++) |
959 | 0 | { |
960 | 0 | const hb_array_t<const number_t> blends = env.argStack.sub_array (start + n + (j * k), k); |
961 | 0 | number_t v; |
962 | 0 | v.set_real (env.argStack[start + j].to_real () + fold (param, blends)); |
963 | 0 | encoder.encode_num_tp (v); |
964 | 0 | } |
965 | 0 | } |
966 | 0 | else |
967 | 0 | { |
968 | 0 | unsigned done = 0; |
969 | 0 | while (done < n) |
970 | 0 | { |
971 | 0 | unsigned avail = 512 > (start + done) ? 512 - (start + done) : 0; |
972 | 0 | unsigned chunk = hb_min (avail / (k_new + 1), n - done); |
973 | 0 | if (unlikely (!chunk)) |
974 | 0 | { |
975 | 0 | env.set_error (); |
976 | 0 | return; |
977 | 0 | } |
978 | | |
979 | 0 | for (unsigned j = done; j < done + chunk; j++) |
980 | 0 | { |
981 | 0 | const hb_array_t<const number_t> blends = env.argStack.sub_array (start + n + (j * k), k); |
982 | 0 | number_t v; |
983 | 0 | v.set_real (env.argStack[start + j].to_real () + fold (param, blends)); |
984 | 0 | encoder.encode_num_tp (v); |
985 | 0 | } |
986 | 0 | for (unsigned j = done; j < done + chunk; j++) |
987 | 0 | { |
988 | 0 | const hb_array_t<const number_t> blends = env.argStack.sub_array (start + n + (j * k), k); |
989 | 0 | for (unsigned r = 0; r < k_new; r++) |
990 | 0 | { |
991 | 0 | number_t v; |
992 | 0 | v.set_int (hb_clamp_to<int> (round (t->transform_delta (r, blends)))); |
993 | 0 | encoder.encode_num_tp (v); |
994 | 0 | } |
995 | 0 | } |
996 | 0 | encoder.encode_int (chunk); |
997 | 0 | encoder.encode_op (OpCode_blenddict); |
998 | 0 | done += chunk; |
999 | 0 | } |
1000 | 0 | } |
1001 | | |
1002 | 0 | auto bytes = str.as_bytes (); |
1003 | 0 | param.c->embed (&bytes, bytes.length); |
1004 | |
|
1005 | 0 | env.clear_args (); |
1006 | 0 | } |
1007 | | |
1008 | | static void process_blend (cff2_priv_dict_interp_env_t& env, cff2_private_blend_encoder_param_t& param) |
1009 | 0 | { |
1010 | 0 | unsigned int n, k; |
1011 | |
|
1012 | 0 | param.process_blend (); |
1013 | 0 | k = param.region_count; |
1014 | 0 | n = env.argStack.pop_uint (); |
1015 | |
|
1016 | 0 | unsigned count = env.argStack.get_count (); |
1017 | 0 | unsigned int total; |
1018 | 0 | if (unlikely (hb_unsigned_mul_overflows (k + 1, n, &total) || total > count)) |
1019 | 0 | { |
1020 | 0 | env.set_error (); |
1021 | 0 | return; |
1022 | 0 | } |
1023 | | |
1024 | | /* copy the blend values into blend array of the default values */ |
1025 | 0 | unsigned int start = count - total; |
1026 | | /* let an obvious error case fail, but note CFF2 spec doesn't forbid n==0 */ |
1027 | 0 | if (unlikely (start > count)) |
1028 | 0 | { |
1029 | 0 | env.set_error (); |
1030 | 0 | return; |
1031 | 0 | } |
1032 | | |
1033 | 0 | if (param.instancer && k) |
1034 | 0 | { |
1035 | 0 | rewrite_blends (env, param, start, n, k); |
1036 | 0 | return; |
1037 | 0 | } |
1038 | | |
1039 | 0 | for (unsigned int i = 0; i < n; i++) |
1040 | 0 | { |
1041 | 0 | const hb_array_t<const number_t> blends = env.argStack.sub_array (start + n + (i * k), k); |
1042 | 0 | process_arg_blend (param, env.argStack[start + i], blends, n, i); |
1043 | 0 | } |
1044 | | |
1045 | | /* pop off blend values leaving default values now adorned with blend values */ |
1046 | 0 | env.argStack.pop (k * n); |
1047 | 0 | } |
1048 | | |
1049 | | static void process_op (op_code_t op, cff2_priv_dict_interp_env_t& env, cff2_private_blend_encoder_param_t& param) |
1050 | 0 | { |
1051 | 0 | switch (op) { |
1052 | 0 | case OpCode_StdHW: |
1053 | 0 | case OpCode_StdVW: |
1054 | 0 | case OpCode_BlueScale: |
1055 | 0 | case OpCode_BlueShift: |
1056 | 0 | case OpCode_BlueFuzz: |
1057 | 0 | case OpCode_ExpansionFactor: |
1058 | 0 | case OpCode_LanguageGroup: |
1059 | 0 | case OpCode_BlueValues: |
1060 | 0 | case OpCode_OtherBlues: |
1061 | 0 | case OpCode_FamilyBlues: |
1062 | 0 | case OpCode_FamilyOtherBlues: |
1063 | 0 | case OpCode_StemSnapH: |
1064 | 0 | case OpCode_StemSnapV: |
1065 | 0 | break; |
1066 | 0 | case OpCode_vsindexdict: |
1067 | 0 | env.process_vsindex (); |
1068 | 0 | param.ivs = env.get_ivs (); |
1069 | 0 | if (param.instancer) |
1070 | 0 | { |
1071 | | /* Re-emit with the remapped vsindex; 0 is the dict default. */ |
1072 | 0 | unsigned new_ivs = param.instancer->remap_ivs (param.ivs); |
1073 | 0 | if (new_ivs) |
1074 | 0 | { |
1075 | 0 | str_buff_t str; |
1076 | 0 | str_encoder_t encoder (str); |
1077 | 0 | encoder.encode_int ((int) new_ivs); |
1078 | 0 | encoder.encode_op (OpCode_vsindexdict); |
1079 | 0 | auto bytes = str.as_bytes (); |
1080 | 0 | param.c->embed (&bytes, bytes.length); |
1081 | 0 | } |
1082 | 0 | } |
1083 | 0 | env.clear_args (); |
1084 | 0 | return; |
1085 | 0 | case OpCode_blenddict: |
1086 | 0 | process_blend (env, param); |
1087 | 0 | return; |
1088 | | |
1089 | 0 | default: |
1090 | 0 | dict_opset_t::process_op (op, env); |
1091 | 0 | if (!env.argStack.is_empty ()) return; |
1092 | 0 | break; |
1093 | 0 | } |
1094 | | |
1095 | 0 | if (unlikely (env.in_error ())) return; |
1096 | | |
1097 | | // Write args then op |
1098 | | |
1099 | 0 | str_buff_t str; |
1100 | 0 | str_encoder_t encoder (str); |
1101 | |
|
1102 | 0 | unsigned count = env.argStack.get_count (); |
1103 | 0 | for (unsigned i = 0; i < count; i++) |
1104 | 0 | encoder.encode_num_tp (env.argStack[i]); |
1105 | |
|
1106 | 0 | encoder.encode_op (op); |
1107 | |
|
1108 | 0 | auto bytes = str.as_bytes (); |
1109 | 0 | param.c->embed (&bytes, bytes.length); |
1110 | |
|
1111 | 0 | env.clear_args (); |
1112 | 0 | param.fold_exact = param.fold_emitted = 0.; |
1113 | 0 | } |
1114 | | }; |
1115 | | |
1116 | | struct cff2_private_dict_op_serializer_t : op_serializer_t |
1117 | | { |
1118 | | cff2_private_dict_op_serializer_t (bool desubroutinize_, bool drop_hints_, bool pinned_, |
1119 | | const CFF::CFF2ItemVariationStore* varStore_, |
1120 | | hb_array_t<int> normalized_coords_, |
1121 | | const cff2_instancing_plan_t *instancer_ = nullptr) |
1122 | 0 | : desubroutinize (desubroutinize_), drop_hints (drop_hints_), pinned (pinned_), |
1123 | 0 | param (nullptr, varStore_, normalized_coords_) |
1124 | 0 | { param.instancer = instancer_; } |
1125 | | |
1126 | | bool serialize (hb_serialize_context_t *c, |
1127 | | const op_str_t &opstr, |
1128 | | objidx_t subrs_link) const |
1129 | 0 | { |
1130 | 0 | TRACE_SERIALIZE (this); |
1131 | |
|
1132 | 0 | if (drop_hints && dict_opset_t::is_hint_op (opstr.op)) |
1133 | 0 | return_trace (true); |
1134 | | |
1135 | 0 | if (opstr.op == OpCode_Subrs) |
1136 | 0 | { |
1137 | 0 | if (desubroutinize || !subrs_link) |
1138 | 0 | return_trace (true); |
1139 | 0 | else |
1140 | 0 | return_trace (FontDict::serialize_link2_op (c, opstr.op, subrs_link)); |
1141 | 0 | } |
1142 | | |
1143 | 0 | if (pinned || param.instancer) |
1144 | 0 | { |
1145 | | /* Reinterpret opstr and process blends. The param persists across |
1146 | | * this dict's opstrs, so that an ivs set by a vsindexdict opstr is |
1147 | | * still in effect when a later opstr's blends are processed. */ |
1148 | 0 | cff2_priv_dict_interp_env_t env {hb_ubytes_t (opstr.ptr, opstr.length)}; |
1149 | 0 | param.c = c; |
1150 | 0 | dict_interpreter_t<cff2_private_dict_blend_opset_t, cff2_private_blend_encoder_param_t, cff2_priv_dict_interp_env_t> interp (env); |
1151 | 0 | return_trace (interp.interpret (param)); |
1152 | 0 | } |
1153 | | |
1154 | 0 | return_trace (copy_opstr (c, opstr)); |
1155 | 0 | } |
1156 | | |
1157 | | protected: |
1158 | | const bool desubroutinize; |
1159 | | const bool drop_hints; |
1160 | | const bool pinned; |
1161 | | mutable cff2_private_blend_encoder_param_t param; |
1162 | | }; |
1163 | | |
1164 | | |
1165 | | |
1166 | | namespace OT { |
1167 | | struct cff2_subset_plan |
1168 | | { |
1169 | | bool create (const OT::cff2::accelerator_subset_t &acc, |
1170 | | hb_subset_plan_t *plan) |
1171 | 0 | { |
1172 | | /* make sure notdef is first */ |
1173 | 0 | hb_codepoint_t old_glyph; |
1174 | 0 | if (!plan->old_gid_for_new_gid (0, &old_glyph) || (old_glyph != 0)) return false; |
1175 | | |
1176 | 0 | num_glyphs = plan->num_output_glyphs (); |
1177 | 0 | orig_fdcount = acc.fdArray->count; |
1178 | |
|
1179 | 0 | drop_hints = plan->flags & HB_SUBSET_FLAGS_NO_HINTING; |
1180 | 0 | bool instancing_requested = (bool) plan->normalized_coords; |
1181 | 0 | pinned = instancing_requested && plan->all_axes_pinned; |
1182 | 0 | normalized_coords = plan->normalized_coords; |
1183 | 0 | head_maxp_info = plan->head_maxp_info; |
1184 | 0 | hmtx_map = &plan->hmtx_map; |
1185 | 0 | desubroutinize = plan->flags & HB_SUBSET_FLAGS_DESUBROUTINIZE || |
1186 | 0 | instancing_requested; // For instancing we need this path |
1187 | | |
1188 | | /* Partial instancing: some axes pinned or limited, others kept. */ |
1189 | 0 | partial_instancing = instancing_requested && !pinned && |
1190 | 0 | acc.varStore != &Null (CFF2ItemVariationStore); |
1191 | 0 | if (partial_instancing && |
1192 | 0 | unlikely (!instancing.create (acc.varStore, plan))) |
1193 | 0 | return false; |
1194 | 0 | axis_tags = &plan->axis_tags; |
1195 | | |
1196 | | /* Enable command capture for CFF2→CFF1 conversion (for specialization) */ |
1197 | 0 | capture_commands = pinned; |
1198 | |
|
1199 | | #ifdef HB_EXPERIMENTAL_API |
1200 | | min_charstrings_off_size = (plan->flags & HB_SUBSET_FLAGS_IFTB_REQUIREMENTS) ? 4 : 0; |
1201 | | #else |
1202 | 0 | min_charstrings_off_size = 0; |
1203 | 0 | #endif |
1204 | |
|
1205 | 0 | if (desubroutinize) |
1206 | 0 | { |
1207 | 0 | if (instancing_requested) |
1208 | 0 | { |
1209 | | /* Identify charstrings that can round absolute coordinates. Conservatively |
1210 | | * keep relative rounding for flex1, whose implicit direction can vary. |
1211 | | * Also collect the VarDatas referenced by partial instances. */ |
1212 | 0 | hb_vector_t<bool> round_charstrings; |
1213 | 0 | if (unlikely (!round_charstrings.resize (num_glyphs))) return false; |
1214 | 0 | hb_set_t used_fds; |
1215 | 0 | for (unsigned int i = 0; i < num_glyphs; i++) |
1216 | 0 | { |
1217 | 0 | hb_codepoint_t glyph; |
1218 | 0 | if (!plan->old_gid_for_new_gid (i, &glyph)) |
1219 | 0 | continue; |
1220 | 0 | const hb_ubytes_t str = (*acc.charStrings)[glyph]; |
1221 | 0 | unsigned int fd = CFF::subset_get_fd (acc, glyph); |
1222 | 0 | if (unlikely (fd >= acc.fdCount)) |
1223 | 0 | return false; |
1224 | 0 | used_fds.add (fd); |
1225 | |
|
1226 | 0 | cff2_cs_interp_env_t<blend_arg_t> env (str, acc, fd); |
1227 | 0 | cs_interpreter_t<cff2_cs_interp_env_t<blend_arg_t>, cff2_cs_opset_usage_t, cff2_usage_param_t> interp (env); |
1228 | 0 | cff2_usage_param_t param; |
1229 | 0 | param.instancing = partial_instancing ? &instancing : nullptr; |
1230 | 0 | param.drop_hints = drop_hints; |
1231 | 0 | if (unlikely (!interp.interpret (param))) |
1232 | 0 | return false; |
1233 | 0 | round_charstrings.arrayZ[i] = param.has_blend && !param.has_flex1; |
1234 | 0 | } |
1235 | 0 | if (partial_instancing) |
1236 | 0 | { |
1237 | 0 | for (unsigned fd : used_fds) |
1238 | 0 | { |
1239 | 0 | cff2_usage_param_t param; |
1240 | 0 | param.instancing = &instancing; |
1241 | 0 | unsigned count = acc.privateDicts[fd].get_count (); |
1242 | 0 | for (unsigned j = 0; j < count; j++) |
1243 | 0 | { |
1244 | 0 | const op_str_t &opstr = acc.privateDicts[fd][j]; |
1245 | 0 | if (drop_hints && dict_opset_t::is_hint_op (opstr.op)) |
1246 | 0 | continue; |
1247 | 0 | cff2_priv_dict_interp_env_t env {hb_ubytes_t (opstr.ptr, opstr.length)}; |
1248 | 0 | dict_interpreter_t<cff2_private_dict_usage_opset_t, cff2_usage_param_t, cff2_priv_dict_interp_env_t> interp (env); |
1249 | 0 | if (unlikely (!interp.interpret (param))) |
1250 | 0 | return false; |
1251 | 0 | } |
1252 | 0 | } |
1253 | 0 | if (unlikely (!instancing.finalize ())) |
1254 | 0 | return false; |
1255 | 0 | } |
1256 | | |
1257 | | /* Keep full-instance blends unrounded until their absolute positions |
1258 | | * are known; partial-instance blends stay symbolic for rewriting. */ |
1259 | 0 | if (!subset_charstrings.resize_exact (num_glyphs)) |
1260 | 0 | return false; |
1261 | 0 | if (capture_commands && !charstring_commands.resize_exact (num_glyphs)) |
1262 | 0 | return false; |
1263 | 0 | for (unsigned int i = 0; i < num_glyphs; i++) |
1264 | 0 | { |
1265 | 0 | hb_codepoint_t glyph; |
1266 | 0 | if (!plan->old_gid_for_new_gid (i, &glyph)) |
1267 | 0 | continue; |
1268 | 0 | const hb_ubytes_t str = (*acc.charStrings)[glyph]; |
1269 | 0 | unsigned int fd = CFF::subset_get_fd (acc, glyph); |
1270 | 0 | if (unlikely (fd >= acc.fdCount)) |
1271 | 0 | return false; |
1272 | | |
1273 | 0 | cff2_cs_interp_env_t<blend_arg_t> env (str, acc, fd, |
1274 | 0 | pinned ? plan->normalized_coords.arrayZ : nullptr, |
1275 | 0 | pinned ? plan->normalized_coords.length : 0); |
1276 | 0 | env.round_blends = !round_charstrings.arrayZ[i]; |
1277 | 0 | cs_interpreter_t<cff2_cs_interp_env_t<blend_arg_t>, cff2_cs_opset_flatten_t, flatten_param_t> interp (env); |
1278 | 0 | flatten_param_t param = { |
1279 | 0 | subset_charstrings.arrayZ[i], |
1280 | 0 | (bool) (plan->flags & HB_SUBSET_FLAGS_NO_HINTING), |
1281 | 0 | plan, |
1282 | 0 | capture_commands ? &charstring_commands.arrayZ[i] : nullptr |
1283 | 0 | }; |
1284 | 0 | cff2_cs_rounding_t rounding; |
1285 | 0 | if (round_charstrings.arrayZ[i]) param.rounding = &rounding; |
1286 | 0 | param.instancer = partial_instancing ? &instancing : nullptr; |
1287 | 0 | if (unlikely (!interp.interpret (param))) |
1288 | 0 | return false; |
1289 | 0 | } |
1290 | 0 | } |
1291 | 0 | else |
1292 | 0 | { |
1293 | | /* Flatten global & local subrs */ |
1294 | 0 | subr_flattener_t<const OT::cff2::accelerator_subset_t, cff2_cs_interp_env_t<blend_arg_t>, cff2_cs_opset_flatten_t> |
1295 | 0 | flattener(acc, plan); |
1296 | | |
1297 | | /* Enable command capture if requested (for specialization) */ |
1298 | 0 | if (capture_commands) |
1299 | 0 | { |
1300 | 0 | if (!charstring_commands.resize_exact (num_glyphs)) |
1301 | 0 | return false; |
1302 | | |
1303 | 0 | if (!flattener.flatten (subset_charstrings, &charstring_commands)) |
1304 | 0 | return false; |
1305 | 0 | } |
1306 | 0 | else |
1307 | 0 | { |
1308 | 0 | if (!flattener.flatten (subset_charstrings)) |
1309 | 0 | return false; |
1310 | 0 | } |
1311 | 0 | } |
1312 | 0 | } |
1313 | 0 | else |
1314 | 0 | { |
1315 | 0 | cff2_subr_subsetter_t subr_subsetter (acc, plan); |
1316 | | |
1317 | | /* Subset subrs: collect used subroutines, leaving all unused ones behind */ |
1318 | 0 | if (!subr_subsetter.subset ()) |
1319 | 0 | return false; |
1320 | | |
1321 | | /* encode charstrings, global subrs, local subrs with new subroutine numbers */ |
1322 | 0 | if (!subr_subsetter.encode_charstrings (subset_charstrings, !pinned)) |
1323 | 0 | return false; |
1324 | | |
1325 | 0 | if (!subr_subsetter.encode_globalsubrs (subset_globalsubrs)) |
1326 | 0 | return false; |
1327 | | |
1328 | | /* local subrs */ |
1329 | 0 | if (!subset_localsubrs.resize (orig_fdcount)) |
1330 | 0 | return false; |
1331 | 0 | for (unsigned int fd = 0; fd < orig_fdcount; fd++) |
1332 | 0 | { |
1333 | 0 | subset_localsubrs[fd].init (); |
1334 | 0 | if (!subr_subsetter.encode_localsubrs (fd, subset_localsubrs[fd])) |
1335 | 0 | return false; |
1336 | 0 | } |
1337 | 0 | } |
1338 | | |
1339 | | /* FDSelect */ |
1340 | 0 | if (acc.fdSelect != &Null (CFF2FDSelect)) |
1341 | 0 | { |
1342 | 0 | if (unlikely (!hb_plan_subset_cff_fdselect (plan, |
1343 | 0 | orig_fdcount, |
1344 | 0 | *(const FDSelect *)acc.fdSelect, |
1345 | 0 | acc.num_glyphs, |
1346 | 0 | subset_fdcount, |
1347 | 0 | subset_fdselect_size, |
1348 | 0 | subset_fdselect_format, |
1349 | 0 | subset_fdselect_ranges, |
1350 | 0 | fdmap))) |
1351 | 0 | return false; |
1352 | 0 | } |
1353 | 0 | else |
1354 | 0 | fdmap.identity (1); |
1355 | | |
1356 | 0 | return true; |
1357 | 0 | } |
1358 | | |
1359 | | cff2_sub_table_info_t info; |
1360 | | |
1361 | | unsigned int num_glyphs; |
1362 | | unsigned int orig_fdcount = 0; |
1363 | | unsigned int subset_fdcount = 1; |
1364 | | unsigned int subset_fdselect_size = 0; |
1365 | | unsigned int subset_fdselect_format = 0; |
1366 | | bool pinned = false; |
1367 | | hb_vector_t<code_pair_t> subset_fdselect_ranges; |
1368 | | |
1369 | | hb_inc_bimap_t fdmap; |
1370 | | |
1371 | | str_buff_vec_t subset_charstrings; |
1372 | | str_buff_vec_t subset_globalsubrs; |
1373 | | hb_vector_t<str_buff_vec_t> subset_localsubrs; |
1374 | | |
1375 | | bool drop_hints = false; |
1376 | | bool desubroutinize = false; |
1377 | | |
1378 | | bool partial_instancing = false; |
1379 | | cff2_instancing_plan_t instancing; |
1380 | | const hb_vector_t<hb_tag_t> *axis_tags = nullptr; |
1381 | | |
1382 | | unsigned min_charstrings_off_size = 0; |
1383 | | |
1384 | | hb_array_t<int> normalized_coords; // For instantiation |
1385 | | head_maxp_info_t head_maxp_info; // For FontBBox |
1386 | | const hb_hashmap_t<hb_codepoint_t, hb_pair_t<unsigned, int>> *hmtx_map; // For widths |
1387 | | |
1388 | | // Width optimization results (for CFF1 conversion) |
1389 | | unsigned default_width = 0; |
1390 | | unsigned nominal_width = 0; |
1391 | | |
1392 | | // Command capture for specialization (CFF2→CFF1 conversion) |
1393 | | bool capture_commands = false; |
1394 | | hb_vector_t<hb_vector_t<cs_command_t>> charstring_commands; |
1395 | | }; |
1396 | | } // namespace OT |
1397 | | |
1398 | | /* |
1399 | | * CFF2 to CFF1 Converter Implementation |
1400 | | */ |
1401 | | |
1402 | | #include "hb-cff-width-optimizer.hh" |
1403 | | #include "hb-cff-specializer.hh" |
1404 | | |
1405 | | /* Serialize charstrings using CFF1 format with widths */ |
1406 | | static bool |
1407 | | _serialize_cff1_charstrings (hb_serialize_context_t *c, |
1408 | | OT::cff2_subset_plan &plan, |
1409 | | unsigned default_width, |
1410 | | unsigned nominal_width) |
1411 | 0 | { |
1412 | 0 | c->push (); |
1413 | | |
1414 | | // CFF1 requires: |
1415 | | // 1. Width at the beginning (if != defaultWidthX) |
1416 | | // 2. endchar at the end |
1417 | 0 | str_buff_vec_t cff1_charstrings; |
1418 | 0 | if (unlikely (!cff1_charstrings.resize (plan.subset_charstrings.length))) |
1419 | 0 | { |
1420 | 0 | c->pop_discard (); |
1421 | 0 | return false; |
1422 | 0 | } |
1423 | | |
1424 | 0 | for (unsigned i = 0; i < plan.subset_charstrings.length; i++) |
1425 | 0 | { |
1426 | | // Get width for this glyph from hmtx_map |
1427 | 0 | unsigned width = 0; |
1428 | 0 | if (plan.hmtx_map->has (i)) |
1429 | 0 | width = plan.hmtx_map->get (i).first; |
1430 | | |
1431 | | // Encode width if different from default |
1432 | 0 | str_encoder_t encoder (cff1_charstrings[i]); |
1433 | 0 | if (width != default_width) |
1434 | 0 | { |
1435 | 0 | int delta = (int) width - (int) nominal_width; |
1436 | 0 | encoder.encode_int (delta); |
1437 | 0 | } |
1438 | | |
1439 | | // Use specialized commands if available, otherwise use binary |
1440 | 0 | if (plan.capture_commands && i < plan.charstring_commands.length && |
1441 | 0 | plan.charstring_commands[i].length > 0) |
1442 | 0 | { |
1443 | | // Specialize and encode commands |
1444 | 0 | auto &commands = plan.charstring_commands[i]; |
1445 | 0 | CFF::specialize_commands (commands, 48); /* maxstack=48 for CFF1 */ |
1446 | 0 | if (unlikely (!CFF::encode_commands (commands, cff1_charstrings[i]))) |
1447 | 0 | { |
1448 | 0 | c->pop_discard (); |
1449 | 0 | return false; |
1450 | 0 | } |
1451 | 0 | } |
1452 | 0 | else |
1453 | 0 | { |
1454 | | // Use binary CharString |
1455 | 0 | const str_buff_t &cs = plan.subset_charstrings[i]; |
1456 | 0 | for (unsigned j = 0; j < cs.length; j++) |
1457 | 0 | cff1_charstrings[i].push (cs[j]); |
1458 | 0 | } |
1459 | | |
1460 | | // Check if it already ends with endchar (0x0e) or return (0x0b) |
1461 | 0 | if (cff1_charstrings[i].length == 0 || |
1462 | 0 | (cff1_charstrings[i].tail () != 0x0e && cff1_charstrings[i].tail () != 0x0b)) |
1463 | 0 | { |
1464 | | // Append endchar operator |
1465 | 0 | if (unlikely (!cff1_charstrings[i].push_or_fail (0x0e))) |
1466 | 0 | { |
1467 | 0 | c->pop_discard (); |
1468 | 0 | return false; |
1469 | 0 | } |
1470 | 0 | } |
1471 | 0 | } |
1472 | | |
1473 | 0 | unsigned data_size = 0; |
1474 | 0 | unsigned total_size = CFF1CharStrings::total_size (cff1_charstrings, &data_size, plan.min_charstrings_off_size); |
1475 | 0 | if (unlikely (!c->start_zerocopy (total_size))) |
1476 | 0 | { |
1477 | 0 | c->pop_discard (); |
1478 | 0 | return false; |
1479 | 0 | } |
1480 | | |
1481 | 0 | auto *cs = c->start_embed<CFF1CharStrings> (); |
1482 | 0 | if (unlikely (!cs->serialize (c, cff1_charstrings, &data_size, plan.min_charstrings_off_size))) |
1483 | 0 | { |
1484 | 0 | c->pop_discard (); |
1485 | 0 | return false; |
1486 | 0 | } |
1487 | | |
1488 | 0 | plan.info.char_strings_link = c->pop_pack (false); |
1489 | 0 | return true; |
1490 | 0 | } |
1491 | | |
1492 | | /* Serialize CID Charset (format 2 range: gid 0-N -> cid 0-N) */ |
1493 | | static bool |
1494 | | _serialize_cff1_charset (hb_serialize_context_t *c, |
1495 | | unsigned int num_glyphs, |
1496 | | objidx_t &charset_link) |
1497 | 0 | { |
1498 | | // For CID fonts, create a simple identity charset |
1499 | | // Format 2: one range covering all glyphs (except .notdef) |
1500 | 0 | c->push (); |
1501 | |
|
1502 | 0 | auto *charset = c->start_embed<Charset> (); |
1503 | 0 | if (unlikely (!charset)) |
1504 | 0 | { |
1505 | 0 | c->pop_discard (); |
1506 | 0 | return false; |
1507 | 0 | } |
1508 | | |
1509 | | // Create a single range for CID 1 to num_glyphs-1 |
1510 | 0 | hb_vector_t<code_pair_t> ranges; |
1511 | 0 | if (num_glyphs > 1) |
1512 | 0 | { |
1513 | 0 | code_pair_t range; |
1514 | 0 | range.code = 1; // first CID |
1515 | 0 | range.glyph = num_glyphs - 2; // nLeft (covers glyphs 1 to num_glyphs-1) |
1516 | 0 | ranges.push (range); |
1517 | 0 | } |
1518 | |
|
1519 | 0 | if (unlikely (!charset->serialize (c, 2, num_glyphs, ranges))) |
1520 | 0 | { |
1521 | 0 | c->pop_discard (); |
1522 | 0 | return false; |
1523 | 0 | } |
1524 | | |
1525 | 0 | charset_link = c->pop_pack (); |
1526 | 0 | return true; |
1527 | 0 | } |
1528 | | |
1529 | | /* CFF2 to CFF1 serialization */ |
1530 | | namespace CFF { |
1531 | | |
1532 | | bool |
1533 | | serialize_cff2_to_cff1 (hb_serialize_context_t *c, |
1534 | | OT::cff2_subset_plan &plan, |
1535 | | const cff2_top_dict_values_t &cff2_topDict, |
1536 | | const OT::cff2::accelerator_subset_t &acc) |
1537 | 0 | { |
1538 | 0 | TRACE_SERIALIZE (this); |
1539 | | |
1540 | | /* |
1541 | | * CFF1 Serialization Order (reverse, as HarfBuzz packs from end): |
1542 | | * 1. CharStrings |
1543 | | * 2. Private DICs & Local Subrs |
1544 | | * 3. FDArray |
1545 | | * 4. FDSelect |
1546 | | * 5. Charset |
1547 | | * 6. Global Subrs |
1548 | | * 7. String INDEX |
1549 | | * 8. Top DICT INDEX |
1550 | | * 9. Name INDEX |
1551 | | * 10. Header |
1552 | | */ |
1553 | | |
1554 | | // 0. Optimize width encoding (for all FDs) |
1555 | 0 | { |
1556 | | // Collect widths from hmtx_map |
1557 | 0 | hb_vector_t<unsigned> widths; |
1558 | 0 | widths.alloc (plan.num_glyphs); |
1559 | |
|
1560 | 0 | for (unsigned gid = 0; gid < plan.num_glyphs; gid++) |
1561 | 0 | { |
1562 | 0 | unsigned width = 0; |
1563 | 0 | if (plan.hmtx_map->has (gid)) |
1564 | 0 | width = plan.hmtx_map->get (gid).first; |
1565 | 0 | widths.push (width); |
1566 | 0 | } |
1567 | | |
1568 | | // Optimize defaultWidthX and nominalWidthX |
1569 | 0 | CFF::optimize_widths (widths, plan.default_width, plan.nominal_width); |
1570 | 0 | } |
1571 | | |
1572 | | // 1. CharStrings (with widths prepended) |
1573 | 0 | if (!_serialize_cff1_charstrings (c, plan, plan.default_width, plan.nominal_width)) |
1574 | 0 | return_trace (false); |
1575 | | |
1576 | | // 2. Private DICs & Local Subrs (same as CFF2) |
1577 | 0 | hb_vector_t<table_info_t> private_dict_infos; |
1578 | 0 | if (unlikely (!private_dict_infos.resize (plan.subset_fdcount))) |
1579 | 0 | return_trace (false); |
1580 | | |
1581 | 0 | for (int i = (int)acc.privateDicts.length; --i >= 0;) |
1582 | 0 | { |
1583 | 0 | if (plan.fdmap.has (i)) |
1584 | 0 | { |
1585 | 0 | objidx_t subrs_link = 0; |
1586 | |
|
1587 | 0 | if (plan.subset_localsubrs[i].length > 0) |
1588 | 0 | { |
1589 | 0 | auto *dest = c->push<CFF1Subrs> (); |
1590 | 0 | if (likely (dest->serialize (c, plan.subset_localsubrs[i]))) |
1591 | 0 | subrs_link = c->pop_pack (false); |
1592 | 0 | else |
1593 | 0 | { |
1594 | 0 | c->pop_discard (); |
1595 | 0 | return_trace (false); |
1596 | 0 | } |
1597 | 0 | } |
1598 | | |
1599 | 0 | auto *pd = c->push<PrivateDict> (); |
1600 | | // Use the CFF2 Private DICT serializer which instantiates blends when pinned=true |
1601 | 0 | cff2_private_dict_op_serializer_t privSzr (plan.desubroutinize, plan.drop_hints, plan.pinned, |
1602 | 0 | acc.varStore, plan.normalized_coords); |
1603 | 0 | if (likely (pd->serialize (c, acc.privateDicts[i], privSzr, subrs_link))) |
1604 | 0 | { |
1605 | | // Add defaultWidthX and nominalWidthX for CFF1 |
1606 | 0 | str_buff_t width_ops; |
1607 | 0 | str_encoder_t encoder (width_ops); |
1608 | 0 | encoder.encode_int (plan.default_width); |
1609 | 0 | encoder.encode_op (OpCode_defaultWidthX); |
1610 | 0 | encoder.encode_int (plan.nominal_width); |
1611 | 0 | encoder.encode_op (OpCode_nominalWidthX); |
1612 | |
|
1613 | 0 | if (!encoder.in_error () && c->embed (width_ops.as_bytes ().arrayZ, width_ops.length)) |
1614 | 0 | { |
1615 | 0 | unsigned fd = plan.fdmap[i]; |
1616 | 0 | private_dict_infos[fd].size = c->length (); |
1617 | 0 | private_dict_infos[fd].link = c->pop_pack (); |
1618 | 0 | } |
1619 | 0 | else |
1620 | 0 | { |
1621 | 0 | c->pop_discard (); |
1622 | 0 | return_trace (false); |
1623 | 0 | } |
1624 | 0 | } |
1625 | 0 | else |
1626 | 0 | { |
1627 | 0 | c->pop_discard (); |
1628 | 0 | return_trace (false); |
1629 | 0 | } |
1630 | 0 | } |
1631 | 0 | } |
1632 | | |
1633 | | // 3. FDArray - serialize CFF2 font dicts as CFF1 |
1634 | 0 | { |
1635 | 0 | auto *fda = c->push<FDArray<HBUINT16>> (); |
1636 | 0 | cff_font_dict_op_serializer_t fontSzr; |
1637 | 0 | auto it = |
1638 | 0 | + hb_zip (+ hb_iter (acc.fontDicts) |
1639 | 0 | | hb_filter ([&] (const cff2_font_dict_values_t &_) |
1640 | 0 | { return plan.fdmap.has (&_ - &acc.fontDicts[0]); }), |
1641 | 0 | hb_iter (private_dict_infos)) |
1642 | 0 | ; |
1643 | | // Explicitly specify template parameters: DICTVAL, INFO |
1644 | 0 | bool success = fda->serialize<cff2_font_dict_values_t, table_info_t> (c, it, fontSzr); |
1645 | 0 | if (success) |
1646 | 0 | plan.info.fd_array_link = c->pop_pack (false); |
1647 | 0 | else |
1648 | 0 | { |
1649 | 0 | c->pop_discard (); |
1650 | 0 | return_trace (false); |
1651 | 0 | } |
1652 | 0 | } |
1653 | | |
1654 | | // 4. FDSelect (required in CFF1 CID-keyed fonts) |
1655 | | // CFF1 requires FDSelect for all CID-keyed fonts, even with just one FD |
1656 | | // CFF2 makes it optional when there's only one FD |
1657 | 0 | if (acc.fdSelect != &Null (CFF2FDSelect)) |
1658 | 0 | { |
1659 | 0 | c->push (); |
1660 | 0 | if (likely (hb_serialize_cff_fdselect (c, plan.num_glyphs, |
1661 | 0 | *(const FDSelect *)acc.fdSelect, |
1662 | 0 | plan.orig_fdcount, |
1663 | 0 | plan.subset_fdselect_format, |
1664 | 0 | plan.subset_fdselect_size, |
1665 | 0 | plan.subset_fdselect_ranges))) |
1666 | 0 | plan.info.fd_select.link = c->pop_pack (); |
1667 | 0 | else |
1668 | 0 | { |
1669 | 0 | c->pop_discard (); |
1670 | 0 | return_trace (false); |
1671 | 0 | } |
1672 | 0 | } |
1673 | 0 | else |
1674 | 0 | { |
1675 | | // Create a range-based FDSelect3 mapping all glyphs to FD 0 |
1676 | | // Format: format(1) + nRanges(2) + range(3) + sentinel(2) = 8 bytes |
1677 | 0 | c->push (); |
1678 | | |
1679 | | // Format byte |
1680 | 0 | HBUINT8 format; |
1681 | 0 | format = 3; |
1682 | 0 | if (unlikely (!c->embed (format))) |
1683 | 0 | { |
1684 | 0 | c->pop_discard (); |
1685 | 0 | return_trace (false); |
1686 | 0 | } |
1687 | | |
1688 | | // nRanges |
1689 | 0 | HBUINT16 nRanges; |
1690 | 0 | nRanges = 1; |
1691 | 0 | if (unlikely (!c->embed (nRanges))) |
1692 | 0 | { |
1693 | 0 | c->pop_discard (); |
1694 | 0 | return_trace (false); |
1695 | 0 | } |
1696 | | |
1697 | | // Single range: {first: 0, fd: 0} |
1698 | 0 | FDSelect3_Range range; |
1699 | 0 | range.first = 0; |
1700 | 0 | range.fd = 0; |
1701 | 0 | if (unlikely (!c->embed (range))) |
1702 | 0 | { |
1703 | 0 | c->pop_discard (); |
1704 | 0 | return_trace (false); |
1705 | 0 | } |
1706 | | |
1707 | | // Sentinel (number of glyphs) |
1708 | 0 | HBUINT16 sentinel; |
1709 | 0 | sentinel = plan.num_glyphs; |
1710 | 0 | if (unlikely (!c->embed (sentinel))) |
1711 | 0 | { |
1712 | 0 | c->pop_discard (); |
1713 | 0 | return_trace (false); |
1714 | 0 | } |
1715 | | |
1716 | 0 | plan.info.fd_select.link = c->pop_pack (); |
1717 | 0 | } |
1718 | | |
1719 | | // 5. Charset (CID charset for identity mapping) |
1720 | 0 | objidx_t charset_link; |
1721 | 0 | if (!_serialize_cff1_charset (c, plan.num_glyphs, charset_link)) |
1722 | 0 | return_trace (false); |
1723 | | |
1724 | | // 6. Global Subrs |
1725 | 0 | { |
1726 | 0 | auto *dest = c->push<CFF1Subrs> (); |
1727 | 0 | if (likely (dest->serialize (c, plan.subset_globalsubrs))) |
1728 | 0 | c->pop_pack (false); |
1729 | 0 | else |
1730 | 0 | { |
1731 | 0 | c->pop_discard (); |
1732 | 0 | return_trace (false); |
1733 | 0 | } |
1734 | 0 | } |
1735 | | |
1736 | | // 7. String INDEX - Add "Adobe" and "Identity" for ROS operator |
1737 | 0 | { |
1738 | 0 | const char *adobe_str = "Adobe"; |
1739 | 0 | const char *identity_str = "Identity"; |
1740 | 0 | unsigned adobe_len = 5; // strlen("Adobe") |
1741 | 0 | unsigned identity_len = 8; // strlen("Identity") |
1742 | | |
1743 | | // Build strings array |
1744 | 0 | hb_vector_t<hb_ubytes_t> strings; |
1745 | 0 | strings.alloc (2); |
1746 | 0 | strings.push (hb_ubytes_t ((const unsigned char *) adobe_str, adobe_len)); |
1747 | 0 | strings.push (hb_ubytes_t ((const unsigned char *) identity_str, identity_len)); |
1748 | | |
1749 | | // Serialize as CFF INDEX |
1750 | 0 | auto *dest = c->push<CFF1Index> (); |
1751 | 0 | if (likely (dest->serialize (c, strings))) |
1752 | 0 | c->pop_pack (false); |
1753 | 0 | else |
1754 | 0 | { |
1755 | 0 | c->pop_discard (); |
1756 | 0 | return_trace (false); |
1757 | 0 | } |
1758 | 0 | } |
1759 | | |
1760 | | // 8. CFF Header |
1761 | 0 | OT::cff1 *cff = c->allocate_min<OT::cff1> (); |
1762 | 0 | if (unlikely (!cff)) return_trace (false); |
1763 | | |
1764 | | /* header */ |
1765 | 0 | cff->version.major = 0x01; |
1766 | 0 | cff->version.minor = 0x00; |
1767 | 0 | cff->nameIndex = cff->min_size; |
1768 | 0 | cff->offSize = 4; /* unused? */ |
1769 | | |
1770 | | // 9. Name INDEX (single entry) |
1771 | 0 | { |
1772 | 0 | unsigned name_len = strlen (CFF1_DEFAULT_FONT_NAME); |
1773 | |
|
1774 | 0 | CFF1Index *idx = c->start_embed<CFF1Index> (); |
1775 | 0 | if (unlikely (!idx)) return_trace (false); |
1776 | | |
1777 | 0 | if (unlikely (!idx->serialize_header (c, hb_iter (&name_len, 1), name_len))) |
1778 | 0 | return_trace (false); |
1779 | | |
1780 | 0 | if (unlikely (!c->embed (CFF1_DEFAULT_FONT_NAME, name_len))) |
1781 | 0 | return_trace (false); |
1782 | 0 | } |
1783 | | |
1784 | | // 10. Top DICT INDEX |
1785 | 0 | { |
1786 | | // Serialize the Top DICT data first |
1787 | 0 | c->push<TopDict> (); |
1788 | 0 | cff1_from_cff2_top_dict_op_serializer_t topSzr; |
1789 | | |
1790 | | // Serialize ROS first |
1791 | 0 | if (unlikely (!topSzr.serialize_ros (c))) |
1792 | 0 | { |
1793 | 0 | c->pop_discard (); |
1794 | 0 | return_trace (false); |
1795 | 0 | } |
1796 | | |
1797 | | // Serialize FontBBox from head table |
1798 | 0 | { |
1799 | 0 | str_buff_t bbox_buff; |
1800 | 0 | str_encoder_t encoder (bbox_buff); |
1801 | |
|
1802 | 0 | encoder.encode_int (plan.head_maxp_info.xMin); |
1803 | 0 | encoder.encode_int (plan.head_maxp_info.yMin); |
1804 | 0 | encoder.encode_int (plan.head_maxp_info.xMax); |
1805 | 0 | encoder.encode_int (plan.head_maxp_info.yMax); |
1806 | 0 | encoder.encode_op (OpCode_FontBBox); |
1807 | |
|
1808 | 0 | if (encoder.in_error () || !c->embed (bbox_buff.as_bytes ().arrayZ, bbox_buff.length)) |
1809 | 0 | { |
1810 | 0 | c->pop_discard (); |
1811 | 0 | return_trace (false); |
1812 | 0 | } |
1813 | 0 | } |
1814 | | |
1815 | | // Serialize charset operator |
1816 | 0 | if (charset_link && unlikely (!FontDict::serialize_link4_op (c, OpCode_charset, charset_link, whence_t::Absolute))) |
1817 | 0 | { |
1818 | 0 | c->pop_discard (); |
1819 | 0 | return_trace (false); |
1820 | 0 | } |
1821 | | |
1822 | | // Serialize FDSelect operator (required for CID-keyed CFF1 fonts) |
1823 | 0 | if (plan.info.fd_select.link && unlikely (!FontDict::serialize_link4_op (c, OpCode_FDSelect, plan.info.fd_select.link, whence_t::Absolute))) |
1824 | 0 | { |
1825 | 0 | c->pop_discard (); |
1826 | 0 | return_trace (false); |
1827 | 0 | } |
1828 | | |
1829 | | // Serialize FDArray operator (required for CID-keyed CFF1 fonts) |
1830 | 0 | if (plan.info.fd_array_link && unlikely (!FontDict::serialize_link4_op (c, OpCode_FDArray, plan.info.fd_array_link, whence_t::Absolute))) |
1831 | 0 | { |
1832 | 0 | c->pop_discard (); |
1833 | 0 | return_trace (false); |
1834 | 0 | } |
1835 | | |
1836 | | // Serialize other operators from CFF2 TopDict |
1837 | 0 | for (const auto &opstr : cff2_topDict.values) |
1838 | 0 | { |
1839 | 0 | if (unlikely (!topSzr.serialize (c, opstr, plan.info))) |
1840 | 0 | { |
1841 | 0 | c->pop_discard (); |
1842 | 0 | return_trace (false); |
1843 | 0 | } |
1844 | 0 | } |
1845 | | |
1846 | 0 | unsigned top_size = c->length (); |
1847 | 0 | c->pop_pack (false); |
1848 | | |
1849 | | // Serialize INDEX header |
1850 | 0 | auto *dest = c->start_embed<CFF1Index> (); |
1851 | 0 | if (unlikely (!dest->serialize_header (c, hb_iter (&top_size, 1), top_size))) |
1852 | 0 | return_trace (false); |
1853 | 0 | } |
1854 | | |
1855 | 0 | return_trace (true); |
1856 | 0 | } |
1857 | | |
1858 | | } /* namespace CFF */ |
1859 | | |
1860 | | static bool _serialize_cff2_charstrings (hb_serialize_context_t *c, |
1861 | | cff2_subset_plan &plan, |
1862 | | const OT::cff2::accelerator_subset_t &acc) |
1863 | 0 | { |
1864 | 0 | c->push (); |
1865 | |
|
1866 | 0 | unsigned data_size = 0; |
1867 | 0 | unsigned total_size = CFF2CharStrings::total_size (plan.subset_charstrings, &data_size, plan.min_charstrings_off_size); |
1868 | 0 | if (unlikely (!c->start_zerocopy (total_size))) |
1869 | 0 | return false; |
1870 | | |
1871 | 0 | auto *cs = c->start_embed<CFF2CharStrings> (); |
1872 | 0 | if (unlikely (!cs->serialize (c, plan.subset_charstrings, &data_size, plan.min_charstrings_off_size))) |
1873 | 0 | { |
1874 | 0 | c->pop_discard (); |
1875 | 0 | return false; |
1876 | 0 | } |
1877 | | |
1878 | 0 | plan.info.char_strings_link = c->pop_pack (false); |
1879 | 0 | return true; |
1880 | 0 | } |
1881 | | |
1882 | | bool |
1883 | | OT::cff2::accelerator_subset_t::serialize (hb_serialize_context_t *c, |
1884 | | struct cff2_subset_plan &plan, |
1885 | | hb_array_t<int> normalized_coords) const |
1886 | 0 | { |
1887 | | /* push charstrings onto the object stack first which will ensure it packs as the last |
1888 | | object in the table. Keeping the chastrings last satisfies the requirements for patching |
1889 | | via IFTB. If this ordering needs to be changed in the future, charstrings should be left |
1890 | | at the end whenever HB_SUBSET_FLAGS_ITFB_REQUIREMENTS is enabled. */ |
1891 | 0 | if (!_serialize_cff2_charstrings(c, plan, *this)) |
1892 | 0 | return false; |
1893 | | |
1894 | | /* private dicts & local subrs */ |
1895 | 0 | hb_vector_t<table_info_t> private_dict_infos; |
1896 | 0 | if (unlikely (!private_dict_infos.resize (plan.subset_fdcount))) return false; |
1897 | | |
1898 | 0 | for (int i = (int)privateDicts.length; --i >= 0 ;) |
1899 | 0 | { |
1900 | 0 | if (plan.fdmap.has (i)) |
1901 | 0 | { |
1902 | 0 | objidx_t subrs_link = 0; |
1903 | |
|
1904 | 0 | if (plan.subset_localsubrs[i].length > 0) |
1905 | 0 | { |
1906 | 0 | auto *dest = c->push <CFF2Subrs> (); |
1907 | 0 | if (likely (dest->serialize (c, plan.subset_localsubrs[i]))) |
1908 | 0 | subrs_link = c->pop_pack (false); |
1909 | 0 | else |
1910 | 0 | { |
1911 | 0 | c->pop_discard (); |
1912 | 0 | return false; |
1913 | 0 | } |
1914 | 0 | } |
1915 | 0 | auto *pd = c->push<PrivateDict> (); |
1916 | 0 | cff2_private_dict_op_serializer_t privSzr (plan.desubroutinize, plan.drop_hints, plan.pinned, |
1917 | 0 | varStore, normalized_coords, |
1918 | 0 | plan.partial_instancing ? &plan.instancing : nullptr); |
1919 | 0 | if (likely (pd->serialize (c, privateDicts[i], privSzr, subrs_link))) |
1920 | 0 | { |
1921 | 0 | unsigned fd = plan.fdmap[i]; |
1922 | 0 | private_dict_infos[fd].size = c->length (); |
1923 | 0 | private_dict_infos[fd].link = c->pop_pack (); |
1924 | 0 | } |
1925 | 0 | else |
1926 | 0 | { |
1927 | 0 | c->pop_discard (); |
1928 | 0 | return false; |
1929 | 0 | } |
1930 | 0 | } |
1931 | 0 | } |
1932 | | |
1933 | | /* FDSelect */ |
1934 | 0 | if (fdSelect != &Null (CFF2FDSelect)) |
1935 | 0 | { |
1936 | 0 | c->push (); |
1937 | 0 | if (likely (hb_serialize_cff_fdselect (c, plan.num_glyphs, *(const FDSelect *)fdSelect, |
1938 | 0 | plan.orig_fdcount, |
1939 | 0 | plan.subset_fdselect_format, plan.subset_fdselect_size, |
1940 | 0 | plan.subset_fdselect_ranges))) |
1941 | 0 | plan.info.fd_select.link = c->pop_pack (); |
1942 | 0 | else |
1943 | 0 | { |
1944 | 0 | c->pop_discard (); |
1945 | 0 | return false; |
1946 | 0 | } |
1947 | 0 | } |
1948 | | |
1949 | | /* FDArray (FD Index) */ |
1950 | 0 | { |
1951 | 0 | auto *fda = c->push<CFF2FDArray> (); |
1952 | 0 | cff_font_dict_op_serializer_t fontSzr; |
1953 | 0 | auto it = |
1954 | 0 | + hb_zip (+ hb_iter (fontDicts) |
1955 | 0 | | hb_filter ([&] (const cff2_font_dict_values_t &_) |
1956 | 0 | { return plan.fdmap.has (&_ - &fontDicts[0]); }), |
1957 | 0 | hb_iter (private_dict_infos)) |
1958 | 0 | ; |
1959 | 0 | if (unlikely (!fda->serialize (c, it, fontSzr))) |
1960 | 0 | { |
1961 | 0 | c->pop_discard (); |
1962 | 0 | return false; |
1963 | 0 | } |
1964 | 0 | plan.info.fd_array_link = c->pop_pack (false); |
1965 | 0 | } |
1966 | | |
1967 | | /* variation store */ |
1968 | 0 | if (varStore != &Null (CFF2ItemVariationStore) && |
1969 | 0 | !plan.pinned) |
1970 | 0 | { |
1971 | 0 | if (plan.partial_instancing) |
1972 | 0 | { |
1973 | 0 | if (plan.instancing.has_variations ()) |
1974 | 0 | { |
1975 | 0 | c->push (); |
1976 | 0 | if (unlikely (!plan.instancing.serialize_var_store (c, *plan.axis_tags))) |
1977 | 0 | { |
1978 | 0 | c->pop_discard (); |
1979 | 0 | return false; |
1980 | 0 | } |
1981 | 0 | plan.info.var_store_link = c->pop_pack (false); |
1982 | 0 | } |
1983 | | /* else: the store emptied; the vstore op gets dropped along with it. */ |
1984 | 0 | } |
1985 | 0 | else |
1986 | 0 | { |
1987 | 0 | auto *dest = c->push<CFF2ItemVariationStore> (); |
1988 | 0 | if (unlikely (!dest->serialize (c, varStore))) |
1989 | 0 | { |
1990 | 0 | c->pop_discard (); |
1991 | 0 | return false; |
1992 | 0 | } |
1993 | 0 | plan.info.var_store_link = c->pop_pack (false); |
1994 | 0 | } |
1995 | 0 | } |
1996 | | |
1997 | 0 | OT::cff2 *cff2 = c->allocate_min<OT::cff2> (); |
1998 | 0 | if (unlikely (!cff2)) return false; |
1999 | | |
2000 | | /* header */ |
2001 | 0 | cff2->version.major = 0x02; |
2002 | 0 | cff2->version.minor = 0x00; |
2003 | 0 | cff2->topDict = OT::cff2::static_size; |
2004 | | |
2005 | | /* top dict */ |
2006 | 0 | { |
2007 | 0 | TopDict &dict = cff2 + cff2->topDict; |
2008 | 0 | cff2_top_dict_op_serializer_t topSzr; |
2009 | 0 | if (unlikely (!dict.serialize (c, topDict, topSzr, plan.info))) return false; |
2010 | 0 | cff2->topDictSize = c->head - (const char *)&dict; |
2011 | 0 | } |
2012 | | |
2013 | | /* global subrs */ |
2014 | 0 | { |
2015 | 0 | auto *dest = c->start_embed <CFF2Subrs> (); |
2016 | 0 | return dest->serialize (c, plan.subset_globalsubrs); |
2017 | 0 | } |
2018 | 0 | } |
2019 | | |
2020 | | bool |
2021 | | OT::cff2::accelerator_subset_t::subset (hb_subset_context_t *c) const |
2022 | 0 | { |
2023 | 0 | cff2_subset_plan cff2_plan; |
2024 | |
|
2025 | 0 | if (unlikely (!cff2_plan.create (*this, c->plan))) |
2026 | 0 | { |
2027 | 0 | c->serializer->err (HB_SERIALIZE_ERROR_OTHER); |
2028 | 0 | return false; |
2029 | 0 | } |
2030 | | |
2031 | | // If instantiating (pinned) and downgrade flag is set, convert to CFF1 |
2032 | 0 | if (cff2_plan.pinned && (c->plan->flags & HB_SUBSET_FLAGS_DOWNGRADE_CFF2)) |
2033 | 0 | { |
2034 | | // Serialize CFF1 to the subsetter's serializer |
2035 | | // If we run out of room, returning true will cause subsetter to retry with larger buffer |
2036 | 0 | bool result = CFF::serialize_cff2_to_cff1 (c->serializer, cff2_plan, topDict, *this); |
2037 | |
|
2038 | 0 | if (c->serializer->ran_out_of_room ()) |
2039 | 0 | return true; // Subsetter will retry with larger buffer |
2040 | | |
2041 | 0 | if (result && !c->serializer->in_error ()) |
2042 | 0 | { |
2043 | | // Success - end serialization to resolve links |
2044 | 0 | c->serializer->end_serialize (); |
2045 | | |
2046 | | // Copy the serialized CFF1 data and add as CFF table |
2047 | 0 | hb_blob_t *cff_blob = c->serializer->copy_blob (); |
2048 | 0 | if (cff_blob) |
2049 | 0 | { |
2050 | 0 | c->plan->add_table (HB_TAG('C','F','F',' '), cff_blob); |
2051 | 0 | hb_blob_destroy (cff_blob); |
2052 | | |
2053 | | // Return false to signal CFF2 table is not needed |
2054 | 0 | return false; |
2055 | 0 | } |
2056 | 0 | } |
2057 | | |
2058 | | // Conversion failed - don't fall back, fail hard for debugging |
2059 | 0 | return false; |
2060 | 0 | } |
2061 | | |
2062 | 0 | return serialize (c->serializer, cff2_plan, |
2063 | 0 | c->plan->normalized_coords.as_array ()); |
2064 | 0 | } |
2065 | | |
2066 | | #endif |