gdritter repos delve / 58fd99c
Card generator for Riand-Zhar Getty Ritter 6 years ago
2 changed file(s) with 176 addition(s) and 2 deletion(s). Collapse all Expand all
99 png = "*"
1010 rand = "*"
1111 serde_json = "*"
12 failure = "*"
13 heck = "*"
14 rsvg = "*"
1215
1316 [dependencies.cairo-rs]
1417 version = "*"
1 extern crate cairo;
2 #[macro_use] extern crate failure;
3 extern crate heck;
4 extern crate pango;
5 extern crate pangocairo;
16 extern crate rrecutils;
7 extern crate rsvg;
8
9 use std::{fs,io};
10 use heck::TitleCase;
11 use pango::LayoutExt;
12 use rsvg::HandleExt;
13
14 const SCALE: i32 = 3;
15
16 fn draw_card(rec: &rrecutils::Record) -> Result<(), failure::Error> {
17 let typ = match rec.rec_type {
18 Some(ref t) => t,
19 None => return Ok(()),
20 };
21 let name = rec.get("Name")
22 .ok_or(format_err!("Weapon found with no Name!"))?;
23
24 let surf = cairo::ImageSurface::create(
25 cairo::Format::Rgb24,
26 4 * SCALE * 100,
27 1 * SCALE * 100,
28 ).map_err(|e| format_err!("Unable to create Cairo surface: {:?}", e))?;
29
30 let ctx = cairo::Context::new(&surf);
31 ctx.scale(SCALE as f64, SCALE as f64);
32 ctx.set_source_rgb(1.0, 1.0, 1.0);
33 ctx.paint();
34
35 ctx.set_source_rgb(0.56, 0.56, 0.56);
36 ctx.rectangle(10.0, 10.0, 80.0, 80.0);
37 ctx.stroke();
38
39 ctx.move_to(105.0, 30.0);
40 ctx.line_to(105.0, 90.0);
41 ctx.stroke();
42
43 ctx.set_source_rgb(0.0, 0.0, 0.0);
44 let layout = pangocairo::functions::create_layout(&ctx)
45 .ok_or(format_err!("Unable to create Pango layout"))?;
46 let mut font = pango::FontDescription::from_string("Fira Sans 12");
47 font.set_weight(pango::Weight::Bold);
48 layout.set_font_description(&font);
49
50 ctx.move_to(100.0, 8.0);
51 let title = match typ.as_ref() {
52 "Weapon" => format!(
53 "{} (Weapon, {})",
54 name,
55 rec.get("Type")
56 .ok_or(format_err!("Missing Type for {}", name))?
57 .to_title_case(),
58 ),
59 "Ability" | "Equipment" | "Item" | "Plant" | "Treasure" =>
60 format!("{} ({})", name, typ),
61 "Monster" => {
62 let mut buf = String::new();
63 let hp = rec.get("HP").ok_or(format_err!("Missing HP for {}", name))?;
64 buf.push_str(&format!("{} (Beast <span font='8' font_weight='heavy'>HP</span>{}", name, hp));
65 if let Some(m) = rec.get("Melee") {
66 buf.push_str(&format!(" <span font='8' font_weight='heavy'>M</span>{}", m));
67 }
68 if let Some(m) = rec.get("Ranged") {
69 buf.push_str(&format!(" <span font='8' font_weight='heavy'>R</span>{}", m));
70 }
71 if let Some(m) = rec.get("Arcane") {
72 buf.push_str(&format!(" <span font='8' font_weight='heavy'>A</span>{}", m));
73 }
74 if let Some(m) = rec.get("Defense") {
75 buf.push_str(&format!(" <span font='8' font_weight='heavy'>D</span>{}", m));
76 }
77 buf.push_str(")");
78
79 buf
80 }
81 _ => bail!("Unknown type: {}", typ),
82 };
83
84 let (attrs, text, _) = pango::parse_markup(&title, '\0')?;
85 layout.set_attributes(&attrs);
86 layout.set_text(&text);
87 pangocairo::functions::show_layout(&ctx, &layout);
88
89 ctx.move_to(115.0, 26.0);
90 let layout = pangocairo::functions::create_layout(&ctx)
91 .ok_or(format_err!("Unable to create Pango layout"))?;
92 layout.set_width((390 - 115) * pango::SCALE);
93 let mut font = pango::FontDescription::from_string("Fira Sans 10");
94 font.set_weight(pango::Weight::Normal);
95 layout.set_font_description(&font);
96
97 let (attrs, text, _) = {
98 let mut buf = String::new();
99 match typ.as_ref() {
100 "Weapon" => {
101 buf.push_str("<span font_weight=\"bold\">");
102 buf.push_str(rec.get("Ability")
103 .ok_or(format_err!("Missing Ability for {}", name))?);
104 buf.push_str(":</span> ");
105 buf.push_str(rec.get("Effect")
106 .ok_or(format_err!("Missing Effect for {}", name))?);
107 }
108 "Ability" | "Equipment" | "Item" | "Plant" | "Treasure" =>
109 buf.push_str(rec.get("Effect")
110 .ok_or(format_err!("Missing Effect for {}", name))?),
111 "Monster" => {
112 buf.push_str("<span font_weight=\"bold\">");
113 buf.push_str(rec.get("Action")
114 .ok_or(format_err!("Missing Action for {}", name))?);
115 buf.push_str(":</span> ");
116 buf.push_str(rec.get("Effect")
117 .ok_or(format_err!("Missing Effect for {}", name))?);
118 }
119 _ => bail!("Unknown type: {}", typ),
120 }
121 pango::parse_markup(&buf, '\0')?
122 };
123
124 layout.set_attributes(&attrs);
125 layout.set_text(&text);
126 pangocairo::functions::show_layout(&ctx, &layout);
127
128 let special_icon = rec.get("Icon").map(|i| format!("../icons/{}-icon.svg", i));
129 let icon_path = special_icon.unwrap_or(format!(
130 "../icons/{}-icon.svg",
131 match typ.as_ref() {
132 "Weapon" => rec.get("Type")
133 .ok_or(format_err!("Missing Type for {}", name))?
134 .to_owned(),
135 _ => typ.to_lowercase(),
136 },
137 ));
138 let icon = rsvg::Handle::new_from_file(&icon_path)
139 .map_err(|_| format_err!("Error loading image: {}", icon_path))?;
140 ctx.translate(10.0, 10.0);
141 ctx.scale(0.9, 0.9);
142 icon.render_cairo(&ctx);
143
144
145 {
146 let name = name.chars().filter_map(|c| {
147 if c == ' ' {
148 Some('_')
149 } else if c.is_alphabetic() {
150 Some(c.to_ascii_lowercase())
151 } else {
152 None
153 }
154 }).collect::<String>();
155 let path = format!("cards/{}_{}.png", typ.to_lowercase(), name);
156 let mut f = fs::File::create(&path)?;
157 println!("Writing to {}", path);
158 surf.write_to_png(&mut f)?;
159 }
160
161 Ok(())
162 }
163
164 fn run() -> Result<(), failure::Error> {
165 let mut f = io::BufReader::new(fs::File::open("cards.rec")?);
166 let recfile = rrecutils::Recfile::parse(&mut f)?;
167
168 for w in recfile.iter() {
169 draw_card(w)?;
170 }
171
172 Ok(())
173 }
2174
3175 fn main() {
4 let recfile;
5 println!("Hello, world!");
176 run().unwrap();
6177 }