1use crate::{ShapeDirection, ShapeFeature, ShapeRequest, ShapeResult, SwashShaper};
8use oxitext_core::OxiTextError;
9
10impl SwashShaper {
11 pub fn shape_batch(
24 &mut self,
25 font_data: &[u8],
26 segments: &[&str],
27 px_size: f32,
28 ) -> Vec<Result<ShapeResult, OxiTextError>> {
29 segments
30 .iter()
31 .map(|text| self.shape_full(font_data, text, px_size))
32 .collect()
33 }
34
35 pub fn shape_batch_directed(
44 &mut self,
45 font_data: &[u8],
46 segments: &[(&str, ShapeDirection)],
47 px_size: f32,
48 ) -> Vec<Result<ShapeResult, OxiTextError>> {
49 segments
50 .iter()
51 .map(|(text, dir)| {
52 let req = ShapeRequest::builder()
53 .text(text)
54 .font_data(font_data)
55 .px_size(px_size)
56 .direction(*dir)
57 .build()
58 .map_err(|e| OxiTextError::Shaping(e.to_string()))?;
59 let glyphs = self.shape_request(&req)?;
60 Ok(ShapeResult::from_glyphs(glyphs, text, *dir))
61 })
62 .collect()
63 }
64
65 pub fn shape_batch_with_features(
71 &mut self,
72 font_data: &[u8],
73 segments: &[&str],
74 px_size: f32,
75 features: &[ShapeFeature],
76 ) -> Vec<Result<ShapeResult, OxiTextError>> {
77 segments
78 .iter()
79 .map(|text| {
80 let glyphs = self.shape_with_features(font_data, text, px_size, false, features)?;
81 Ok(ShapeResult::from_glyphs(glyphs, text, ShapeDirection::Ltr))
82 })
83 .collect()
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use std::path::Path;
91 use std::sync::Arc;
92
93 fn load_test_font() -> Arc<[u8]> {
94 let fixture =
95 Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/fixtures/test-font.ttf");
96 if fixture.exists() {
97 return Arc::from(
98 std::fs::read(&fixture)
99 .expect("read fixture font")
100 .as_slice(),
101 );
102 }
103 let candidates = [
104 "/Library/Fonts/Arial Unicode.ttf",
105 "/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf",
106 ];
107 for p in &candidates {
108 if Path::new(p).exists() {
109 return Arc::from(std::fs::read(p).expect("read system font").as_slice());
110 }
111 }
112 panic!("no test font found — add tests/fixtures/test-font.ttf");
113 }
114
115 #[test]
116 fn test_shape_batch_produces_one_result_per_segment() {
117 let font_bytes = load_test_font();
118 let mut shaper = SwashShaper::new();
119 let segments = ["Hello", "World", "Test"];
120 let results = shaper.shape_batch(&font_bytes, &segments, 16.0);
121 assert_eq!(results.len(), 3);
122 for r in &results {
123 assert!(r.is_ok(), "expected Ok for segment, got: {r:?}");
124 }
125 }
126
127 #[test]
128 fn test_shape_batch_empty_segments() {
129 let font_bytes = load_test_font();
130 let mut shaper = SwashShaper::new();
131 let results = shaper.shape_batch(&font_bytes, &[], 16.0);
132 assert!(results.is_empty());
133 }
134
135 #[test]
136 fn test_shape_batch_directed_mixed() {
137 let font_bytes = load_test_font();
138 let mut shaper = SwashShaper::new();
139 let segments = [
140 ("Hello", ShapeDirection::Ltr),
141 ("world", ShapeDirection::Ltr),
142 ];
143 let results = shaper.shape_batch_directed(&font_bytes, &segments, 16.0);
144 assert_eq!(results.len(), 2);
145 for r in &results {
146 assert!(
147 r.is_ok(),
148 "expected Ok from shape_batch_directed, got: {r:?}"
149 );
150 }
151 }
152
153 #[test]
154 fn test_shape_batch_glyphs_are_non_empty() {
155 let font_bytes = load_test_font();
156 let mut shaper = SwashShaper::new();
157 let segments = ["Hi", "Test"];
158 let results = shaper.shape_batch(&font_bytes, &segments, 16.0);
159 for r in &results {
160 let shape = r.as_ref().expect("batch result ok");
161 assert!(
162 !shape.glyphs.is_empty(),
163 "expected non-empty glyphs per segment"
164 );
165 }
166 }
167}