manifest/
v1_to_v2.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Utilities for conversion from Font Manifest v1 to v2.
6
7use crate::{v2, Family as FamilyV1, Font as FontV1, FontsManifest as FontsManifestV1};
8use anyhow::{format_err, Error};
9use itertools::Itertools;
10use std::path::{Path, PathBuf};
11
12impl TryFrom<FontsManifestV1> for v2::FontsManifest {
13    type Error = Error;
14
15    /// Converts a v1 [`manifest::FontsManifest`] to a v2 [`manifest::v2::Manifest`].
16    ///
17    /// This is purely an in-memory conversion, and does not load character sets for local files.
18    fn try_from(old: FontsManifestV1) -> Result<v2::FontsManifest, Error> {
19        // In v2, whether a font is a `fallback` is specified not per font family, but in an
20        // explicit ordered list.
21        // We capture each v1 family's `fallback` property for later use.
22        let families_and_fallbacks: Result<Vec<(v2::Family, bool)>, _> = old
23            .families
24            .iter()
25            .map(|v1_family| {
26                v2::Family::try_from(v1_family).map(|v2_family| (v2_family, v1_family.fallback))
27            })
28            .collect();
29        let families_and_fallbacks = families_and_fallbacks?;
30        // For every v1 family that's `fallback: true`, for every asset, for every typeface, add the
31        // typeface to the v2 `fallback_chain`.
32        let fallback_chain: Vec<v2::TypefaceId> = families_and_fallbacks
33            .iter()
34            .filter(|(_, is_fallback)| *is_fallback)
35            .flat_map(|(family, _)| {
36                family.assets.iter().flat_map(|asset| {
37                    asset.typefaces.iter().map(move |typeface| v2::TypefaceId {
38                        file_name: asset.file_name.clone(),
39                        index: typeface.index,
40                    })
41                })
42            })
43            .collect();
44        let families = families_and_fallbacks.into_iter().map(|(family, _)| family).collect();
45        Ok(v2::FontsManifest { families, fallback_chain, settings: v2::Settings::default() })
46    }
47}
48
49impl TryFrom<&FamilyV1> for v2::Family {
50    type Error = Error;
51
52    /// Converts a v1 [`manifest::Family`] to a [`manifest::v2::Family`].
53    ///
54    /// Assumes that all v1 fonts are local files.
55    fn try_from(old: &FamilyV1) -> Result<v2::Family, Error> {
56        let assets: Result<Vec<v2::Asset>, _> = old
57            .fonts
58            .iter()
59            .group_by(|font| &font.asset)
60            .into_iter()
61            .map(|(asset_path, font_group)| group_fonts_into_assets(asset_path, font_group))
62            .collect();
63
64        // v1 manifests only allow plain aliases, without any typeface property overrides.
65        let aliases = match &old.aliases {
66            None => vec![],
67            Some(aliases) => vec![v2::FontFamilyAliasSet::without_overrides(aliases)?],
68        };
69
70        Ok(v2::Family {
71            name: old.family.clone(),
72            aliases,
73            generic_family: old.generic_family.clone(),
74            assets: assets?,
75        })
76    }
77}
78
79/// Groups v1 [`manifest::Font`]s that share a single path into a v2 [`manifest::v2::Asset`] with
80/// one or more [`manifest::v2::Typeface`]s.
81///
82/// Params:
83/// - `asset_path`: The path to the font file
84/// - `font_group`: Iterator for all the v1 `Font`s that share the same `asset_path`
85fn group_fonts_into_assets<'a>(
86    asset_path: &PathBuf,
87    font_group: impl Iterator<Item = &'a FontV1>,
88) -> Result<v2::Asset, Error> {
89    // We unwrap PathBuf to_str conversions because we should only be reading valid Unicode-encoded
90    // paths from JSON manifests.
91    let file_name: String = asset_path
92        .file_name()
93        .ok_or_else(|| format_err!("Invalid path: {:?}", asset_path))?
94        .to_str()
95        .ok_or_else(|| format_err!("Invalid path: {:?}", asset_path))?
96        .to_string();
97    // If the file is in the package root, then the parent directory will be blank.
98    let directory: PathBuf =
99        asset_path.parent().map_or_else(|| PathBuf::from(""), Path::to_path_buf);
100    // Assuming that all v1 fonts are local files.
101    Ok(v2::Asset {
102        file_name,
103        location: v2::AssetLocation::LocalFile(v2::LocalFileLocator { directory }),
104        typefaces: font_group.map(font_to_typeface).collect(),
105    })
106}
107
108/// Convert a v1 [`manifest::Font`] to a v2 [`v2::Typeface`].
109fn font_to_typeface(font: &FontV1) -> v2::Typeface {
110    v2::Typeface {
111        index: font.index,
112        languages: font.languages.clone(),
113        style: v2::Style { slant: font.slant, weight: font.weight, width: font.width },
114        code_points: font.code_points.clone(),
115        postscript_name: None,
116        full_name: None,
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use char_set::CharSet;
124    use fidl_fuchsia_fonts::{GenericFontFamily, Slant, Width};
125
126    #[test]
127    fn test_v1_to_v2() -> Result<(), Error> {
128        let old = FontsManifestV1 {
129            families: vec![
130                FamilyV1 {
131                    family: "FamilyA".to_string(),
132                    aliases: Some(vec!["Family A".to_string(), "A Family".to_string()]),
133                    fonts: vec![
134                        FontV1 {
135                            asset: PathBuf::from("path/to/FamilyA-ExtraBold-Condensed.ttf"),
136                            index: 0,
137                            slant: Slant::Upright,
138                            weight: 800,
139                            width: Width::Condensed,
140                            languages: vec!["en-US".to_string()],
141                            package: None,
142                            code_points: CharSet::new(vec![0x1, 0x2, 0x3, 0x7, 0x8, 0x9, 0x100]),
143                        },
144                        FontV1 {
145                            asset: PathBuf::from("path/to/FamilyA-ExtraLight.ttf"),
146                            index: 0,
147                            slant: Slant::Upright,
148                            weight: 200,
149                            width: Width::Normal,
150                            languages: vec!["en-US".to_string()],
151                            package: None,
152                            code_points: CharSet::new(vec![
153                                0x11, 0x12, 0x13, 0x17, 0x18, 0x19, 0x100,
154                            ]),
155                        },
156                    ],
157                    fallback: true,
158                    generic_family: Some(GenericFontFamily::SansSerif),
159                },
160                FamilyV1 {
161                    family: "FamilyB".to_string(),
162                    aliases: Some(vec!["Family B".to_string(), "B Family".to_string()]),
163                    fonts: vec![
164                        FontV1 {
165                            asset: PathBuf::from("FamilyB.ttc"),
166                            index: 0,
167                            slant: Slant::Upright,
168                            weight: 800,
169                            width: Width::Condensed,
170                            languages: vec!["en-US".to_string()],
171                            package: None,
172                            code_points: CharSet::new(vec![0x1, 0x2, 0x3, 0x7, 0x8, 0x9, 0x100]),
173                        },
174                        FontV1 {
175                            asset: PathBuf::from("FamilyB.ttc"),
176                            index: 1,
177                            slant: Slant::Upright,
178                            weight: 200,
179                            width: Width::Normal,
180                            languages: vec!["zh-Hant".to_string()],
181                            package: None,
182                            code_points: CharSet::new(vec![
183                                0x11, 0x12, 0x13, 0x17, 0x18, 0x19, 0x100,
184                            ]),
185                        },
186                    ],
187                    fallback: false,
188                    generic_family: None,
189                },
190            ],
191        };
192
193        let expected = v2::FontsManifest {
194            families: vec![
195                v2::Family {
196                    name: "FamilyA".to_string(),
197                    aliases: vec![v2::FontFamilyAliasSet::without_overrides(vec![
198                        "Family A", "A Family",
199                    ])?],
200                    generic_family: Some(GenericFontFamily::SansSerif),
201                    assets: vec![
202                        v2::Asset {
203                            file_name: "FamilyA-ExtraBold-Condensed.ttf".to_string(),
204                            location: v2::AssetLocation::LocalFile(v2::LocalFileLocator {
205                                directory: PathBuf::from("path/to"),
206                            }),
207                            typefaces: vec![v2::Typeface {
208                                index: 0,
209                                languages: vec!["en-US".to_string()],
210                                style: v2::Style {
211                                    slant: Slant::Upright,
212                                    weight: 800,
213                                    width: Width::Condensed,
214                                },
215                                code_points: CharSet::new(vec![
216                                    0x1, 0x2, 0x3, 0x7, 0x8, 0x9, 0x100,
217                                ]),
218                                postscript_name: None,
219                                full_name: None,
220                            }],
221                        },
222                        v2::Asset {
223                            file_name: "FamilyA-ExtraLight.ttf".to_string(),
224                            location: v2::AssetLocation::LocalFile(v2::LocalFileLocator {
225                                directory: PathBuf::from("path/to"),
226                            }),
227                            typefaces: vec![v2::Typeface {
228                                index: 0,
229                                languages: vec!["en-US".to_string()],
230                                style: v2::Style {
231                                    slant: Slant::Upright,
232                                    weight: 200,
233                                    width: Width::Normal,
234                                },
235                                code_points: CharSet::new(vec![
236                                    0x11, 0x12, 0x13, 0x17, 0x18, 0x19, 0x100,
237                                ]),
238                                postscript_name: None,
239                                full_name: None,
240                            }],
241                        },
242                    ],
243                },
244                v2::Family {
245                    name: "FamilyB".to_string(),
246                    aliases: vec![v2::FontFamilyAliasSet::without_overrides(vec![
247                        "Family B", "B Family",
248                    ])?],
249                    generic_family: None,
250                    assets: vec![v2::Asset {
251                        file_name: "FamilyB.ttc".to_string(),
252                        location: v2::AssetLocation::LocalFile(v2::LocalFileLocator {
253                            directory: PathBuf::from(""),
254                        }),
255                        typefaces: vec![
256                            v2::Typeface {
257                                index: 0,
258                                languages: vec!["en-US".to_string()],
259                                style: v2::Style {
260                                    slant: Slant::Upright,
261                                    weight: 800,
262                                    width: Width::Condensed,
263                                },
264                                code_points: CharSet::new(vec![
265                                    0x1, 0x2, 0x3, 0x7, 0x8, 0x9, 0x100,
266                                ]),
267                                postscript_name: None,
268                                full_name: None,
269                            },
270                            v2::Typeface {
271                                index: 1,
272                                languages: vec!["zh-Hant".to_string()],
273                                style: v2::Style {
274                                    slant: Slant::Upright,
275                                    weight: 200,
276                                    width: Width::Normal,
277                                },
278                                code_points: CharSet::new(vec![
279                                    0x11, 0x12, 0x13, 0x17, 0x18, 0x19, 0x100,
280                                ]),
281                                postscript_name: None,
282                                full_name: None,
283                            },
284                        ],
285                    }],
286                },
287            ],
288            fallback_chain: vec![
289                v2::TypefaceId {
290                    file_name: "FamilyA-ExtraBold-Condensed.ttf".to_string(),
291                    index: 0,
292                },
293                v2::TypefaceId { file_name: "FamilyA-ExtraLight.ttf".to_string(), index: 0 },
294            ],
295            settings: v2::Settings::default(),
296        };
297
298        assert_eq!(v2::FontsManifest::try_from(old)?, expected);
299        Ok(())
300    }
301}