sg_sprite/
lib.rs

1#![allow(dead_code, unused_imports)]
2
3use lazy_format::lazy_format;
4use std::env;
5use std::fmt::Display;
6use std::format as fmt;
7use std::path::{Path, PathBuf};
8use std::fs::{File, DirEntry};
9use std::io;
10use structopt::StructOpt;
11
12mod dep;
13mod draw;
14mod parse;
15mod util;
16
17use dep::*;
18use draw::*;
19use parse::*;
20use util::*;
21
22pub type SgSpriteErr = anyhow::Error;
23use anyhow::{anyhow as raise_e, bail as raise};
24
25pub fn print_err(e: impl Display) {
26    eprintln!("[E] {}", e);
27}
28
29#[derive(StructOpt, Debug)]
30#[structopt()]
31pub struct Opts {
32    /// Output dir
33    #[structopt(short, long, parse(from_os_str))]
34    pub dir: Option<PathBuf>,
35
36    /// Limit variants to draw per sprite
37    #[structopt(short, long)]
38    pub limit: Option<usize>,
39
40    /// .lay files to parse
41    #[structopt(name = "LAY_FILES", parse(from_os_str))]
42    pub lay_files: Vec<PathBuf>,
43
44    /// Perform parsing only to test for errors.
45    /// Do not compose actual images
46    #[structopt(long)]
47    pub dry_run: bool,
48}
49
50const LAY_EXT: &[&str] = &["_.lay", ".lay"];
51
52pub fn lib_main(o: &Opts) -> Result<(), SgSpriteErr> {
53    let layouts = &o.lay_files;
54    let out_dir = o.dir.as_ref();
55
56    match out_dir {
57        Some(d) if !d.is_dir() => raise!("out_dir isn't a directory"),
58        None if !o.dry_run => {
59            raise!("Output dir should be specified (-d)\nSee --help for details");
60        }
61        _ => (),
62    }
63
64    let total = o.lay_files.len();
65
66    if total == 0 {
67        raise!("no .lay files provided");
68    }
69
70    let status = |c: usize| move |t: &str| println!("[{}/{}] {}", c + 1, total, t);
71
72    for i in 0..layouts.len() {
73        let lay_path = &layouts[i];
74        if let Err(e) = lay_in(out_dir, lay_path, o.limit, status(i)) {
75            print_err(e);
76            print_err(format_args!("({})", lay_path.display()));
77        }
78    }
79
80    Ok(())
81}
82
83fn lay_in(
84    out_dir: Option<impl AsRef<Path>>,
85    lay_file: &Path,
86    limit: Option<usize>,
87    status_cb: impl Fn(&str),
88) -> Result<(), SgSpriteErr> {
89    let limit = limit.unwrap_or(0);
90
91    let (lay_filename, lay_ext) = lay_file
92        .file_name()
93        .and_then(|n| n.to_str())
94        .and_then(|f| LAY_EXT.iter().find(|e| f.ends_with(**e)).map(|e| (f, e)))
95        .ok_or_else(|| raise_e!("not a lay file"))?;
96
97    let sprite_name =
98        lay_filename.trim_end_matches(lay_ext);
99
100    status_cb(sprite_name);
101
102    let lay = parse_lay(&mut File::open(lay_file)?)?;
103    let graph = DepGraph::resolve_dep_graph(&lay);
104    let leaves = graph.get_leaf_sprites();
105
106    if let Some(out_dir) = out_dir {
107        let src_image_path: PathBuf = {
108            let mut path_buf = lay_file.canonicalize().expect("Can't canonicalize .lay path");
109            let parent_dir = path_buf.parent().expect("No parent dir");
110            // bail out right away if there are problems with path resolution
111
112            let src_filename = parent_dir.read_dir()
113                .unwrap()
114                .flatten()
115                .find(|f|
116                    f.file_name().to_str()
117                        .map(|n| n.starts_with(sprite_name) && n.ends_with(".png"))
118                        .unwrap_or(false)
119                )
120                .ok_or_else(|| raise_e!("No corresponding png file"))?;
121
122            path_buf.pop();
123            path_buf.push(src_filename.file_name());
124            path_buf
125        };
126
127        println!("[I] Using source file: {}",
128                 src_image_path.file_name().unwrap().to_str().unwrap_or("???"));
129
130        let mut src_image = draw_prep(&src_image_path)?;
131
132        for (pass, sp) in leaves.enumerate() {
133            if limit > 0 && pass >= limit {
134                eprintln!("[I] Limit reached, proceeding to next sprite");
135                break;
136            }
137
138            let s = sp.sprite;
139            let name_suf = lazy_format!(match (s.sprite_type) {
140                SpriteT::Base => ("b{}", s.id),
141                SpriteT::Sub => ("s{}", s.id),
142                SpriteT::Overlay => ("o{}", s.id),
143                SpriteT::Dep { exact_type: st, depends_on: dep } => ("t{}_d{}_{}", st, dep, s.id),
144            });
145
146            let layers = graph.resolve_layers(sp)?;
147            let mut out_path = out_dir.as_ref().to_path_buf();
148            out_path.push(fmt!("{}_{}.png", sprite_name, name_suf));
149
150            if let Err(e) = draw_sprites(&mut src_image, &out_path, layers.as_ref(), pass + 1, &lay) {
151                print_err(e);
152            }
153        }
154    } else {
155        for sp in leaves {
156            graph.resolve_layers(sp)?; // validation resolve for --dry-run
157        }
158    }
159
160    Ok(())
161}