1
use std::mem;
2

            
3
use rustc_hash::{FxHashMap, FxHashSet};
4

            
5
use super::{
6
    owned, CFFFont, CFFVariant, CIDData, Charset, CustomCharset, DictDelta, FDSelect, Font,
7
    FontDict, MaybeOwnedIndex, Operand, Operator, ParseError, Range, ADOBE, CFF, IDENTITY,
8
    ISO_ADOBE_LAST_SID, OFFSET_ZERO, STANDARD_STRINGS,
9
};
10
use crate::binary::read::ReadArrayCow;
11
use crate::binary::write::{WriteBinaryDep, WriteBuffer};
12
use crate::subset::{SubsetError, SubsetGlyphs};
13

            
14
/// A subset CFF font.
15
pub struct SubsetCFF<'a> {
16
    table: CFF<'a>,
17
    new_to_old_id: Vec<u16>,
18
    old_to_new_id: FxHashMap<u16, u16>,
19
}
20

            
21
impl<'a> SubsetCFF<'a> {
22
    pub(crate) fn new(
23
        table: CFF<'a>,
24
        new_to_old_id: Vec<u16>,
25
        old_to_new_id: FxHashMap<u16, u16>,
26
    ) -> Self {
27
        SubsetCFF {
28
            table,
29
            new_to_old_id,
30
            old_to_new_id,
31
        }
32
    }
33
}
34

            
35
impl<'a> From<SubsetCFF<'a>> for CFF<'a> {
36
    fn from(subset: SubsetCFF<'a>) -> CFF<'a> {
37
        subset.table
38
    }
39
}
40

            
41
impl SubsetGlyphs for SubsetCFF<'_> {
42
    fn len(&self) -> usize {
43
        self.new_to_old_id.len()
44
    }
45

            
46
    fn old_id(&self, new_id: u16) -> u16 {
47
        self.new_to_old_id[usize::from(new_id)]
48
    }
49

            
50
    fn new_id(&self, old_id: u16) -> u16 {
51
        self.old_to_new_id.get(&old_id).copied().unwrap_or(0)
52
    }
