font_imp.rs - icy_draw - icy_draw is the successor to mystic draw. fork / mirror
 (HTM) git clone https://git.drkhsh.at/icy_draw.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       font_imp.rs (17614B)
       ---
            1 use std::{fs, io::Read, path::Path, sync::Arc, thread};
            2 
            3 use crate::{AnsiEditor, Message, Settings};
            4 
            5 use super::{Event, MKey, MModifiers, Position, Tool};
            6 use eframe::{
            7     egui::{self, Button, RichText},
            8     epaint::{FontFamily, FontId},
            9 };
           10 use egui::mutex::Mutex;
           11 use i18n_embed_fl::fl;
           12 use icy_engine::{editor::OperationType, Size, TextPane, TheDrawFont};
           13 use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
           14 use walkdir::{DirEntry, WalkDir};
           15 pub struct FontTool {
           16     pub selected_font: Arc<Mutex<i32>>,
           17     pub fonts: Arc<Mutex<Vec<TheDrawFont>>>,
           18     pub sizes: Vec<Size>,
           19 }
           20 
           21 impl FontTool {
           22     /*pub fn get_selected_font(&self) -> Option<&TheDrawFont> {
           23         self.fonts.get(self.selected_font as usize)
           24     }*/
           25 
           26     pub(crate) fn is_hidden(entry: &DirEntry) -> bool {
           27         entry.file_name().to_str().map_or(false, |s| s.starts_with('.'))
           28     }
           29 
           30     pub fn install_watcher(&self) {
           31         if let Ok(tdf_dir) = Settings::get_tdf_diretory() {
           32             let fonts = self.fonts.clone();
           33             thread::spawn(move || loop {
           34                 if watch(tdf_dir.as_path(), &fonts).is_err() {
           35                     return;
           36                 }
           37             });
           38         }
           39     }
           40 
           41     pub fn load_fonts(&mut self) {
           42         if let Ok(tdf_dir) = Settings::get_tdf_diretory() {
           43             self.fonts = Arc::new(Mutex::new(load_fonts(tdf_dir.as_path())));
           44         }
           45     }
           46 }
           47 
           48 fn load_fonts(tdf_dir: &Path) -> Vec<TheDrawFont> {
           49     let mut fonts = Vec::new();
           50     let walker = WalkDir::new(tdf_dir).into_iter();
           51     for entry in walker.filter_entry(|e| !FontTool::is_hidden(e)) {
           52         if let Err(e) = entry {
           53             log::error!("Can't load tdf font library: {e}");
           54             break;
           55         }
           56         let Ok(entry) = entry else {
           57             continue;
           58         };
           59         let path = entry.path();
           60 
           61         if path.is_dir() {
           62             continue;
           63         }
           64         let extension = path.extension();
           65         if extension.is_none() {
           66             continue;
           67         }
           68         let Some(extension) = extension else {
           69             continue;
           70         };
           71         let extension = extension.to_str();
           72         let Some(extension) = extension else {
           73             continue;
           74         };
           75 
           76         let extension = extension.to_lowercase();
           77 
           78         if extension == "tdf" {
           79             if let Ok(loaded_fonts) = TheDrawFont::load(path) {
           80                 fonts.extend(loaded_fonts);
           81             }
           82         }
           83 
           84         if extension == "zip" {
           85             match fs::File::open(path) {
           86                 Ok(mut file) => {
           87                     let mut data = Vec::new();
           88                     file.read_to_end(&mut data).unwrap_or_default();
           89                     read_zip_archive(data, &mut fonts);
           90                 }
           91 
           92                 Err(err) => {
           93                     log::error!("Failed to open zip file: {}", err);
           94                 }
           95             }
           96         }
           97     }
           98     fonts
           99 }
          100 
          101 fn read_zip_archive(data: Vec<u8>, fonts: &mut Vec<TheDrawFont>) {
          102     let file = std::io::Cursor::new(data);
          103     match zip::ZipArchive::new(file) {
          104         Ok(mut archive) => {
          105             for i in 0..archive.len() {
          106                 match archive.by_index(i) {
          107                     Ok(mut file) => {
          108                         if let Some(name) = file.enclosed_name() {
          109                             if name.to_string_lossy().to_ascii_lowercase().ends_with(".tdf") {
          110                                 let mut data = Vec::new();
          111                                 file.read_to_end(&mut data).unwrap_or_default();
          112 
          113                                 if let Ok(loaded_fonts) = TheDrawFont::from_tdf_bytes(&data) {
          114                                     fonts.extend(loaded_fonts);
          115                                 }
          116                             } else if name.to_string_lossy().to_ascii_lowercase().ends_with(".zip") {
          117                                 let mut data = Vec::new();
          118                                 file.read_to_end(&mut data).unwrap_or_default();
          119                                 read_zip_archive(data, fonts);
          120                             }
          121                         }
          122                     }
          123                     Err(err) => {
          124                         log::error!("Error reading zip file: {}", err);
          125                     }
          126                 }
          127             }
          128         }
          129         Err(err) => {
          130             log::error!("Error reading zip archive: {}", err);
          131         }
          132     }
          133 }
          134 
          135 impl Tool for FontTool {
          136     fn get_icon(&self) -> &egui::Image<'static> {
          137         &super::icons::FONT_SVG
          138     }
          139 
          140     fn tool_name(&self) -> String {
          141         fl!(crate::LANGUAGE_LOADER, "tool-tdf_name")
          142     }
          143 
          144     fn tooltip(&self) -> String {
          145         fl!(crate::LANGUAGE_LOADER, "tool-tdf_tooltip")
          146     }
          147 
          148     fn use_selection(&self) -> bool {
          149         false
          150     }
          151 
          152     fn show_ui(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui, _editor_opt: Option<&mut AnsiEditor>) -> Option<Message> {
          153         let mut select = false;
          154         let font_count = self.fonts.lock().len();
          155         let selected_font = *self.selected_font.lock();
          156 
          157         ui.vertical_centered(|ui| {
          158             ui.label(fl!(crate::LANGUAGE_LOADER, "font_tool_current_font_label"));
          159 
          160             let mut selected_text = fl!(crate::LANGUAGE_LOADER, "font_tool_no_font");
          161 
          162             if selected_font >= 0 && (selected_font as usize) < font_count {
          163                 if let Some(font) = self.fonts.lock().get(selected_font as usize) {
          164                     selected_text = font.name.clone();
          165                 }
          166             }
          167             let selected_text = RichText::new(selected_text).font(FontId::new(18.0, FontFamily::Proportional));
          168             select = ui.add_enabled(font_count > 0, Button::new(selected_text)).clicked();
          169         });
          170 
          171         if font_count == 0 {
          172             ui.add_space(32.0);
          173             let mut msg = None;
          174             ui.vertical_centered(|ui| {
          175                 ui.label(fl!(crate::LANGUAGE_LOADER, "font_tool_no_fonts_label"));
          176                 if ui.button(fl!(crate::LANGUAGE_LOADER, "font_tool_open_directory_button")).clicked() {
          177                     msg = Some(Message::OpenTdfDirectory);
          178                 }
          179             });
          180             if msg.is_some() {
          181                 return msg;
          182             }
          183         }
          184 
          185         if selected_font >= 0 && (selected_font as usize) < font_count {
          186             ui.add_space(8.0);
          187             let left_border = 16.0;
          188             ui.vertical_centered(|ui| {
          189                 ui.horizontal(|ui| {
          190                     ui.add_space(left_border);
          191 
          192                     if let Some(font) = self.fonts.lock().get(selected_font as usize) {
          193                         for ch in '!'..'9' {
          194                             ui.spacing_mut().item_spacing = eframe::epaint::Vec2::new(0.0, 0.0);
          195                             let color = if font.has_char(ch as u8) {
          196                                 ui.style().visuals.strong_text_color()
          197                             } else {
          198                                 ui.style().visuals.text_color()
          199                             };
          200 
          201                             ui.colored_label(color, RichText::new(ch.to_string()).font(FontId::new(14.0, FontFamily::Monospace)));
          202                         }
          203                     }
          204                 });
          205 
          206                 ui.horizontal(|ui| {
          207                     ui.add_space(left_border);
          208 
          209                     if let Some(font) = self.fonts.lock().get(selected_font as usize) {
          210                         for ch in '9'..'Q' {
          211                             ui.spacing_mut().item_spacing = eframe::epaint::Vec2::new(0.0, 0.0);
          212                             let color = if font.has_char(ch as u8) {
          213                                 ui.style().visuals.strong_text_color()
          214                             } else {
          215                                 ui.style().visuals.text_color()
          216                             };
          217 
          218                             ui.colored_label(color, RichText::new(ch.to_string()).font(FontId::new(14.0, FontFamily::Monospace)));
          219                         }
          220                     }
          221                 });
          222 
          223                 ui.horizontal(|ui| {
          224                     ui.add_space(left_border);
          225                     if let Some(font) = self.fonts.lock().get(selected_font as usize) {
          226                         ui.spacing_mut().item_spacing = eframe::epaint::Vec2::new(0.0, 0.0);
          227                         for ch in 'Q'..'i' {
          228                             let color = if font.has_char(ch as u8) {
          229                                 ui.style().visuals.strong_text_color()
          230                             } else {
          231                                 ui.style().visuals.text_color()
          232                             };
          233 
          234                             ui.colored_label(color, RichText::new(ch.to_string()).font(FontId::new(14.0, FontFamily::Monospace)));
          235                         }
          236                     }
          237                 });
          238                 ui.horizontal(|ui| {
          239                     ui.add_space(left_border);
          240                     if let Some(font) = self.fonts.lock().get(selected_font as usize) {
          241                         ui.spacing_mut().item_spacing = eframe::epaint::Vec2::new(0.0, 0.0);
          242                         for ch in 'i'..='~' {
          243                             let color = if font.has_char(ch as u8) {
          244                                 ui.style().visuals.strong_text_color()
          245                             } else {
          246                                 ui.style().visuals.text_color()
          247                             };
          248 
          249                             ui.colored_label(color, RichText::new(ch.to_string()).font(FontId::new(14.0, FontFamily::Monospace)));
          250                         }
          251                     }
          252                 });
          253             });
          254         }
          255 
          256         if font_count > 0 {
          257             if let Some(font) = self.fonts.lock().get(selected_font as usize) {
          258                 if matches!(font.font_type, icy_engine::FontType::Outline) {
          259                     ui.add_space(32.0);
          260                     let mut msg = None;
          261                     ui.vertical_centered(|ui| {
          262                         if ui.button(fl!(crate::LANGUAGE_LOADER, "font_tool_select_outline_button")).clicked() {
          263                             msg = Some(Message::ShowOutlineDialog);
          264                         }
          265                     });
          266                     if msg.is_some() {
          267                         return msg;
          268                     }
          269                 }
          270             }
          271         }
          272 
          273         if select {
          274             Some(Message::SelectFontDialog(self.fonts.clone(), self.selected_font.clone()))
          275         } else {
          276             None
          277         }
          278     }
          279 
          280     fn handle_click(&mut self, editor: &mut AnsiEditor, button: i32, pos: Position, _pos_abs: Position, _response: &egui::Response) -> Option<Message> {
          281         if button == 1 {
          282             editor.set_caret_position(pos);
          283             editor.buffer_view.lock().clear_selection();
          284         }
          285         None
          286     }
          287 
          288     fn handle_hover(&mut self, _ui: &egui::Ui, response: egui::Response, _editor: &mut AnsiEditor, _cur: Position, _cur_abs: Position) -> egui::Response {
          289         response.on_hover_cursor(egui::CursorIcon::Text)
          290     }
          291 
          292     fn handle_key(&mut self, editor: &mut AnsiEditor, key: MKey, modifier: MModifiers) -> Event {
          293         let selected_font = *self.selected_font.lock();
          294 
          295         if selected_font < 0 || selected_font >= self.fonts.lock().len() as i32 {
          296             return Event::None;
          297         }
          298         let font = &self.fonts.lock()[selected_font as usize];
          299         let pos = editor.buffer_view.lock().get_caret().get_position();
          300 
          301         match key {
          302             MKey::Down => {
          303                 editor.set_caret(pos.x, pos.y + 1);
          304             }
          305             MKey::Up => {
          306                 editor.set_caret(pos.x, pos.y - 1);
          307             }
          308             MKey::Left => {
          309                 editor.set_caret(pos.x - 1, pos.y);
          310             }
          311             MKey::Right => {
          312                 editor.set_caret(pos.x + 1, pos.y);
          313             }
          314 
          315             MKey::Home => {
          316                 if let MModifiers::Control = modifier {
          317                     let end = editor.buffer_view.lock().get_buffer().get_width();
          318                     for i in 0..end {
          319                         if !editor.get_char_from_cur_layer(pos.with_x(i)).is_transparent() {
          320                             editor.set_caret(i, pos.y);
          321                             return Event::None;
          322                         }
          323                     }
          324                 }
          325                 editor.set_caret(0, pos.y);
          326             }
          327 
          328             MKey::End => {
          329                 if let MModifiers::Control = modifier {
          330                     let end = editor.buffer_view.lock().get_buffer().get_width();
          331                     for i in (0..end).rev() {
          332                         if !editor.get_char_from_cur_layer(pos.with_x(i)).is_transparent() {
          333                             editor.set_caret(i, pos.y);
          334                             return Event::None;
          335                         }
          336                     }
          337                 }
          338                 let w = editor.buffer_view.lock().get_buffer().get_width();
          339                 editor.set_caret(w - 1, pos.y);
          340             }
          341 
          342             MKey::Return => {
          343                 editor.set_caret(0, pos.y + font.get_font_height());
          344                 /*
          345                 if let Some(size) = self.sizes.last() {
          346                     editor.set_caret(0,pos.y + size.height as i32);
          347                 } else {
          348                     editor.set_caret(0,pos.y + 1);
          349                 }*/
          350                 self.sizes.clear();
          351             }
          352 
          353             MKey::Backspace => {
          354                 let mut use_backspace = true;
          355                 {
          356                     let mut render = false;
          357                     let mut reverse_count = 0;
          358 
          359                     let op = if let Ok(stack) = editor.buffer_view.lock().get_edit_state().get_undo_stack().lock() {
          360                         for i in (0..stack.len()).rev() {
          361                             match stack[i].get_operation_type() {
          362                                 OperationType::RenderCharacter => {
          363                                     if reverse_count == 0 {
          364                                         render = true;
          365                                         reverse_count = i;
          366                                         break;
          367                                     }
          368                                     reverse_count -= 1;
          369                                 }
          370                                 OperationType::ReversedRenderCharacter => {
          371                                     reverse_count += 1;
          372                                 }
          373                                 OperationType::Unknown => {
          374                                     render = false;
          375                                 }
          376                             }
          377                         }
          378                         if reverse_count < stack.len() {
          379                             stack[reverse_count].try_clone()
          380                         } else {
          381                             None
          382                         }
          383                     } else {
          384                         None
          385                     };
          386 
          387                     if render {
          388                         if let Some(op) = op {
          389                             let _ = editor.buffer_view.lock().get_edit_state_mut().push_reverse_undo(
          390                                 fl!(crate::LANGUAGE_LOADER, "undo-delete_character"),
          391                                 op,
          392                                 OperationType::ReversedRenderCharacter,
          393                             );
          394                             use_backspace = false;
          395                         }
          396                     }
          397                 }
          398 
          399                 if use_backspace {
          400                     editor.backspace();
          401                 }
          402             }
          403             MKey::Character(ch) => {
          404                 let c_pos = editor.get_caret_position();
          405                 let _undo = editor
          406                     .buffer_view
          407                     .lock()
          408                     .get_edit_state_mut()
          409                     .begin_typed_atomic_undo(fl!(crate::LANGUAGE_LOADER, "undo-render_character"), OperationType::RenderCharacter);
          410 
          411                 let outline_style = if editor.outline_font_mode {
          412                     usize::MAX
          413                 } else {
          414                     Settings::get_font_outline_style()
          415                 };
          416                 editor.buffer_view.lock().get_edit_state_mut().set_outline_style(outline_style);
          417 
          418                 let _ = editor.buffer_view.lock().get_edit_state_mut().undo_caret_position();
          419 
          420                 let opt_size: Option<Size> = font.render(editor.buffer_view.lock().get_edit_state_mut(), ch as u8);
          421                 if let Some(size) = opt_size {
          422                     editor.set_caret(c_pos.x + size.width + font.spaces, c_pos.y);
          423                     let new_pos = editor.get_caret_position();
          424                     self.sizes.push(Size {
          425                         width: (new_pos.x - c_pos.x),
          426                         height: size.height,
          427                     });
          428                 } else {
          429                     editor.type_key(unsafe { char::from_u32_unchecked(ch as u32) });
          430                     self.sizes.push(Size::new(1, 1));
          431                 }
          432             }
          433             _ => {}
          434         }
          435         Event::None
          436     }
          437 }
          438 
          439 fn watch(path: &Path, fonts: &Arc<Mutex<Vec<TheDrawFont>>>) -> notify::Result<()> {
          440     let (tx, rx) = std::sync::mpsc::channel();
          441 
          442     // Automatically select the best implementation for your platform.
          443     // You can also access each implementation directly e.g. INotifyWatcher.
          444     let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
          445 
          446     // Add a path to be watched. All files and directories at that path and
          447     // below will be monitored for changes.
          448     watcher.watch(path.as_ref(), RecursiveMode::Recursive)?;
          449 
          450     for res in rx {
          451         match res {
          452             Ok(_) => {
          453                 fonts.lock().clear();
          454                 fonts.lock().extend(load_fonts(path));
          455 
          456                 break;
          457             }
          458             Err(e) => log::error!("watch error: {e:}"),
          459         }
          460     }
          461 
          462     Ok(())
          463 }