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