this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

cargo fmt

+317 -115
+117 -41
crates/tala-cli/src/main.rs
··· 32 32 fn main() { 33 33 let cli = Cli::parse(); 34 34 let result = match cli.command { 35 - Command::Check { path } => cmd_check(&path), 36 - Command::Review { path, tag } => cmd_review(&path, tag.as_deref()), 35 + Command::Check { path } => cmd_check(&path), 36 + Command::Review { path, tag } => cmd_review(&path, tag.as_deref()), 37 37 }; 38 38 if let Err(e) = result { 39 39 eprintln!("error: {e}"); ··· 62 62 .map_err(|e| format!("{}: {e}", typ_path.display()))?; 63 63 let cards = parse_cards(&source); 64 64 let sidecar = Sidecar::load_or_empty_for(&typ_path)?; 65 - files.push(DeckFile { typ_path, cards, sidecar }); 65 + files.push(DeckFile { 66 + typ_path, 67 + cards, 68 + sidecar, 69 + }); 66 70 } 67 71 Ok(files) 68 72 } ··· 93 97 for df in &files { 94 98 // Cards are identified by position index in the file. 95 99 let ids: Vec<String> = (0..df.cards.len()).map(|i| i.to_string()).collect(); 96 - let n_fb = df.cards.iter().filter(|c| matches!(c.kind, CardKind::FrontBack { .. })).count(); 97 - let n_cloze = df.cards.iter().filter(|c| matches!(c.kind, CardKind::Cloze { .. })).count(); 98 - let n_img = df.cards.iter().filter(|c| matches!(c.kind, CardKind::ImgCloze { .. })).count(); 100 + let n_fb = df 101 + .cards 102 + .iter() 103 + .filter(|c| matches!(c.kind, CardKind::FrontBack { .. })) 104 + .count(); 105 + let n_cloze = df 106 + .cards 107 + .iter() 108 + .filter(|c| matches!(c.kind, CardKind::Cloze { .. })) 109 + .count(); 110 + let n_img = df 111 + .cards 112 + .iter() 113 + .filter(|c| matches!(c.kind, CardKind::ImgCloze { .. })) 114 + .count(); 99 115 100 116 println!("{}", df.typ_path.display()); 101 - println!(" {} cards (front/back: {n_fb} cloze: {n_cloze} img-cloze: {n_img})", 102 - df.cards.len()); 117 + println!( 118 + " {} cards (front/back: {n_fb} cloze: {n_cloze} img-cloze: {n_img})", 119 + df.cards.len() 120 + ); 103 121 104 122 let orphans = df.sidecar.orphaned(&ids); 105 123 let missing = df.sidecar.missing(&ids); ··· 131 149 // Collect all due reviewable items across files (owned, no refs into `files`). 132 150 struct Item { 133 151 file_idx: usize, 134 - card_id: String, 135 - kind: CardKind, 136 - sub_id: String, // "forward", "b0", "rect-0" 137 - current: Option<Schedule>, 152 + card_id: String, 153 + kind: CardKind, 154 + sub_id: String, // "forward", "b0", "rect-0" 155 + current: Option<Schedule>, 138 156 } 139 157 140 158 let mut queue: Vec<Item> = Vec::new(); 141 159 for (fi, df) in files.iter().enumerate() { 142 160 for (ci, card) in df.cards.iter().enumerate() { 143 161 let card_id = ci.to_string(); 144 - if let Some(tag) = tag_filter && !card.tags.iter().any(|t| t == tag) { 162 + if let Some(tag) = tag_filter 163 + && !card.tags.iter().any(|t| t == tag) 164 + { 145 165 continue; 146 166 } 147 167 match (&card.kind, df.sidecar.get(&card_id)) { 148 168 (CardKind::FrontBack { .. }, sched) => { 149 169 let fwd = match sched { 150 - Some(CardSchedule::FrontBack { forward_schedule, .. }) => forward_schedule.clone(), 170 + Some(CardSchedule::FrontBack { 171 + forward_schedule, .. 172 + }) => forward_schedule.clone(), 151 173 _ => None, 152 174 }; 153 175 if fwd.as_ref().is_none_or(is_due) { 154 - queue.push(Item { file_idx: fi, card_id: card_id.clone(), 155 - kind: card.kind.clone(), sub_id: "forward".into(), current: fwd }); 176 + queue.push(Item { 177 + file_idx: fi, 178 + card_id: card_id.clone(), 179 + kind: card.kind.clone(), 180 + sub_id: "forward".into(), 181 + current: fwd, 182 + }); 156 183 } 157 184 // TODO: reverse side for dir: bi 158 185 } ··· 165 192 let key = format!("b{}", blank.index); 166 193 let current = blank_schedules.get(&key).cloned(); 167 194 if current.as_ref().is_none_or(is_due) { 168 - queue.push(Item { file_idx: fi, card_id: card_id.clone(), 169 - kind: card.kind.clone(), sub_id: key, current }); 195 + queue.push(Item { 196 + file_idx: fi, 197 + card_id: card_id.clone(), 198 + kind: card.kind.clone(), 199 + sub_id: key, 200 + current, 201 + }); 170 202 } 171 203 } 172 204 } ··· 182 214 return Ok(()); 183 215 } 184 216 185 - println!("{} cards due. Grade with 1 (again) 2 (hard) 3 (good) 4 (easy). Ctrl-C to stop.\n", 186 - queue.len()); 217 + println!( 218 + "{} cards due. Grade with 1 (again) 2 (hard) 3 (good) 4 (easy). Ctrl-C to stop.\n", 219 + queue.len() 220 + ); 187 221 188 222 let stdin = io::stdin(); 189 223 let mut grades: Vec<(usize, String, String, Grade)> = Vec::new(); // (file_idx, card_id, sub_id, grade) 190 224 191 225 for (i, item) in queue.iter().enumerate() { 192 - println!("[{}/{}] {} — {}", i + 1, queue.len(), item.card_id, item.sub_id); 226 + println!( 227 + "[{}/{}] {} — {}", 228 + i + 1, 229 + queue.len(), 230 + item.card_id, 231 + item.sub_id 232 + ); 193 233 print_card_preview(&item.kind); 194 234 195 235 let grade = loop { ··· 199 239 stdin.lock().read_line(&mut line)?; 200 240 match line.trim().parse::<u8>().ok().and_then(Grade::from_u8) { 201 241 Some(g) => break g, 202 - None => eprintln!(" Enter 1, 2, 3, or 4."), 242 + None => eprintln!(" Enter 1, 2, 3, or 4."), 203 243 } 204 244 }; 205 245 206 - let days_elapsed = item.current.as_ref().map_or(0, |s| { 207 - days_between(&s.due, &today_str()).max(0) as u32 208 - }); 246 + let days_elapsed = item 247 + .current 248 + .as_ref() 249 + .map_or(0, |s| days_between(&s.due, &today_str()).max(0) as u32); 209 250 let new_sched = next_schedule(item.current.as_ref(), days_elapsed, grade); 210 251 println!(" → due {}\n", new_sched.due); 211 252 212 - grades.push((item.file_idx, item.card_id.clone(), item.sub_id.clone(), grade)); 253 + grades.push(( 254 + item.file_idx, 255 + item.card_id.clone(), 256 + item.sub_id.clone(), 257 + grade, 258 + )); 213 259 214 260 // Write the new schedule into the in-memory sidecar. 215 261 let df = &mut files[item.file_idx]; 216 - apply_schedule(&mut df.sidecar, &item.card_id, &item.sub_id, new_sched, &item.kind); 262 + apply_schedule( 263 + &mut df.sidecar, 264 + &item.card_id, 265 + &item.sub_id, 266 + new_sched, 267 + &item.kind, 268 + ); 217 269 } 218 270 219 271 if grades.is_empty() { ··· 232 284 let repo = git2::Repository::discover(path)?; 233 285 for fi in saved { 234 286 let sidecar_path = Sidecar::path_for(&files[fi].typ_path); 235 - git_add_and_commit(&repo, &sidecar_path, 236 - &format!("review session {}", today_str()))?; 287 + git_add_and_commit( 288 + &repo, 289 + &sidecar_path, 290 + &format!("review session {}", today_str()), 291 + )?; 237 292 } 238 293 239 294 println!("Session saved ({} cards reviewed).", grades.len()); 240 295 Ok(()) 241 296 } 242 297 243 - fn apply_schedule(sidecar: &mut Sidecar, card_id: &str, sub_id: &str, sched: Schedule, kind: &CardKind) { 298 + fn apply_schedule( 299 + sidecar: &mut Sidecar, 300 + card_id: &str, 301 + sub_id: &str, 302 + sched: Schedule, 303 + kind: &CardKind, 304 + ) { 244 305 match kind { 245 306 CardKind::FrontBack { .. } => { 246 - let entry = sidecar.cards.entry(card_id.to_owned()) 247 - .or_insert(CardSchedule::FrontBack { forward_schedule: None, reverse_schedule: None }); 248 - if let CardSchedule::FrontBack { forward_schedule, .. } = entry 249 - && sub_id == "forward" { *forward_schedule = Some(sched); } 307 + let entry = 308 + sidecar 309 + .cards 310 + .entry(card_id.to_owned()) 311 + .or_insert(CardSchedule::FrontBack { 312 + forward_schedule: None, 313 + reverse_schedule: None, 314 + }); 315 + if let CardSchedule::FrontBack { 316 + forward_schedule, .. 317 + } = entry 318 + && sub_id == "forward" 319 + { 320 + *forward_schedule = Some(sched); 321 + } 250 322 } 251 323 CardKind::Cloze { .. } => { 252 - let entry = sidecar.cards.entry(card_id.to_owned()) 253 - .or_insert(CardSchedule::Cloze { blanks: std::collections::HashMap::new() }); 324 + let entry = sidecar 325 + .cards 326 + .entry(card_id.to_owned()) 327 + .or_insert(CardSchedule::Cloze { 328 + blanks: std::collections::HashMap::new(), 329 + }); 254 330 if let CardSchedule::Cloze { blanks } = entry { 255 331 blanks.insert(sub_id.to_owned(), sched); 256 332 } ··· 262 338 fn print_card_preview(kind: &CardKind) { 263 339 match kind { 264 340 CardKind::FrontBack { .. } => println!(" [front/back — render with tala-ui]"), 265 - CardKind::Cloze { .. } => println!(" [cloze — render with tala-ui]"), 341 + CardKind::Cloze { .. } => println!(" [cloze — render with tala-ui]"), 266 342 CardKind::ImgCloze { src } => println!(" [img-cloze: {src}]"), 267 343 } 268 344 } ··· 270 346 fn days_between(from: &str, to: &str) -> i64 { 271 347 // Lexicographic YYYY-MM-DD comparison; parse to epoch days for arithmetic. 272 348 fn to_epoch(s: &str) -> i64 { 273 - let parts: Vec<i64> = s.splitn(3, '-') 274 - .filter_map(|p| p.parse().ok()) 275 - .collect(); 276 - if parts.len() != 3 { return 0; } 349 + let parts: Vec<i64> = s.splitn(3, '-').filter_map(|p| p.parse().ok()).collect(); 350 + if parts.len() != 3 { 351 + return 0; 352 + } 277 353 let (y, m, d) = (parts[0] as i32, parts[1] as u32, parts[2] as u32); 278 354 let (y2, m2) = if m <= 2 { (y - 1, m + 9) } else { (y, m - 3) }; 279 355 let era = if y2 >= 0 { y2 } else { y2 - 399 } / 400;
+24 -13
crates/tala-format/src/lib.rs
··· 76 76 let name = func_ident(node)?; 77 77 let span = node.range(); 78 78 match name.as_str() { 79 - "card" => parse_front_back(node, span), 80 - "cloze" => parse_cloze(node, span), 79 + "card" => parse_front_back(node, span), 80 + "cloze" => parse_cloze(node, span), 81 81 "img_cloze" => parse_img_cloze(node, span), 82 - _ => None, 82 + _ => None, 83 83 } 84 84 } 85 85 ··· 190 190 } 191 191 let dir = match named.get("dir").map(|s| s.as_str()) { 192 192 Some("bi") => Direction::Bidirectional, 193 - _ => Direction::Forward, 193 + _ => Direction::Forward, 194 194 }; 195 195 196 196 Some(CardEntry { ··· 198 198 span, 199 199 kind: CardKind::FrontBack { 200 200 front_span: content_blocks[0].clone(), 201 - back_span: content_blocks[1].clone(), 201 + back_span: content_blocks[1].clone(), 202 202 dir, 203 203 }, 204 204 }) ··· 211 211 let body_span = content_blocks.first()?.clone(); 212 212 213 213 // Walk the body ContentBlock for #blank[] calls. 214 - let body_node = args.children().find(|c| c.kind() == SyntaxKind::ContentBlock)?; 214 + let body_node = args 215 + .children() 216 + .find(|c| c.kind() == SyntaxKind::ContentBlock)?; 215 217 let mut blanks = Vec::new(); 216 218 find_blanks(&body_node, &mut blanks); 217 219 ··· 244 246 { 245 247 if let Some(content_span) = blank_content_span(node) { 246 248 out.push(BlankEntry { 247 - index: out.len(), 248 - span: node.range(), 249 + index: out.len(), 250 + span: node.range(), 249 251 content_span, 250 252 }); 251 253 } ··· 298 300 #[test] 299 301 fn bidirectional_flag() { 300 302 let cards = parse_cards(SAMPLE); 301 - let CardKind::FrontBack { dir, .. } = &cards[1].kind else { panic!() }; 303 + let CardKind::FrontBack { dir, .. } = &cards[1].kind else { 304 + panic!() 305 + }; 302 306 assert_eq!(*dir, Direction::Bidirectional); 303 307 } 304 308 305 309 #[test] 306 310 fn cloze_blanks() { 307 311 let cards = parse_cards(SAMPLE); 308 - let CardKind::Cloze { blanks, .. } = &cards[2].kind else { panic!() }; 312 + let CardKind::Cloze { blanks, .. } = &cards[2].kind else { 313 + panic!() 314 + }; 309 315 assert_eq!(blanks.len(), 2); 310 316 assert_eq!(blanks[0].index, 0); 311 317 assert_eq!(blanks[1].index, 1); ··· 314 320 #[test] 315 321 fn img_cloze_src() { 316 322 let cards = parse_cards(SAMPLE); 317 - let CardKind::ImgCloze { src } = &cards[3].kind else { panic!() }; 323 + let CardKind::ImgCloze { src } = &cards[3].kind else { 324 + panic!() 325 + }; 318 326 assert_eq!(src, "capacitor-photo"); 319 327 } 320 328 ··· 338 346 for (i, card) in cards.iter().enumerate() { 339 347 let text = &SAMPLE[card.span.clone()]; 340 348 assert!( 341 - text.starts_with("card") || text.starts_with("cloze") || text.starts_with("img_cloze"), 342 - "card {i} span text: {:?}", &text[..20.min(text.len())] 349 + text.starts_with("card") 350 + || text.starts_with("cloze") 351 + || text.starts_with("img_cloze"), 352 + "card {i} span text: {:?}", 353 + &text[..20.min(text.len())] 343 354 ); 344 355 } 345 356 }
+58 -26
crates/tala-srs/src/lib.rs
··· 51 51 52 52 impl Sidecar { 53 53 pub fn empty() -> Self { 54 - Self { version: 1, cards: HashMap::new() } 54 + Self { 55 + version: 1, 56 + cards: HashMap::new(), 57 + } 55 58 } 56 59 57 60 pub fn load(path: &Path) -> Result<Self, Error> { ··· 69 72 /// Load the sidecar paired with `typ_path`, or return empty if it doesn't exist. 70 73 pub fn load_or_empty_for(typ_path: &Path) -> Result<Self, Error> { 71 74 let path = Self::path_for(typ_path); 72 - if path.exists() { Self::load(&path) } else { Ok(Self::empty()) } 75 + if path.exists() { 76 + Self::load(&path) 77 + } else { 78 + Ok(Self::empty()) 79 + } 73 80 } 74 81 75 82 pub fn save(&self, path: &Path) -> Result<(), Error> { ··· 90 97 #[deprecated(note = "use load_or_empty_for(typ_path) instead")] 91 98 pub fn load_or_empty(deck_dir: &Path) -> Result<Self, Error> { 92 99 let path = deck_dir.join("cards.srs.json"); 93 - if path.exists() { Self::load(&path) } else { Ok(Self::empty()) } 100 + if path.exists() { 101 + Self::load(&path) 102 + } else { 103 + Ok(Self::empty()) 104 + } 94 105 } 95 106 96 107 #[deprecated(note = "use save_for(typ_path) instead")] ··· 137 148 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 138 149 pub enum Grade { 139 150 Again = 1, 140 - Hard = 2, 141 - Good = 3, 142 - Easy = 4, 151 + Hard = 2, 152 + Good = 3, 153 + Easy = 4, 143 154 } 144 155 145 156 impl Grade { ··· 177 188 178 189 let item_state = match grade { 179 190 Grade::Again => next.again, 180 - Grade::Hard => next.hard, 181 - Grade::Good => next.good, 182 - Grade::Easy => next.easy, 191 + Grade::Hard => next.hard, 192 + Grade::Good => next.good, 193 + Grade::Easy => next.easy, 183 194 }; 184 195 185 196 let due = { ··· 260 271 impl std::fmt::Display for Error { 261 272 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 262 273 match self { 263 - Error::Io(e) => write!(f, "io: {e}"), 274 + Error::Io(e) => write!(f, "io: {e}"), 264 275 Error::Json(e) => write!(f, "json: {e}"), 265 276 } 266 277 } ··· 268 279 269 280 impl std::error::Error for Error {} 270 281 271 - impl From<std::io::Error> for Error { fn from(e: std::io::Error) -> Self { Error::Io(e) } } 272 - impl From<serde_json::Error> for Error { fn from(e: serde_json::Error) -> Self { Error::Json(e) } } 282 + impl From<std::io::Error> for Error { 283 + fn from(e: std::io::Error) -> Self { 284 + Error::Io(e) 285 + } 286 + } 287 + impl From<serde_json::Error> for Error { 288 + fn from(e: serde_json::Error) -> Self { 289 + Error::Json(e) 290 + } 291 + } 273 292 274 293 // ── Tests ───────────────────────────────────────────────────────────────────── 275 294 ··· 279 298 280 299 fn sample_sidecar() -> Sidecar { 281 300 let mut s = Sidecar::empty(); 282 - s.insert("fb-001".into(), CardSchedule::FrontBack { 283 - forward_schedule: Some(Schedule { 284 - due: "2026-03-20".into(), 285 - stability: 12.3, 286 - difficulty: 0.4, 287 - }), 288 - reverse_schedule: None, 289 - }); 290 - s.insert("cl-001".into(), CardSchedule::Cloze { 291 - blanks: { 292 - let mut m = HashMap::new(); 293 - m.insert("b0".into(), Schedule { due: "2026-03-21".into(), stability: 4.1, difficulty: 0.6 }); 294 - m 301 + s.insert( 302 + "fb-001".into(), 303 + CardSchedule::FrontBack { 304 + forward_schedule: Some(Schedule { 305 + due: "2026-03-20".into(), 306 + stability: 12.3, 307 + difficulty: 0.4, 308 + }), 309 + reverse_schedule: None, 295 310 }, 296 - }); 311 + ); 312 + s.insert( 313 + "cl-001".into(), 314 + CardSchedule::Cloze { 315 + blanks: { 316 + let mut m = HashMap::new(); 317 + m.insert( 318 + "b0".into(), 319 + Schedule { 320 + due: "2026-03-21".into(), 321 + stability: 4.1, 322 + difficulty: 0.6, 323 + }, 324 + ); 325 + m 326 + }, 327 + }, 328 + ); 297 329 s 298 330 } 299 331
+33 -13
crates/tala-typst/examples/dump_render.rs
··· 3 3 4 4 fn main() { 5 5 let cases = [ 6 - ("authoring", tala_typst::Preamble::Authoring, r#"#card(id: "fb")[What is the circuit model of an actual capacitor?][Series: ESR + ESL + ideal $C$]"#), 7 - ("review_front", tala_typst::Preamble::ReviewFront, r#"#card(id: "fb")[$ integral_(-infinity)^infinity e^(-x^2) dif x = $][$ sqrt(pi) $]"#), 8 - ("review_cloze", tala_typst::Preamble::ReviewCloze, r#"#cloze(id: "cl")[The resonant frequency is where #blank[inductance] and #blank[capacitance] cancel.]"#), 6 + ( 7 + "authoring", 8 + tala_typst::Preamble::Authoring, 9 + r#"#card(id: "fb")[What is the circuit model of an actual capacitor?][Series: ESR + ESL + ideal $C$]"#, 10 + ), 11 + ( 12 + "review_front", 13 + tala_typst::Preamble::ReviewFront, 14 + r#"#card(id: "fb")[$ integral_(-infinity)^infinity e^(-x^2) dif x = $][$ sqrt(pi) $]"#, 15 + ), 16 + ( 17 + "review_cloze", 18 + tala_typst::Preamble::ReviewCloze, 19 + r#"#cloze(id: "cl")[The resonant frequency is where #blank[inductance] and #blank[capacitance] cancel.]"#, 20 + ), 9 21 ]; 10 22 11 23 let tmp = std::env::temp_dir(); ··· 35 47 } 36 48 37 49 fn premul_to_straight(rgba: &[u8]) -> Vec<u8> { 38 - rgba.chunks_exact(4).flat_map(|px| { 39 - let [r, g, b, a] = [px[0], px[1], px[2], px[3]]; 40 - if a == 0 { return [255u8, 255, 255, 255]; } 41 - if a == 255 { return [r, g, b, 255]; } 42 - let f = 255.0 / a as f32; 43 - [(r as f32 * f).min(255.0) as u8, 44 - (g as f32 * f).min(255.0) as u8, 45 - (b as f32 * f).min(255.0) as u8, 46 - a] 47 - }).collect() 50 + rgba.chunks_exact(4) 51 + .flat_map(|px| { 52 + let [r, g, b, a] = [px[0], px[1], px[2], px[3]]; 53 + if a == 0 { 54 + return [255u8, 255, 255, 255]; 55 + } 56 + if a == 255 { 57 + return [r, g, b, 255]; 58 + } 59 + let f = 255.0 / a as f32; 60 + [ 61 + (r as f32 * f).min(255.0) as u8, 62 + (g as f32 * f).min(255.0) as u8, 63 + (b as f32 * f).min(255.0) as u8, 64 + a, 65 + ] 66 + }) 67 + .collect() 48 68 }
+80 -21
crates/tala-typst/src/lib.rs
··· 57 57 let world = TalaWorld::new(deck_dir, source_text); 58 58 59 59 let warned = typst::compile::<typst::layout::PagedDocument>(&world); 60 - let document = warned.output.map_err(|diags| { 61 - Error::Compile(diags.iter().map(|d| d.message.to_string()).collect()) 62 - })?; 60 + let document = warned 61 + .output 62 + .map_err(|diags| Error::Compile(diags.iter().map(|d| d.message.to_string()).collect()))?; 63 63 64 64 let page = document.pages.first().ok_or(Error::NoOutput)?; 65 65 ··· 67 67 let preamble_offset = preamble_str.len() + 1; // +1 for the '\n' separator 68 68 69 69 let math_spans = collect_math_spans(&world.source, preamble_offset); 70 - let glyph_map = 71 - collect_glyph_map(&page.frame, &world.source, preamble_offset, 2.0, &math_spans); 70 + let glyph_map = collect_glyph_map( 71 + &page.frame, 72 + &world.source, 73 + preamble_offset, 74 + 2.0, 75 + &math_spans, 76 + ); 72 77 73 78 // Derive blank boxes using the glyph_map (per-char positions for text glyphs) 74 79 // combined with shapes from the frame (shapes carry source spans and cover ··· 76 81 let blank_boxes = if blank_spans.is_empty() { 77 82 vec![] 78 83 } else { 79 - collect_blank_boxes(&page.frame, &glyph_map, blank_spans, &world.source, preamble_offset, 2.0) 84 + collect_blank_boxes( 85 + &page.frame, 86 + &glyph_map, 87 + blank_spans, 88 + &world.source, 89 + preamble_offset, 90 + 2.0, 91 + ) 80 92 }; 81 93 82 94 let pixmap = typst_render_page(page, 2.0); // 2 px/pt ≈ 144 dpi ··· 127 139 } 128 140 129 141 // Pass 2: shapes from the frame (e.g. math vinculum, rule lines). 130 - walk_frame_for_shapes(frame, source, blank_spans, preamble_offset, scale, 0.0, 0.0, &mut boxes); 142 + walk_frame_for_shapes( 143 + frame, 144 + source, 145 + blank_spans, 146 + preamble_offset, 147 + scale, 148 + 0.0, 149 + 0.0, 150 + &mut boxes, 151 + ); 131 152 132 - boxes.into_iter().map(|b| b.unwrap_or([0.0, 0.0, 0.0, 0.0])).collect() 153 + boxes 154 + .into_iter() 155 + .map(|b| b.unwrap_or([0.0, 0.0, 0.0, 0.0])) 156 + .collect() 133 157 } 134 158 135 159 #[allow(clippy::too_many_arguments)] ··· 149 173 match item { 150 174 typst::layout::FrameItem::Group(g) => { 151 175 walk_frame_for_shapes( 152 - &g.frame, source, blank_spans, preamble_offset, scale, ax, ay, boxes, 176 + &g.frame, 177 + source, 178 + blank_spans, 179 + preamble_offset, 180 + scale, 181 + ax, 182 + ay, 183 + boxes, 153 184 ); 154 185 } 155 186 typst::layout::FrameItem::Shape(shape, span) => { 156 - let Some(sr) = source.range(*span) else { continue }; 157 - if sr.end <= preamble_offset { continue; } 187 + let Some(sr) = source.range(*span) else { 188 + continue; 189 + }; 190 + if sr.end <= preamble_offset { 191 + continue; 192 + } 158 193 let frag_start = sr.start.saturating_sub(preamble_offset); 159 194 for (i, blank_span) in blank_spans.iter().enumerate() { 160 195 // Span-start-within check: catches math shapes whose node spans ··· 167 202 let ph = (bb.max.y - bb.min.y).to_pt() as f32 * scale; 168 203 let shape_rect = [px, py, pw, ph]; 169 204 if shape_rect[2] > 0.0 { 170 - boxes[i] = Some(boxes[i].map_or(shape_rect, |b| merge_rect(b, shape_rect))); 205 + boxes[i] = 206 + Some(boxes[i].map_or(shape_rect, |b| merge_rect(b, shape_rect))); 171 207 } 172 208 } 173 209 } ··· 218 254 math_spans: &[Range<usize>], 219 255 ) -> Vec<([f32; 4], Range<usize>, bool)> { 220 256 let mut map = Vec::new(); 221 - walk_frame_for_glyphs(frame, source, preamble_offset, scale, 0.0, 0.0, math_spans, &mut map); 257 + walk_frame_for_glyphs( 258 + frame, 259 + source, 260 + preamble_offset, 261 + scale, 262 + 0.0, 263 + 0.0, 264 + math_spans, 265 + &mut map, 266 + ); 222 267 map 223 268 } 224 269 ··· 239 284 match item { 240 285 typst::layout::FrameItem::Group(g) => { 241 286 walk_frame_for_glyphs( 242 - &g.frame, source, preamble_offset, scale, ax, ay, math_spans, map, 287 + &g.frame, 288 + source, 289 + preamble_offset, 290 + scale, 291 + ax, 292 + ay, 293 + math_spans, 294 + map, 243 295 ); 244 296 } 245 297 typst::layout::FrameItem::Text(text) => { ··· 268 320 // Use actual per-glyph font metrics instead of the 0.8*fs 269 321 // cap-height approximation, which is wildly wrong for math 270 322 // symbols like the radical sign (ascent ≈ 0.2pt, not 8.8pt). 271 - let (py, ph) = if let Some(bb) = ttf.glyph_bounding_box( 272 - ttf_parser::GlyphId(glyph.id) 273 - ) { 274 - let ascent = bb.y_max as f32 / upm * fs; 323 + let (py, ph) = if let Some(bb) = 324 + ttf.glyph_bounding_box(ttf_parser::GlyphId(glyph.id)) 325 + { 326 + let ascent = bb.y_max as f32 / upm * fs; 275 327 let descent = -bb.y_min as f32 / upm * fs; 276 328 ((ay - ascent) * scale, (ascent + descent) * scale) 277 329 } else { ··· 482 534 use super::*; 483 535 484 536 const FRONT_BACK: &str = r#"#card[What is $E = m c^2$?][Einstein's mass--energy relation.]"#; 485 - const CLOZE: &str = r#"#cloze[Resonance occurs where #blank[inductance] cancels #blank[capacitance].]"#; 537 + const CLOZE: &str = 538 + r#"#cloze[Resonance occurs where #blank[inductance] cancels #blank[capacitance].]"#; 486 539 487 540 #[test] 488 541 fn render_authoring_front_back() { ··· 528 581 let result = render(&tmp, CLOZE, Preamble::Authoring, &spans).unwrap(); 529 582 assert_eq!(result.blank_boxes.len(), 2); 530 583 // Both boxes should have non-zero width (glyphs found). 531 - assert!(result.blank_boxes[0][2] > 0.0, "inductance box has no width"); 532 - assert!(result.blank_boxes[1][2] > 0.0, "capacitance box has no width"); 584 + assert!( 585 + result.blank_boxes[0][2] > 0.0, 586 + "inductance box has no width" 587 + ); 588 + assert!( 589 + result.blank_boxes[1][2] > 0.0, 590 + "capacitance box has no width" 591 + ); 533 592 // Inductance should be to the left of capacitance (same line, earlier in text). 534 593 assert!( 535 594 result.blank_boxes[0][0] < result.blank_boxes[1][0],
+5 -1
crates/tala/src/main.rs
··· 18 18 let path = config_dir_file()?; 19 19 let s = std::fs::read_to_string(path).ok()?; 20 20 let p = PathBuf::from(s.trim()); 21 - if p.is_dir() { Some(p) } else { None } 21 + if p.is_dir() { 22 + Some(p) 23 + } else { 24 + None 25 + } 22 26 } 23 27 24 28 fn persist_dir(path: &Path) {