53
}
54

            
55
impl<'a> CFF<'a> {
56
    /// Create a subset of this CFF table.
57
    ///
58
    /// - `glyph_ids` contains the ids of the glyphs to retain.
59
    ///
60
    /// When subsetting a Type 1 CFF font and retaining more than 255 glyphs the
61
    /// `convert_cff_to_cid_if_more_than_255_glyphs` argument controls whether the Type 1 font
62
    /// is converted to a CID keyed font in the process. The primary motivation for this is
63
    /// broader compatibility, especially if the subset font is embedded in a PDF.
64
    ///
65
    /// **Known Limitations**
66
    ///
67
    /// Currently the subsetting process does not produce the smallest possible output font.
68
    /// There are various parts of the source font that are copied to the output font as-is.
69
    /// Specifically the subsetting process does not subset the String INDEX.
70
    ///
71
    /// Subsetting the String INDEX requires updating all String IDs (SID) in the font so
72
    /// that they point at their new position in the String INDEX.
73
    pub fn subset(
74
        &'a self,
75
        glyph_ids: &[u16],
76
        convert_cff_to_cid_if_more_than_255_glyphs: bool,
77
    ) -> Result<SubsetCFF<'a>, SubsetError> {
78
        let mut cff = self.to_owned();
79
        let font: &mut Font<'_> = &mut cff.fonts[0];
80
        let mut charset = Vec::with_capacity(glyph_ids.len());
81
        let mut fd_select = Vec::with_capacity(glyph_ids.len());
82
        let mut new_to_old_id = Vec::with_capacity(glyph_ids.len());
83
        let mut old_to_new_id =
84
            FxHashMap::with_capacity_and_hasher(glyph_ids.len(), Default::default());
85
        let mut glyph_data = Vec::with_capacity(glyph_ids.len());
86
        let mut used_local_subrs = FxHashMap::default();
87
        let mut used_global_subrs = FxHashSet::default();
88

            
89
        for &glyph_id in glyph_ids {
90
            let char_string = font
91
                .char_strings_index
92
                .read_object(usize::from(glyph_id))
93
                .ok_or(ParseError::BadIndex)?;
94

            
95
            let subrs = super::charstring::char_string_used_subrs(
96
                CFFFont::CFF(font),
97
                &font.char_strings_index,
98
                &cff.global_subr_index,
99
                glyph_id,
100
            )?;
101
            used_global_subrs.extend(subrs.global_subr_used);
102
            if !subrs.local_subr_used.is_empty() {
103
                used_local_subrs.insert(glyph_id, subrs.local_subr_used);
104
            }
105

            
106
            glyph_data.push(char_string.to_owned());
107
            // Cast should be safe as there must be less than u16::MAX glyphs in a font
108
            old_to_new_id.insert(glyph_id, new_to_old_id.len() as u16);
109
            new_to_old_id.push(glyph_id);
110

            
111
            if glyph_id != 0 {
112
                let sid_or_cid = font
113
                    .charset
114
                    .id_for_glyph(glyph_id)
115
                    .ok_or(ParseError::BadIndex)?;
116
                charset.push(sid_or_cid);
117
            }
118

            
119
            // Calculate CID/Type 1 specific updates
120
            match &font.data {
121
                CFFVariant::CID(cid) => {
122
                    // Find out which font DICT this glyph maps to if it's a CID font
123
                    // Need to know which font DICT applies to each glyph, then ideally work out which FDSelect
124
                    // format is the best to use. For now it's probably good enough to just use format 0
125
                    let fd_index = cid
126
                        .fd_select
127
                        .font_dict_index(glyph_id)
128
                        .ok_or(ParseError::BadIndex)?;
129
                    fd_select.push(fd_index);
130
                }
131
                CFFVariant::Type1(_type1) => {}
132
            }
133
        }
134

            
135
        cff.global_subr_index =
136
            rebuild_global_subr_index(&cff.global_subr_index, used_global_subrs)?;
137
        font.char_strings_index = MaybeOwnedIndex::Owned(owned::Index { data: glyph_data });
138

            
139
        // Update CID/Type 1 specific structures
140
        match &mut font.data {
141
            CFFVariant::CID(cid) => {
142
                // Build new local_subr_indices
143
                cid.local_subr_indices = rebuild_local_subr_indices(cid, used_local_subrs)?;
144

            
145
                // Filter out Subr ops in the Private DICT if the local subr INDEX is None for
146
                // that DICT.
147
                filter_private_dict_subr_ops(cid);
148

            
149
                cid.fd_select = FDSelect::Format0 {
150
                    glyph_font_dict_indices: ReadArrayCow::Owned(fd_select),
151
                };
152
            }
153
            CFFVariant::Type1(type1) => {
154
                // Build new local_subr_index
155
                type1.local_subr_index = rebuild_type_1_local_subr_index(
156
                    type1.local_subr_index.as_ref(),
157
                    used_local_subrs,
158
                )?;
159

            
160
                // Filter out Subr ops in the Private DICT if the local subr INDEX is None.
161
                if type1.local_subr_index.is_none() {
162
                    type1
163
                        .private_dict
164
                        .dict
165
                        .retain(|(op, _)| *op != Operator::Subrs);
166
                }
167
            }
168
        }
169

            
170
        // Update the charset
171
        if font.is_cid_keyed() {
172
            font.charset = Charset::Custom(CustomCharset::Format0 {
173
                glyphs: ReadArrayCow::Owned(charset),
174
            });
175
        } else if convert_cff_to_cid_if_more_than_255_glyphs && font.char_strings_index.len() > 255
176
        {
177
            font.charset = convert_type1_to_cid(&mut cff.string_index, font)?;
178
        } else {
179
            let iso_adobe = 1..=ISO_ADOBE_LAST_SID;
180
            if charset
181
                .iter()
182
                .zip(iso_adobe)
183
                .all(|(sid, iso_adobe_sid)| *sid == iso_adobe_sid)
184
            {
185
                // As per section 18 of Technical Note #5176: There are no predefined charsets for
186
                // CID fonts. So this branch is only taken for Type 1 fonts.
187
                font.charset = Charset::ISOAdobe;
188
            } else {
189
                font.charset = Charset::Custom(CustomCharset::Format0 {
190
                    glyphs: ReadArrayCow::Owned(charset),
191
                });
192
            }
193
        }
194

            
195
        Ok(SubsetCFF {
196
            table: cff,
197
            new_to_old_id,
198
            old_to_new_id,
199
        })
200
    }
