1 use std::io::Read;
2 use std::str::FromStr;
3 
4 struct Args {
5     face_index: u32,
6     font_size: Option<f32>,
7     font_ptem: Option<f32>,
8     #[allow(dead_code)] font_funcs: Option<String>, // we don't use it, but have to parse it anyway
9     variations: Vec<String>,
10     #[allow(dead_code)] shaper: Vec<String>, // we don't use it, but have to parse it anyway
11     #[allow(dead_code)] shapers: Vec<String>, // we don't use it, but have to parse it anyway
12     direction: Option<rustybuzz::Direction>,
13     language: Option<rustybuzz::Language>,
14     script: Option<rustybuzz::Tag>,
15     #[allow(dead_code)] remove_default_ignorables: bool, // we don't use it, but have to parse it anyway
16     cluster_level: rustybuzz::BufferClusterLevel,
17     features: Vec<String>,
18     no_glyph_names: bool,
19     no_positions: bool,
20     no_advances: bool,
21     no_clusters: bool,
22     show_extents: bool,
23     show_flags: bool,
24     ned: bool,
25 }
26 
parse_args(args: Vec<std::ffi::OsString>) -> Result<Args, pico_args::Error>27 fn parse_args(args: Vec<std::ffi::OsString>) -> Result<Args, pico_args::Error> {
28     let mut parser = pico_args::Arguments::from_vec(args);
29     let args = Args {
30         face_index: parser.opt_value_from_str("--face-index")?.unwrap_or(0),
31         font_size: parser.opt_value_from_str("--font-size")?,
32         font_ptem: parser.opt_value_from_str("--font-ptem")?,
33         font_funcs: parser.opt_value_from_str("--font-funcs")?,
34         variations: parser.opt_value_from_fn("--variations", parse_string_list)?.unwrap_or_default(),
35         shaper: parser.opt_value_from_fn("--shaper", parse_string_list)?.unwrap_or_default(),
36         shapers: parser.opt_value_from_fn("--shapers", parse_string_list)?.unwrap_or_default(),
37         direction: parser.opt_value_from_str("--direction")?,
38         language: parser.opt_value_from_str("--language")?,
39         script: parser.opt_value_from_str("--script")?,
40         remove_default_ignorables: parser.contains("--remove-default-ignorables"),
41         cluster_level: parser.opt_value_from_fn("--cluster-level", parse_cluster)?.unwrap_or_default(),
42         features: parser.opt_value_from_fn("--features", parse_string_list)?.unwrap_or_default(),
43         no_glyph_names: parser.contains("--no-glyph-names"),
44         no_positions: parser.contains("--no-positions"),
45         no_advances: parser.contains("--no-advances"),
46         no_clusters: parser.contains("--no-clusters"),
47         show_extents: parser.contains("--show-extents"),
48         show_flags: parser.contains("--show-flags"),
49         ned: parser.contains("--ned"),
50     };
51 
52     parser.finish()?;
53 
54     Ok(args)
55 }
56 
parse_string_list(s: &str) -> Result<Vec<String>, String>57 fn parse_string_list(s: &str) -> Result<Vec<String>, String> {
58     Ok(s.split(',').map(|s| s.to_string()).collect())
59 }
60 
parse_cluster(s: &str) -> Result<rustybuzz::BufferClusterLevel, String>61 fn parse_cluster(s: &str) -> Result<rustybuzz::BufferClusterLevel, String> {
62     match s {
63         "0" => Ok(rustybuzz::BufferClusterLevel::MonotoneGraphemes),
64         "1" => Ok(rustybuzz::BufferClusterLevel::MonotoneCharacters),
65         "2" => Ok(rustybuzz::BufferClusterLevel::Characters),
66         _ => Err(format!("invalid cluster level"))
67     }
68 }
69 
shape(font_path: &str, text: &str, options: &str) -> String70 pub fn shape(font_path: &str, text: &str, options: &str) -> String {
71     let args = options.split(' ').filter(|s| !s.is_empty()).map(|s| std::ffi::OsString::from(s)).collect();
72     let args = parse_args(args).unwrap();
73 
74     let font_data = std::fs::read(font_path).unwrap();
75     let mut font = rustybuzz::Font::from_data(&font_data, args.face_index).unwrap();
76 
77     if let Some(ptem) = args.font_ptem {
78         font.set_ptem(ptem);
79     }
80 
81     if let Some(size) = args.font_size {
82         font.set_scale(size as i32, size as i32);
83     }
84 
85     if !args.variations.is_empty() {
86         let variations: Vec<_> = args.variations.iter()
87             .map(|s| rustybuzz::Variation::from_str(s).unwrap()).collect();
88         font.set_variations(&variations);
89     }
90 
91     let mut buffer = rustybuzz::UnicodeBuffer::new();
92     buffer.push_str(text);
93 
94     if let Some(d) = args.direction {
95         buffer.set_direction(d);
96     }
97 
98     if let Some(lang) = args.language {
99         buffer.set_language(lang);
100     }
101 
102     if let Some(script) = args.script {
103         buffer.set_script(script);
104     }
105 
106     buffer.set_cluster_level(args.cluster_level);
107     buffer.reset_clusters();
108 
109     let mut features = Vec::new();
110     for feature_str in args.features {
111         let feature = rustybuzz::Feature::from_str(&feature_str).unwrap();
112         features.push(feature);
113     }
114 
115     let glyph_buffer = rustybuzz::shape(&font, &features, buffer);
116 
117     let mut format_flags = rustybuzz::SerializeFlags::default();
118     if args.no_glyph_names {
119         format_flags |= rustybuzz::SerializeFlags::NO_GLYPH_NAMES;
120     }
121 
122     if args.no_clusters || args.ned {
123         format_flags |= rustybuzz::SerializeFlags::NO_CLUSTERS;
124     }
125 
126     if args.no_positions {
127         format_flags |= rustybuzz::SerializeFlags::NO_POSITIONS;
128     }
129 
130     if args.no_advances || args.ned {
131         format_flags |= rustybuzz::SerializeFlags::NO_ADVANCES;
132     }
133 
134     if args.show_extents {
135         format_flags |= rustybuzz::SerializeFlags::GLYPH_EXTENTS;
136     }
137 
138     if args.show_flags {
139         format_flags |= rustybuzz::SerializeFlags::GLYPH_FLAGS;
140     }
141 
142     let mut serializer = glyph_buffer.serializer(
143         Some(&font),
144         rustybuzz::SerializeFormat::Text,
145         format_flags,
146     );
147 
148     let mut string = String::new();
149     serializer.read_to_string(&mut string).unwrap();
150     string
151 }
152