#![allow(non_snake_case)]
use pyo3::prelude::*;
use std::path::PathBuf;
pub use ufo_compile::*;
pub mod ufo_compile {
use super::*;
#[pyclass(module = "export")]
#[derive(Debug, Clone, Copy)]
pub enum OutputFormat {
Otf,
Ttf,
}
#[pyclass]
#[derive(Debug, Clone)]
pub struct UFOCompileOptions {
#[pyo3(get, set)]
input_dir: PathBuf,
#[pyo3(get, set)]
output_dir: PathBuf,
#[pyo3(get, set)]
format: OutputFormat,
#[pyo3(get, set)]
filename_stem: Option<String>,
#[pyo3(get, set)]
output_path: Option<PathBuf>,
}
impl Default for UFOCompileOptions {
fn default() -> Self {
Self::new()
}
}
#[pymethods]
impl UFOCompileOptions {
#[new]
fn new_python() -> Self {
Self {
input_dir: PathBuf::new(),
output_dir: PathBuf::new(),
format: OutputFormat::Otf,
filename_stem: None,
output_path: None,
}
}
}
macro_rules! gen_setter {
($($field_name:ident: $t:ty),* $(,)?) => {
$(pub fn $field_name(mut self, value: $t) -> Self {
self.$field_name = value;
self
})*
};
}
impl UFOCompileOptions {
pub fn new() -> Self {
Self {
input_dir: PathBuf::new(),
output_dir: PathBuf::new(),
format: OutputFormat::Otf,
filename_stem: None,
output_path: None,
}
}
gen_setter! {
input_dir: PathBuf,
output_dir: PathBuf,
format: OutputFormat,
filename_stem: Option<String>,
output_path: Option<PathBuf>,
}
}
const FUNC: &str = include_str!("export.py");
pub fn export(options: UFOCompileOptions) -> Result<PathBuf, Box<dyn std::error::Error>> {
let res: PyResult<PathBuf> = Python::with_gil(|py| {
let module = PyModule::from_code(py, FUNC, "export.py", "export")?;
module.add_class::<OutputFormat>()?;
let filename: &PyAny = module
.call_method1("export", (Py::new(py, options)?.into_ref(py),))?
.extract()?;
filename.extract()
});
Ok(res?)
}
pub fn export_action_cb(
app: &crate::app::Application,
window: gtk::Window,
project: crate::prelude::Project,
) {
use crate::prelude::*;
const OPEN_FOLDER: gtk::ResponseType = gtk::ResponseType::Other(0);
const OPEN_ARTIFACT: gtk::ResponseType = gtk::ResponseType::Other(1);
let input_dir = project.path.borrow().clone();
let filename_stem = project.property::<Option<String>>(Project::FILENAME_STEM);
let otf_filter = gtk::FileFilter::new();
otf_filter.add_pattern("*.otf");
otf_filter.set_name(Some("OpenType (.otf)"));
let ttf_filter = gtk::FileFilter::new();
ttf_filter.add_pattern("*.ttf");
ttf_filter.set_name(Some("TrueType (.ttf)"));
let filechooser = gtk::FileChooserNative::builder()
.accept_label("Export")
.create_folders(true)
.do_overwrite_confirmation(true)
.title("Select output path")
.action(gtk::FileChooserAction::Save)
.transient_for(&window)
.build();
filechooser.add_filter(&otf_filter);
filechooser.add_filter(&ttf_filter);
filechooser.set_filter(&otf_filter);
_ = filechooser.add_shortcut_folder(&input_dir);
filechooser.set_current_folder(&input_dir);
if let Some(f) = filename_stem.as_ref() {
filechooser.set_filename(&format!("{f}.otf"));
}
return_if_not_ok_or_accept!(filechooser.run());
let Some(f) = filechooser.filename() else { return; };
let Some(output_dir) = f.to_str() else { return; };
let (output_path, format) = if let Some(path) = filechooser.filename() {
let filter = |p: &PathBuf| !p.is_dir();
match filechooser.filter().as_ref() {
Some(f) if f == &ttf_filter => (Some(path).filter(filter), OutputFormat::Ttf),
Some(f) if f == &otf_filter => (Some(path).filter(filter), OutputFormat::Otf),
other => unreachable!("{other:?}"),
}
} else {
match filechooser.filter().as_ref() {
Some(f) if f == &ttf_filter => (None, OutputFormat::Ttf),
Some(f) if f == &otf_filter => (None, OutputFormat::Otf),
other => unreachable!("{other:?}"),
}
};
filechooser.hide();
match export(
UFOCompileOptions::new()
.input_dir(input_dir)
.output_dir(output_dir.into())
.format(format)
.filename_stem(filename_stem)
.output_path(output_path),
) {
Ok(result_path) => {
let title = match format {
OutputFormat::Otf => "Exported OTF artifact.",
OutputFormat::Ttf => "Exported TTF artifact.",
};
let folder_uri = result_path
.parent()
.and_then(|p| glib::filename_to_uri(p, None).ok())
.as_ref()
.map(glib::GString::to_string);
let file_uri = glib::filename_to_uri(&result_path, None)
.ok()
.as_ref()
.map(glib::GString::to_string);
let body = if let Some(ref folder_uri) = folder_uri {
format!(
"Project was exported successfully to\n<tt><a href=\"{}\">{}</a></tt>",
folder_uri,
result_path.display()
)
} else {
format!(
"Project was exported successfully to\n<tt>{}</tt>",
result_path.display()
)
};
let notif = gio::Notification::new(title);
notif.set_body(Some(&body));
app.send_notification(None, ¬if);
let dialog = crate::utils::widgets::new_simple_info_dialog(
Some(title),
&body,
None,
&window,
);
dialog.add_button("Open folder", OPEN_FOLDER);
dialog.add_button(
match format {
OutputFormat::Otf => "Open OTF file",
OutputFormat::Ttf => "Open TTF file",
},
OPEN_ARTIFACT,
);
loop {
match dialog.run() {
response if matches!(response, OPEN_FOLDER) => {
if let Some(uri) = folder_uri.as_ref() {
gtk::gio::AppInfo::launch_default_for_uri(
uri,
gtk::gio::AppLaunchContext::NONE,
)
.unwrap();
}
}
response if matches!(response, OPEN_ARTIFACT) => {
if let Some(uri) = file_uri.as_ref() {
gtk::gio::AppInfo::launch_default_for_uri(
uri,
gtk::gio::AppLaunchContext::NONE,
)
.unwrap();
}
}
_ => break,
}
}
dialog.emit_close();
}
Err(err) => {
let dialog = crate::utils::widgets::new_simple_error_dialog(
Some("Error: could not perform export"),
&err.to_string(),
None,
&window,
);
dialog.run();
dialog.emit_close();
}
}
}
}