1 | 1 |
extern crate cairo;
|
2 | 2 |
#[macro_use] extern crate failure;
|
3 | |
extern crate heck;
|
4 | 3 |
extern crate pango;
|
5 | 4 |
extern crate pangocairo;
|
6 | 5 |
extern crate rrecutils;
|
7 | 6 |
extern crate rsvg;
|
8 | 7 |
|
9 | 8 |
use std::{fs,io};
|
10 | |
use heck::TitleCase;
|
11 | 9 |
use pango::LayoutExt;
|
12 | 10 |
use rsvg::HandleExt;
|
13 | 11 |
|
|
42 | 40 |
},
|
43 | 41 |
}
|
44 | 42 |
|
45 | |
#[derive(Debug)]
|
| 43 |
#[derive(Debug, Clone, Copy)]
|
46 | 44 |
enum Kind { Ability, Equipment, Item, Plant, Treasure }
|
47 | 45 |
|
48 | |
#[derive(Debug)]
|
| 46 |
#[derive(Debug, Clone, Copy)]
|
49 | 47 |
enum WeaponType { Melee, Ranged, Arcane }
|
50 | 48 |
|
51 | 49 |
impl<'a> Card<'a> {
|
|
53 | 51 |
-> Result<Card<'a>, failure::Error>
|
54 | 52 |
{
|
55 | 53 |
let name = rec.get("Name")?;
|
56 | |
|
57 | |
let card_type = match rec.get_type()? {
|
| 54 |
let typ = rec.get_type()?;
|
| 55 |
|
| 56 |
let card_type = match typ {
|
58 | 57 |
"Weapon" => CardType::Weapon {
|
59 | 58 |
weapon_type: match rec.get("Type")? {
|
60 | 59 |
"melee" => WeaponType::Melee,
|
|
88 | 87 |
};
|
89 | 88 |
|
90 | 89 |
|
91 | |
let icon = format!("icon");
|
| 90 |
let lower_type = typ.to_lowercase();
|
| 91 |
let icon = format!(
|
| 92 |
"../icons/{}-icon.svg",
|
| 93 |
rec.get("Icon").unwrap_or(
|
| 94 |
match typ {
|
| 95 |
"Weapon" => rec.get("Type")?,
|
| 96 |
_ => &lower_type,
|
| 97 |
}
|
| 98 |
),
|
| 99 |
);
|
92 | 100 |
|
93 | 101 |
Ok(Card { name, icon, card_type })
|
94 | 102 |
}
|
95 | |
}
|
96 | |
|
97 | |
fn draw_card(rec: &rrecutils::Record) -> Result<(), failure::Error> {
|
98 | |
let typ = match rec.rec_type {
|
99 | |
Some(ref t) => t,
|
100 | |
None => return Ok(()),
|
101 | |
};
|
102 | |
let _ = Card::from_record(rec)?;
|
103 | |
let name = rec.get("Name")?;
|
| 103 |
|
| 104 |
fn type_name(&self) -> &'static str {
|
| 105 |
match self.card_type {
|
| 106 |
CardType::Weapon { .. } => "weapon",
|
| 107 |
CardType::Monster { .. } => "monster",
|
| 108 |
CardType::Generic { kind, .. } => match kind {
|
| 109 |
Kind::Ability => "ability",
|
| 110 |
Kind::Item => "item",
|
| 111 |
Kind::Equipment => "equipment",
|
| 112 |
Kind::Plant => "plant",
|
| 113 |
Kind::Treasure => "treasure",
|
| 114 |
},
|
| 115 |
}
|
| 116 |
}
|
| 117 |
|
| 118 |
fn card_name(&self) -> String {
|
| 119 |
self.name.chars().filter_map(|c| {
|
| 120 |
if c == ' '{
|
| 121 |
Some('_')
|
| 122 |
} else if c.is_alphabetic() {
|
| 123 |
Some(c.to_ascii_lowercase())
|
| 124 |
} else {
|
| 125 |
None
|
| 126 |
}
|
| 127 |
}).collect()
|
| 128 |
}
|
| 129 |
}
|
| 130 |
|
| 131 |
|
| 132 |
|
| 133 |
fn draw_card(card: &Card) -> Result<(), failure::Error> {
|
| 134 |
use CardType::*;
|
104 | 135 |
|
105 | 136 |
let surf = cairo::ImageSurface::create(
|
106 | 137 |
cairo::Format::Rgb24,
|
|
129 | 160 |
layout.set_font_description(&font);
|
130 | 161 |
|
131 | 162 |
ctx.move_to(100.0, 8.0);
|
132 | |
let title = match typ.as_ref() {
|
133 | |
"Weapon" => format!(
|
134 | |
"{} (Weapon, {})",
|
135 | |
name,
|
136 | |
rec.get("Type")?
|
137 | |
.to_title_case(),
|
138 | |
),
|
139 | |
"Ability" | "Equipment" | "Item" | "Plant" | "Treasure" =>
|
140 | |
format!("{} ({})", name, typ),
|
141 | |
"Monster" => {
|
| 163 |
let title = match card.card_type {
|
| 164 |
Weapon { ref weapon_type, .. } =>
|
| 165 |
format!("{} (Weapon, {:?})", card.name, weapon_type),
|
| 166 |
Monster { ref hp, ref melee, ref ranged, ref arcane, ref defense, .. } => {
|
142 | 167 |
let mut buf = String::new();
|
143 | |
let hp = rec.get("HP")?;
|
144 | |
buf.push_str(&format!("{} (Beast <span font='8' font_weight='heavy'>HP</span>{}", name, hp));
|
145 | |
if let Ok(m) = rec.get("Melee") {
|
| 168 |
buf.push_str(&format!(
|
| 169 |
"{} (Beast <span font='8' font_weight='heavy'>HP</span>{}",
|
| 170 |
card.name,
|
| 171 |
hp,
|
| 172 |
));
|
| 173 |
if let &Some(ref m) = melee {
|
146 | 174 |
buf.push_str(&format!(" <span font='8' font_weight='heavy'>M</span>{}", m));
|
147 | 175 |
}
|
148 | |
if let Ok(m) = rec.get("Ranged") {
|
| 176 |
if let &Some(ref m) = ranged {
|
149 | 177 |
buf.push_str(&format!(" <span font='8' font_weight='heavy'>R</span>{}", m));
|
150 | 178 |
}
|
151 | |
if let Ok(m) = rec.get("Arcane") {
|
| 179 |
if let &Some(ref m) = arcane {
|
152 | 180 |
buf.push_str(&format!(" <span font='8' font_weight='heavy'>A</span>{}", m));
|
153 | 181 |
}
|
154 | |
if let Ok(m) = rec.get("Defense") {
|
| 182 |
if let &Some(ref m) = defense {
|
155 | 183 |
buf.push_str(&format!(" <span font='8' font_weight='heavy'>D</span>{}", m));
|
156 | 184 |
}
|
157 | 185 |
buf.push_str(")");
|
158 | |
|
159 | 186 |
buf
|
160 | 187 |
}
|
161 | |
_ => bail!("Unknown type: {}", typ),
|
| 188 |
Generic { ref kind, .. } => format!("{} ({:?})", card.name, kind),
|
162 | 189 |
};
|
163 | 190 |
|
164 | 191 |
let (attrs, text, _) = pango::parse_markup(&title, '\0')?;
|
|
176 | 203 |
|
177 | 204 |
let (attrs, text, _) = {
|
178 | 205 |
let mut buf = String::new();
|
179 | |
match typ.as_ref() {
|
180 | |
"Weapon" => {
|
| 206 |
match card.card_type {
|
| 207 |
Weapon { ability, effect, .. } => {
|
181 | 208 |
buf.push_str("<span font_weight=\"bold\">");
|
182 | |
buf.push_str(rec.get("Ability")?);
|
| 209 |
buf.push_str(ability);
|
183 | 210 |
buf.push_str(":</span> ");
|
184 | |
buf.push_str(rec.get("Effect")?);
|
185 | |
}
|
186 | |
"Ability" | "Equipment" | "Item" | "Plant" | "Treasure" =>
|
187 | |
buf.push_str(rec.get("Effect")?),
|
188 | |
"Monster" => {
|
| 211 |
buf.push_str(effect);
|
| 212 |
},
|
| 213 |
Monster { action, effect, .. } => {
|
189 | 214 |
buf.push_str("<span font_weight=\"bold\">");
|
190 | |
buf.push_str(rec.get("Action")?);
|
| 215 |
buf.push_str(action);
|
191 | 216 |
buf.push_str(":</span> ");
|
192 | |
buf.push_str(rec.get("Effect")?);
|
193 | |
}
|
194 | |
_ => bail!("Unknown type: {}", typ),
|
| 217 |
buf.push_str(effect);
|
| 218 |
},
|
| 219 |
Generic { effect, .. } => {
|
| 220 |
buf.push_str(effect);
|
| 221 |
},
|
195 | 222 |
}
|
196 | 223 |
pango::parse_markup(&buf, '\0')?
|
197 | 224 |
};
|
|
200 | 227 |
layout.set_text(&text);
|
201 | 228 |
pangocairo::functions::show_layout(&ctx, &layout);
|
202 | 229 |
|
203 | |
let special_icon = rec.get("Icon").map(|i| format!("../icons/{}-icon.svg", i));
|
204 | |
let icon_path = special_icon.unwrap_or(format!(
|
205 | |
"../icons/{}-icon.svg",
|
206 | |
match typ.as_ref() {
|
207 | |
"Weapon" => rec.get("Type")?.to_owned(),
|
208 | |
_ => typ.to_lowercase(),
|
209 | |
},
|
210 | |
));
|
211 | |
let icon = rsvg::Handle::new_from_file(&icon_path)
|
212 | |
.map_err(|_| format_err!("Error loading image: {}", icon_path))?;
|
| 230 |
let icon = rsvg::Handle::new_from_file(&card.icon)
|
| 231 |
.map_err(|_| format_err!("Error loading image: {}", card.icon))?;
|
213 | 232 |
ctx.translate(10.0, 10.0);
|
214 | 233 |
ctx.scale(0.9, 0.9);
|
215 | 234 |
icon.render_cairo(&ctx);
|
216 | 235 |
|
217 | 236 |
|
218 | |
{
|
219 | |
let name = name.chars().filter_map(|c| {
|
220 | |
if c == ' ' {
|
221 | |
Some('_')
|
222 | |
} else if c.is_alphabetic() {
|
223 | |
Some(c.to_ascii_lowercase())
|
224 | |
} else {
|
225 | |
None
|
226 | |
}
|
227 | |
}).collect::<String>();
|
228 | |
let path = format!("cards/{}_{}.png", typ.to_lowercase(), name);
|
229 | |
let mut f = fs::File::create(&path)?;
|
230 | |
println!("Writing to {}", path);
|
231 | |
surf.write_to_png(&mut f)?;
|
232 | |
}
|
| 237 |
let path = format!("cards/{}_{}.png", card.type_name(), card.card_name());
|
| 238 |
let mut f = fs::File::create(&path)?;
|
| 239 |
println!("Writing to {}", path);
|
| 240 |
surf.write_to_png(&mut f)?;
|
233 | 241 |
|
234 | 242 |
Ok(())
|
235 | 243 |
}
|
|
239 | 247 |
let recfile = rrecutils::Recfile::parse(&mut f)?;
|
240 | 248 |
|
241 | 249 |
for w in recfile.iter() {
|
242 | |
draw_card(w)?;
|
| 250 |
if w.rec_type.is_some() {
|
| 251 |
let card = Card::from_record(w)?;
|
| 252 |
draw_card(&card)?;
|
| 253 |
}
|
243 | 254 |
}
|
244 | 255 |
|
245 | 256 |
Ok(())
|