1use super::basic::{center, number_to_braille, python_lines, yield_dots};
4use super::lookup::symbol;
5use crate::error::{Error, Result};
6
7#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct BrailleTextLine {
11 pub is_heading: bool,
13 pub contains_note_grouping: bool,
15 pub line_length: usize,
17 cells: Vec<String>,
18 pub text_location: usize,
20 pub highest_used_location: usize,
22}
23
24fn refuse(message: &str) -> Error {
25 Error::Notation(message.to_string())
26}
27
28impl BrailleTextLine {
29 pub fn new(line_length: usize) -> Self {
31 Self {
32 is_heading: false,
33 contains_note_grouping: false,
34 line_length,
35 cells: vec![symbol("space"); line_length],
36 text_location: 0,
37 highest_used_location: 0,
38 }
39 }
40
41 pub fn append(&mut self, text: &str, add_space: bool) -> Result<()> {
48 if !self.can_append(text, add_space) {
49 return Err(refuse("Text does not fit at end of braille text line."));
50 }
51 if add_space {
52 self.cells[self.text_location] = symbol("space");
53 self.text_location += 1;
54 }
55 for cell in text.chars() {
56 self.cells[self.text_location] = cell.to_string();
57 self.text_location += 1;
58 }
59 self.highest_used_location = self.text_location;
60 Ok(())
61 }
62
63 pub fn insert(&mut self, location: usize, text: &str) -> Result<()> {
70 if !self.can_insert(location, text) {
71 return Err(refuse("Text cannot be inserted at specified location."));
72 }
73 for (at, cell) in (location..).zip(text.chars()) {
74 self.cells[at] = cell.to_string();
75 }
76 self.text_location = location + text.chars().count();
77 self.highest_used_location = self.highest_used_location.max(self.text_location);
78 Ok(())
79 }
80
81 pub fn can_append(&self, text: &str, add_space: bool) -> bool {
84 let search = self.highest_used_location.max(self.text_location);
85 search + text.chars().count() + usize::from(add_space) <= self.line_length
86 }
87
88 pub fn can_insert(&self, location: usize, text: &str) -> bool {
90 location + text.chars().count() <= self.line_length
91 }
92
93 pub fn last_hyphen_to_space(&mut self) {
96 let Some(previous) = self.text_location.checked_sub(1) else {
97 return;
98 };
99 if self.cells[previous] == symbol("music_hyphen") {
100 self.cells[previous] = symbol("space");
101 self.text_location -= 1;
102 }
103 }
104
105 pub fn cells(&self) -> &[String] {
108 &self.cells
109 }
110
111 pub fn text(&self) -> String {
113 self.cells[..self.text_location].concat()
114 }
115}
116
117#[derive(Clone, Debug, PartialEq, Eq)]
120pub struct BrailleText {
121 pub line_length: usize,
123 pub all_lines: Vec<BrailleTextLine>,
125 pub right_hand_symbol: bool,
127 pub left_hand_symbol: bool,
129 pub all_headings: Vec<(usize, usize)>,
131 show_hand: Option<&'static str>,
132}
133
134impl BrailleText {
135 pub fn new(line_length: usize, show_hand: Option<&str>) -> Result<Self> {
142 let mut text = Self {
143 line_length,
144 all_lines: Vec::new(),
145 right_hand_symbol: false,
146 left_hand_symbol: false,
147 all_headings: Vec::new(),
148 show_hand: None,
149 };
150 text.make_new_line();
151 text.set_show_hand(show_hand)?;
152 Ok(text)
153 }
154
155 pub fn set_show_hand(&mut self, hand: Option<&str>) -> Result<()> {
162 self.show_hand = match hand {
163 Some("right") => Some("right"),
164 Some("left") => Some("left"),
165 None => None,
166 Some(_) => return Err(refuse("Illegal hand sign request.")),
167 };
168 self.right_hand_symbol = hand == Some("right");
169 self.left_hand_symbol = hand == Some("left");
170 Ok(())
171 }
172
173 pub fn show_hand(&self) -> Option<&'static str> {
175 self.show_hand
176 }
177
178 pub fn current_line(&mut self) -> &mut BrailleTextLine {
180 self.all_lines.last_mut().expect("a text always has a line")
181 }
182
183 pub fn make_new_line(&mut self) {
185 self.all_lines.push(BrailleTextLine::new(self.line_length));
186 }
187
188 pub fn add_heading(&mut self, heading: &str) -> Result<()> {
194 if self.current_line().text_location != 0 {
195 self.make_new_line();
196 }
197 let start = self.all_lines.len() - 1;
198 let mut end = start;
199 for line in python_lines(heading) {
200 let current = self.current_line();
201 current.is_heading = true;
202 current.append(&line, false)?;
203 self.make_new_line();
204 end += 1;
205 }
206 self.all_headings.push((start, end));
207 Ok(())
208 }
209
210 pub fn add_long_expression(&mut self, expression: &str) -> Result<()> {
217 for word in expression.split(&symbol("space")) {
218 self.append_or_insert_current(word, true)?;
219 }
220 Ok(())
221 }
222
223 pub fn add_to_new_line(&mut self, grouping: &str) -> Result<()> {
230 self.make_new_line();
231 if self.right_hand_symbol || self.left_hand_symbol {
232 self.optional_add_keyboard_symbols_and_dots(Some(grouping))?;
233 self.current_line().append(grouping, false)
234 } else {
235 self.current_line().insert(2, grouping)
236 }
237 }
238
239 pub fn append_or_insert_current(&mut self, text: &str, add_space: bool) -> Result<()> {
246 if self.current_line().can_append(text, add_space) {
247 self.current_line().append(text, add_space)
248 } else {
249 self.make_new_line();
250 self.current_line().insert(2, text)
251 }
252 }
253
254 pub fn add_inaccord(&mut self, inaccord: &str) -> Result<()> {
261 let add_space = self.optional_add_keyboard_symbols_and_dots(Some(inaccord))?;
262 if self.current_line().append(inaccord, add_space).is_err() {
263 self.make_new_line();
264 if self.right_hand_symbol || self.left_hand_symbol {
265 let sign = symbol(if self.right_hand_symbol {
266 "rh_keyboard"
267 } else {
268 "lh_keyboard"
269 });
270 self.current_line().insert(2, &sign)?;
271 if let Some(first) = inaccord.chars().next() {
272 for dot in yield_dots(first) {
273 self.current_line().append(&dot, false)?;
274 }
275 }
276 self.current_line().append(inaccord, false)?;
277 } else {
278 self.current_line().insert(2, inaccord)?;
279 }
280 }
281 self.current_line().contains_note_grouping = true;
282 Ok(())
283 }
284
285 pub fn add_measure_number(&mut self, number: &str) -> Result<()> {
292 if self.current_line().text_location != 0 {
293 self.make_new_line();
294 }
295 self.current_line().append(number, false)
296 }
297
298 pub fn add_measure_number_value(&mut self, number: i64) -> Result<()> {
304 let braille = number_to_braille(&number.to_string(), true, false)?;
305 self.add_measure_number(&braille)
306 }
307
308 pub fn optional_add_keyboard_symbols_and_dots(
316 &mut self,
317 grouping: Option<&str>,
318 ) -> Result<bool> {
319 let mut add_space = true;
320 let (right, left) = (self.right_hand_symbol, self.left_hand_symbol);
321 let line = self.current_line();
322 if !line.contains_note_grouping && (right || left) {
323 if line.text_location == 0 {
324 add_space = false;
325 }
326 let sign = symbol(if right { "rh_keyboard" } else { "lh_keyboard" });
327 line.append(&sign, add_space)?;
328 if let Some(first) = grouping.and_then(|grouping| grouping.chars().next()) {
329 for dot in yield_dots(first) {
330 line.append(&dot, false)?;
331 }
332 }
333 add_space = false;
334 }
335 if line.text_location == 0 {
336 add_space = false;
337 }
338 Ok(add_space)
339 }
340
341 pub fn add_signatures(&mut self, signatures: &str) -> Result<()> {
348 let add_space = self.current_line().text_location != 0;
349 self.append_or_insert_current(signatures, add_space)
350 }
351
352 pub fn recenter_headings(&mut self) {
355 let space = symbol("space");
356 for (start, end) in self.all_headings.clone() {
357 let mut widest = 0;
358 for line in &self.all_lines[end..] {
359 if line.is_heading {
360 break;
361 }
362 widest = widest.max(line.text_location);
363 }
364 for line in &mut self.all_lines[start..end] {
365 let text = line.text();
366 let trimmed = text.trim_matches(|cell: char| cell.to_string() == space);
367 if widest > trimmed.chars().count() {
368 let centred = center(trimmed, widest, &space);
369 let _ = line.insert(0, ¢red);
371 line.text_location = widest;
372 }
373 }
374 }
375 }
376
377 pub fn render(&mut self) -> String {
379 self.recenter_headings();
380 self.all_lines
381 .iter()
382 .map(BrailleTextLine::text)
383 .collect::<Vec<_>>()
384 .join("\n")
385 }
386}
387
388#[derive(Clone, Debug, PartialEq, Eq)]
391pub struct BrailleKeyboard {
392 pub text: BrailleText,
394 pub right_hand_line: Option<usize>,
396 pub left_hand_line: Option<usize>,
398 pub highest_measure_number_length: usize,
401}
402
403impl BrailleKeyboard {
404 pub fn new(line_length: usize) -> Self {
406 Self {
407 text: BrailleText::new(line_length, None).expect("no hand is always allowed"),
408 right_hand_line: None,
409 left_hand_line: None,
410 highest_measure_number_length: 0,
411 }
412 }
413
414 pub fn make_new_lines(&mut self) {
417 let length = self.text.line_length;
418 if self.text.current_line().text_location == 0 {
419 self.right_hand_line = Some(self.text.all_lines.len() - 1);
420 } else {
421 self.text.all_lines.push(BrailleTextLine::new(length));
422 self.right_hand_line = Some(self.text.all_lines.len() - 1);
423 }
424 self.text.all_lines.push(BrailleTextLine::new(length));
425 self.left_hand_line = Some(self.text.all_lines.len() - 1);
426 }
427
428 fn lines(&mut self) -> (&mut BrailleTextLine, &mut BrailleTextLine) {
429 let right = self.right_hand_line.expect("the hands have lines");
430 let left = self.left_hand_line.expect("the hands have lines");
431 assert!(right < left, "the right hand's line is above the left's");
432 let (above, below) = self.text.all_lines.split_at_mut(left);
433 (&mut above[right], &mut below[0])
434 }
435
436 pub fn add_note_groupings(
444 &mut self,
445 measure_number: &str,
446 right: &str,
447 left: &str,
448 ) -> Result<()> {
449 if self.right_hand_line.is_none() && self.left_hand_line.is_none() {
450 self.make_new_lines();
451 }
452 let indent = self
453 .highest_measure_number_length
454 .saturating_sub(measure_number.chars().count());
455 let (right_line, left_line) = self.lines();
456 if right_line.text_location == 0 {
457 right_line.insert(indent, measure_number)?;
458 left_line.text_location = right_line.text_location;
459 }
460 let mut add_space = true;
461 if !right_line.contains_note_grouping {
462 add_space = false;
463 right_line.append(&symbol("rh_keyboard"), true)?;
464 left_line.append(&symbol("lh_keyboard"), true)?;
465 if let Some(first) = right.chars().next() {
466 for dot in yield_dots(first) {
467 right_line.append(&dot, false)?;
468 }
469 }
470 if let Some(first) = left.chars().next() {
471 for dot in yield_dots(first) {
472 left_line.append(&dot, false)?;
473 }
474 }
475 }
476 if right_line.can_append(right, add_space) && left_line.can_append(left, add_space) {
477 if !left.is_empty() {
478 left_line.append(left, add_space)?;
479 }
480 if !right.is_empty() {
481 right_line.append(right, add_space)?;
482 }
483 } else {
484 self.make_new_lines();
485 let (right_line, left_line) = self.lines();
486 right_line.insert(indent, measure_number)?;
487 left_line.text_location = right_line.text_location;
488 right_line.append(&symbol("rh_keyboard"), true)?;
489 left_line.append(&symbol("lh_keyboard"), true)?;
490 if let Some(first) = right.chars().next() {
491 for dot in yield_dots(first) {
492 right_line.append(&dot, false)?;
493 }
494 right_line.append(right, false)?;
495 }
496 if let Some(first) = left.chars().next() {
497 for dot in yield_dots(first) {
498 left_line.append(&dot, false)?;
499 }
500 left_line.append(left, false)?;
501 }
502 }
503 let (right_line, left_line) = self.lines();
504 if right_line.text_location > left_line.text_location {
505 left_line.text_location = right_line.text_location;
506 } else {
507 right_line.text_location = left_line.text_location;
508 }
509 right_line.contains_note_grouping = true;
510 left_line.contains_note_grouping = true;
511 Ok(())
512 }
513}
514
515#[cfg(test)]
516mod tests {
517 use super::*;
518
519 #[test]
520 fn lines_take_what_fits_and_headings_are_centred() -> Result<()> {
521 let mut text = BrailleText::new(10, None)?;
522 text.add_heading("⠁⠃")?;
523 text.add_measure_number("⠼⠁")?;
524 text.append_or_insert_current("⠉⠙⠑⠋", true)?;
525 text.append_or_insert_current("⠛⠓⠊⠚", true)?;
526 assert_eq!(text.render(), "⠀⠀⠀⠁⠃⠀⠀\n⠼⠁⠀⠉⠙⠑⠋\n⠀⠀⠛⠓⠊⠚");
527 Ok(())
528 }
529}