201
}
202

            
203
pub(crate) fn rebuild_global_subr_index(
204
    src_global_subr_index: &MaybeOwnedIndex<'_>,
205
    used_global_subrs: FxHashSet<usize>,
206
) -> Result<MaybeOwnedIndex<'static>, ParseError> {
207
    // Return a completely empty global subr index if there are no used global subrs
208
    if used_global_subrs.is_empty() {
209
        return Ok(MaybeOwnedIndex::Owned(owned::Index { data: Vec::new() }));
210
    }
211

            
212
    // Create a destination INDEX with the same number of entries as the source INDEX (see note
213
    // in rebuild_local_subr_indices)
214
    let mut dst_global_subr_index = owned::Index {
215
        data: vec![Vec::new(); src_global_subr_index.len()],
216
    };
217

            
218
    copy_used_subrs(
219
        used_global_subrs.iter().copied(),
220
        src_global_subr_index,
221
        &mut dst_global_subr_index,
222
    )?;
223

            
224
    Ok(MaybeOwnedIndex::Owned(dst_global_subr_index))
225
}
226

            
227
pub(crate) fn rebuild_local_subr_indices(
228
    cid: &CIDData<'_>,
229
    used_subrs_by_glyph: FxHashMap<u16, FxHashSet<usize>>,
230
) -> Result<Vec<Option<MaybeOwnedIndex<'static>>>, ParseError> {
231
    // Start off with all local subr indices as absent
232
    let mut indices = vec![None; cid.private_dicts.len()];
233

            
234
    for (glyph_id, used_subrs) in used_subrs_by_glyph {
235
        // For each glyph determine the index of the local subr index
236
        let index_of_local_subr_index = cid
237
            .fd_select
238
            .font_dict_index(glyph_id)
239
            .map(usize::from)
240
            .ok_or(ParseError::BadIndex)?;
241

            
242
        // Get the source Local Subr INDEX that we'll be copying from
243
        let src_local_subrs_index = match cid.local_subr_indices.get(index_of_local_subr_index) {
244
            Some(Some(index)) => Some(index),
245
            _ => None,
246
        }
247
        .ok_or(ParseError::BadIndex)?;
248

            
249
        // Get the Local Subr INDEX that we'll be copying to, if it doesn't exist then create it
250
        //
251
        // To avoid needing to rewrite all CharStrings to reference updated sub-routine
252
        // indices we instead fill the Local Subr INDEX with empty entries so that
253
        // indexes into it remain stable.
254
        //
255
        // An earlier iteration of this code only populated entries in the INDEX up to the largest
256
        // sub-routine index that was used. However this doesn't work because the operand to
257
        // callsubr is biased based on the number of entries in the INDEX, so for the existing char
258
        // strings to continue to work the same number of entries needs to be maintained. To do that
259
        // we fill it with empty entries.
260
        let dst_local_subr_index = match &mut indices[index_of_local_subr_index] {
261
            Some(index) => index,
262
            local_subr_index @ None => {
263
                *local_subr_index = Some(owned::Index {
264
                    data: vec![Vec::new(); src_local_subrs_index.len()],
265
                });
266
                local_subr_index.as_mut().unwrap() // NOTE(unwrap): safe as we set value above
267
            }
268
        };
269

            
270
        copy_used_subrs(
271
            used_subrs.iter().copied(),
272
            src_local_subrs_index,
273
            dst_local_subr_index,
274
        )?;
275
    }
276

            
277
    Ok(indices
278
        .into_iter()
279
        .map(|index| index.map(MaybeOwnedIndex::Owned))
280
        .collect())
