1use std::{
16 fs::{write, File},
17 io::{read_to_string, Read},
18 path::{Path, PathBuf},
19};
20
21use anyhow::Result;
22pub use paperdoll;
23use paperdoll::{Manifest, PaperdollFactory};
24use tar::{Archive, Builder};
25use tempdir::TempDir;
26
27pub const EXTENSION_NAME: &'static str = "ppd";
29
30pub const FILE_NAME_MANIFEST: &'static str = "manifest.yml";
32
33const FILE_NAME_TEMP_DIR: &'static str = "paperdoll_ppd_";
34
35pub fn load<P>(path: P) -> Result<PaperdollFactory>
37where
38 P: AsRef<Path>,
39{
40 read(File::open(&path)?)
41}
42
43pub fn read<R>(r: R) -> Result<PaperdollFactory>
45where
46 R: Read,
47{
48 let mut archive = Archive::new(r);
49
50 let temp_dir = TempDir::new(FILE_NAME_TEMP_DIR)?;
51 archive.unpack(temp_dir.path())?;
52
53 let manifest_path = temp_dir.path().clone().join(FILE_NAME_MANIFEST);
54 let manifest_file = File::open(manifest_path)?;
55
56 let mut manifest: Manifest = serde_yaml::from_str(&read_to_string(manifest_file)?)?;
57
58 for doll in &mut manifest.dolls {
59 if doll.path.is_empty() {
60 continue;
61 }
62
63 let img_path = temp_dir.path().clone().join(&doll.path);
64 let img = image::open(img_path)?.into_rgba8();
65
66 doll.image.width = img.width();
67 doll.image.height = img.height();
68 doll.image.pixels = img.into_vec();
69 }
70
71 for fragment in &mut manifest.fragments {
72 if fragment.path.is_empty() {
73 continue;
74 }
75
76 let img_path = temp_dir.path().clone().join(&fragment.path);
77 let img = image::open(img_path)?.into_rgba8();
78
79 fragment.image.width = img.width();
80 fragment.image.height = img.height();
81 fragment.image.pixels = img.into_vec();
82 }
83
84 temp_dir.close()?;
85
86 PaperdollFactory::from_manifest(manifest)
87}
88
89pub fn save<P>(manifest: &mut Manifest, path: P) -> Result<()>
91where
92 P: AsRef<Path>,
93{
94 let temp_dir = TempDir::new(FILE_NAME_TEMP_DIR)?;
95
96 for doll in &mut manifest.dolls {
97 if doll.image.is_empty() {
98 continue;
99 }
100
101 let extension = (!doll.path.is_empty())
102 .then(|| {
103 PathBuf::from(&doll.path)
104 .extension()
105 .map(|ext| ext.to_string_lossy().to_string())
106 })
107 .flatten()
108 .map(|ext| {
109 if &ext == "webp" {
111 "png".to_owned()
112 } else {
113 ext
114 }
115 })
116 .unwrap_or("png".to_owned());
117
118 let filename = format!("doll_{}.{}", doll.id(), extension);
119
120 let img_path = temp_dir.path().clone().join(&filename);
121
122 doll.path = filename;
123
124 image::save_buffer(
125 img_path,
126 &doll.image.pixels,
127 doll.image.width,
128 doll.image.height,
129 image::ColorType::Rgba8,
130 )?;
131 }
132
133 for fragment in &mut manifest.fragments {
134 if fragment.image.is_empty() {
135 continue;
136 }
137
138 let extension = (!fragment.path.is_empty())
139 .then(|| {
140 PathBuf::from(&fragment.path)
141 .extension()
142 .map(|ext| ext.to_string_lossy().to_string())
143 })
144 .flatten()
145 .map(|ext| {
146 if &ext == "webp" {
148 "png".to_owned()
149 } else {
150 ext
151 }
152 })
153 .unwrap_or("png".to_owned());
154
155 let filename = format!("fragment_{}.{}", fragment.id(), extension);
156
157 let img_path = temp_dir.path().clone().join(&filename);
158
159 fragment.path = filename;
160
161 image::save_buffer(
162 img_path,
163 &fragment.image.pixels,
164 fragment.image.width,
165 fragment.image.height,
166 image::ColorType::Rgba8,
167 )?;
168 }
169
170 let manifest_str = serde_yaml::to_string(manifest)?;
171
172 let manifest_path = temp_dir.path().clone().join(FILE_NAME_MANIFEST);
173 write(manifest_path, manifest_str)?;
174
175 let output = File::create(path)?;
176
177 let mut archive = Builder::new(output);
178
179 archive.append_dir_all(".", temp_dir.path())?;
180
181 archive.finish()?;
182
183 temp_dir.close()?;
184
185 Ok(())
186}