const HTML_PRELUDE: &str = r##"
<!-- Copyright 2020u Ian Jackson
SPDX-License-Identifier: AGPL-3.0-or-later
There is NO WARRANTY. -->
<html>
<head>
<title>Otter builtin shape library</title>
</head>
<body>
<style>
html { background-color: #ddf; }
table { background-color: #eef; }
</style>
<h1>Otter builtin shape library</h1>
This lists all the shapes provided by the library in this version of Otter.
</p>
"##;
const HTML_TRAILER: &str = r##"
<hr>
<address>
Otter and its shape (piece picture) libraries
are <a href="/_/libre">Free Software</a> and come with NO
WARRANTY.
<p>
The shapes come from a variety of sources and are the work of many
contributors. <a href="/_/LICENCE">Further information about
their authorship and licensing</a> etc. is available.
<p>
If you wish to edit these shapes you should probably start with
the <a href="/_/src/">source code</a>.
"##;
pub use otter::prelude::*;
pub use shapelib::*;
use structopt::StructOpt;
#[derive(Debug,Clone)]
#[derive(StructOpt)]
pub struct Opts {
#[structopt(long="--libs", default_value="library/*.toml")]
libs: String,
#[structopt(long="--items", default_value="*")]
items: String,
#[structopt(flatten)]
outkind: OutputKind,
}
#[derive(StructOpt,Debug,Clone,Copy)]
pub enum OutputKind {
List,
Preview,
}
pub type ItemForOutput = (String, ItemEnquiryData);
pub const VIS: ShowUnocculted = ShowUnocculted::new_visible();
#[throws(AE)]
fn preview(items: Vec<ItemForOutput>) {
const BORDER: f64 = 1.;
struct Prep {
spec: ItemSpec,
sortkey: Option<String>,
p: Box<dyn PieceTrait>,
uos: Vec<String>,
bbox: Vec<Vec<f64>>,
size: Vec<f64>,
}
const SEVERAL: usize = 3;
let pcaliases = default();
impl Prep {
fn want_several(&self) -> bool {
self.size[0] < 50.0
}
fn face_cols(&self) -> usize {
usize::from(self.p.nfaces())
* if self.want_several() { SEVERAL } else { 1 }
}
}
let mut pieces: Vec<Prep> = items.into_iter().map(|it| {
let spec = ItemSpec { lib: it.0, item: it.1.itemname.into() };
let sortkey = it.1.sortkey;
(||{
let (p, _occultable) = spec.clone().find_load(&pcaliases)
.context("load")?;
let mut uos = vec![];
p.add_ui_operations(VIS, &mut uos, &GameState::dummy(), &GPiece::dummy())
.context("add uos")?;
let uos = uos.into_iter().map(|uo| uo.opname).collect::<Vec<_>>();
let spec = spec.clone();
let bbox = p
.bbox_approx()?;
let mut bbox = bbox.corners.iter()
.map(|PosC{coords}| coords.iter().map(|&p| p as f64)
.collect::<Vec<_>>())
.collect::<Vec<_>>();
for xy in &mut bbox[0] { *xy -= BORDER }
for xy in &mut bbox[1] { *xy += BORDER }
let size = izip!(&bbox[0], &bbox[1])
.map(|(min,max)| max-min)
.collect::<Vec<_>>();
Ok::<_,AE>(Prep { spec, p, sortkey, uos, bbox, size })
})().with_context(|| format!("{:?}", &spec))
}).collect::<Result<Vec<_>,_>>()?;
pieces.sort_by_key(|p| (p.spec.item.clone(), p.spec.lib.clone()));
let max_facecols = pieces.iter().map(|s| s.face_cols()).max().unwrap_or(1);
let max_uos = pieces.iter().map(|s| s.uos.len()).max().unwrap_or(0);
println!("{}", &HTML_PRELUDE);
println!(r#"<table rules="all">"#);
for s in &pieces {
let Prep { spec, sortkey, p, uos, bbox, size } = s;
macro_rules! println {
($($x:tt)*) => ({
let mut s = Html::new();
hwrite!(&mut s, $($x)*).unwrap();
std::println!("{}", &s.as_html_str());
})
}
println!(r#"<tr>"#);
println!(r#"<th align="left"><kbd>{}</kbd></th>"#,
Html::from_txt(&spec.lib));
println!(r#"<th align="left"><kbd>{}</kbd>"#,
Html::from_txt(&spec.item));
if let Some(sortkey) = sortkey {
println!(r#"<br><kbd>[{}]</kbd>"#,
Html::from_txt(sortkey));
}
println!(r#"</th>"#);
println!(r#"<th align="left">{}</th>"#,
p.describe_html(&GPiece::dummy(), &default())?);
let only1 = s.face_cols() == 1;
for facecol in 0..(if only1 { 1 } else { max_facecols }) {
let (face, inseveral) = if s.want_several() {
(facecol / SEVERAL, facecol % SEVERAL)
} else {
(facecol, 0)
};
print!(r#"<td align="center""#);
if only1 {
assert!(!s.want_several());
print!(r#" colspan="{}""#, max_facecols);
}
print!(r##" bgcolor="#{}""##,
match inseveral {
0 | 1 => "eee",
2 => "555",
_ => panic!(),
});
println!(r#">"#);
if face < (p.nfaces() as usize) {
let viewport =
[bbox[0].clone(), size.clone()]
.iter().cloned()
.flatten()
.map(|c| c.to_string())
.join(" ");
let wh = size.iter().map(|&s| s * SVG_SCALE)
.collect::<Vec<_>>();
let surround = p.surround_path()?;
print!(r#"<svg xmlns="http://www.w3.org/2000/svg"
viewBox="{}" width={} height={}>"#,
&viewport, wh[0], wh[1]);
if inseveral == 1 {
let dasharray = player_num_dasharray(1.try_into().unwrap());
println!(r#"<path d="{}" stroke-dasharray="{}"
fill="none" stroke="{}" />"#,
&surround, &dasharray, HELD_SURROUND_COLOUR);
}
let mut html = Html::lit("").into();
let gpc = GPiece { face: face.into(), ..GPiece::dummy() };
p.svg_piece(&mut html, &gpc, &GameState::dummy(), default())?;
println!("{}</svg>", html);
}
println!("</td>");
if max_uos > 0 {
println!(r#"<td>{}</td>"#,
uos.iter()
.map(|uo| Html::from_txt(uo))
.collect::<Vec<Html>>()
.iter()
.hjoin(&Html::lit(" ")));
}
};
println!("</tr>");
}
println!("</table>");
println!("{}", &HTML_TRAILER);
}
#[throws(anyhow::Error)]
fn main() {
let opts = Opts::from_args();
env_logger::Builder::new()
.filter_level(log::LevelFilter::Info)
.parse_env("OTTERLIB_LOG")
.init();
const SPLIT: &[char] = &[',', ' '];
for libs in opts.libs.split(SPLIT) {
let tlibs = Config1::PathGlob(libs.to_owned());
load(&vec![tlibs.clone()])?;
}
let mut items: Vec<ItemForOutput> =
libs_list()
.into_iter()
.map(|lib| {
let contents = libs_lookup(&lib)?;
let items = opts.items
.split(SPLIT)
.map(|items| contents.list_glob(items))
.collect::<Result<Vec<_>,_>>()?
.into_iter()
.flatten();
Ok::<_,AE>((lib, items))
})
.collect::<Result<Vec<_>,_>>()?
.into_iter()
.map(|(lib, items)| {
items.into_iter().map(|item| (lib.clone(),item)).collect::<Vec<_>>()
}).flatten()
.collect();
items.sort();
match opts.outkind {
OutputKind::List => for item in &items {
println!("{:<10} {}", &item.0, item.1.line_for_list());
}
OutputKind::Preview => {
preview(items)?
}
}
}