281
}
282

            
283
fn copy_used_subrs(
284
    used_subrs: impl Iterator<Item = usize>,
285
    src_subrs_index: &MaybeOwnedIndex<'_>,
286
    dst_subr_index: &mut owned::Index,
287
) -> Result<(), ParseError> {
288
    // `used_subrs` contains the indexes of sub-routines in `src_subr_index` that need to be copied.
289
    // For each used subr we copy it to `dst_subr_index`.
290
    for subr_index in used_subrs {
291
        // Check to see if this sub-routine has already been copied to the INDEX. We do this
292
        // by checking if its length is greater than zero. A defined subroutine will have a
293
        // non-zero length as it must at least end with either an endchar or a return operator.
294
        if dst_subr_index
295
            .data
296
            .get(subr_index)
297
            .is_some_and(|subr| !subr.is_empty())
298
        {
299
            continue;
300
        }
301

            
302
        // Retrieve the Subr contents from the source Local Subr INDEX
303
        let char_string = src_subrs_index
304
            .read_object(subr_index)
305
            .ok_or(ParseError::BadIndex)?;
306

            
307
        // Now copy the Subr into the new index. I was curious about the efficiency of
308
        // extend_from_slice in this context but looking at the assembly it compiles down to
309
        // a call to memcpy.
310
        debug_assert_eq!(dst_subr_index.data[subr_index].len(), 0);
311
        dst_subr_index.data[subr_index].reserve_exact(char_string.len());
312
        dst_subr_index.data[subr_index].extend_from_slice(char_string);
313
    }
314
    Ok(())
315
}
316

            
317
pub(crate) fn rebuild_type_1_local_subr_index(
318
    src_local_subrs_index: Option<&MaybeOwnedIndex<'_>>,
319
    used_subrs_by_glyph: FxHashMap<u16, FxHashSet<usize>>,
320
) -> Result<Option<MaybeOwnedIndex<'static>>, ParseError> {
321
    if used_subrs_by_glyph.is_empty() {
322
        return Ok(None);
323
    }
324

            
325
    // Get the source Local Subr INDEX that we'll be copying from
326
    let src_local_subrs_index = src_local_subrs_index.ok_or(ParseError::BadIndex)?;
327

            
328
    // Create a destination INDEX with the same number of entries as the source INDEX (see note
329
    // in rebuild_local_subr_indices)
330
    let mut dst_local_subr_index = owned::Index {
331
        data: vec![Vec::new(); src_local_subrs_index.len()],
332
    };
333

            
334
    for used_subrs in used_subrs_by_glyph.values() {
335
        copy_used_subrs(
336
            used_subrs.iter().copied(),
337
            src_local_subrs_index,
338
            &mut dst_local_subr_index,
339
        )?;
340
    }
341

            
342
    Ok(Some(MaybeOwnedIndex::Owned(dst_local_subr_index)))
343
}
344

            
345
fn filter_private_dict_subr_ops(cid: &mut CIDData<'_>) {
346
    for (private_dict, local_subr_index) in cid
347
        .private_dicts
348
        .iter_mut()
349
        .zip(cid.local_subr_indices.iter())
350
    {
351
        if local_subr_index.is_none() {
352
            private_dict.dict.retain(|(op, _)| *op != Operator::Subrs);
353
        }
354
    }
355
}
356

            
357
fn convert_type1_to_cid<'a>(
358
    string_index: &mut MaybeOwnedIndex<'a>,
359
    font: &mut Font<'a>,
360
) -> Result<Charset<'a>, ParseError> {
361
    assert!(!font.is_cid_keyed());
362

            
363
    // Retrieve the SIDs of Adobe and Identity, adding them if they're not in the String INDEX
364
    // already.
365
    let (adobe_sid, identity_sid) = match (string_index.index(ADOBE), string_index.index(IDENTITY))
366
    {
367
        (Some(adobe_sid), Some(identity_sid)) => (adobe_sid, identity_sid),
368
        (Some(adobe_sid), None) => (adobe_sid, string_index.push(IDENTITY.to_owned())),
369
        (None, Some(identity_sid)) => (string_index.push(ADOBE.to_owned()), identity_sid),
370
        (None, None) => (
371
            string_index.push(ADOBE.to_owned()),
372
            string_index.push(IDENTITY.to_owned()),
373
        ),
374
    };
375

            
376
    // > the standard strings take SIDs in the range 0 to (nStdStrings –1). The first string in the
377
    // > String INDEX corresponds to the SID whose value is equal to nStdStrings, the first
378
    // > non-standard string
379
    let adobe_sid = adobe_sid + STANDARD_STRINGS.len();
380
    let identity_sid = identity_sid + STANDARD_STRINGS.len();
381

            
382
    // Build Font DICT
383
    let mut font_dict = FontDict::new();
384
    font_dict.inner_mut().push((
385
        Operator::Private,
386
        vec![Operand::Offset(0), Operand::Offset(0)],
387
    )); // Size and Offset will be updated when written out
388

            
389
    let mut font_dict_buffer = WriteBuffer::new();
390
    FontDict::write_dep(&mut font_dict_buffer, &font_dict, DictDelta::new())
391
        .map_err(|_err| ParseError::BadValue)?;
392
    let font_dict_index = MaybeOwnedIndex::Owned(owned::Index {
393
        data: vec![font_dict_buffer.into_inner()],
394
    });
395

            
396
    let n_glyphs = u16::try_from(font.char_strings_index.len())?;
397

            
398
    let fd_select = FDSelect::Format3 {
399
        ranges: ReadArrayCow::Owned(vec![Range {
400
            first: 0,
401
            n_left: 0,
402
        }]),
403
        sentinel: n_glyphs,
404
    };
405
    let cid_data = CFFVariant::CID(CIDData {
406
        font_dict_index,
407
        private_dicts: Vec::new(),
408
        local_subr_indices: Vec::new(),
409
        fd_select,
410
    });
411

            
412
    // Swap Type1 data with CID data
413
    let type1_data = match mem::replace(&mut font.data, cid_data) {
414
        CFFVariant::Type1(data) => data,
415
        CFFVariant::CID(_) => unreachable!(),
416
    };
417
    match &mut font.data {
418
        CFFVariant::Type1(_type1) => unreachable!(),
419
        CFFVariant::CID(cid) => {
420
            cid.private_dicts = vec![type1_data.private_dict];
421
            cid.local_subr_indices = vec![type1_data.local_subr_index];
422
        }
423
    };
424

            
425
    // Update the Top DICT
426
    // Add ROS
427
    let registry = Operand::Integer(i32::try_from(adobe_sid)?);
428
    let ordering = Operand::Integer(i32::try_from(identity_sid)?);
429
    let supplement = Operand::Integer(0);
430
    let ros = (Operator::ROS, vec![registry, ordering, supplement]);
431
    font.top_dict.inner_mut().insert(0, ros);
432

            
433
    // Add FDSelect and FDArray offsets to Top DICT
434
    // Actual offsets will be filled in when writing
435
    font.top_dict
436
        .inner_mut()
437
        .push((Operator::FDArray, OFFSET_ZERO.to_vec()));
438
    font.top_dict
439
        .inner_mut()
440
        .push((Operator::FDSelect, OFFSET_ZERO.to_vec()));
441

            
442
    // Add CIDCount
443
    font.top_dict.inner_mut().push((
444
        Operator::CIDCount,
445
        vec![Operand::Integer(i32::from(n_glyphs))],
446
    ));
447

            
448
    // Remove Private DICT offset and encoding
449
    font.top_dict.remove(Operator::Private);
450
    font.top_dict.remove(Operator::Encoding);
451

            
452
    // Add charset
453
    Ok(Charset::Custom(CustomCharset::Format2 {
454
        ranges: ReadArrayCow::Owned(vec![Range {
455
            first: 1,
456
            n_left: n_glyphs.checked_sub(2).ok_or(ParseError::BadIndex)?,
457
        }]),
458
    }))
459
}