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="--nwtemplates", default_value="./nwtemplates")]
nwtemplates: String,
#[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 = ItemEnquiryData;
pub const VIS: ShowUnocculted = ShowUnocculted::new_visible();
#[throws(AE)]
fn preview(opts: &Opts, 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 ig_dummy = Instance::dummy();
nwtemplates::init_from_dir(&opts.nwtemplates)?;
impl Prep {
fn large(&self) -> bool {
self.size[0] >= 50.0
}
fn face_want_span(&self) -> usize {
if self.large() { 6 } else { 1 }
}
fn face_want_several(&self, face: RawFaceId) -> usize {
if self.large() || face >= 2 { 1 } else { SEVERAL }
}
fn face_want_cols(&self, face: RawFaceId) -> usize {
self.face_want_several(face) *
self.face_want_span()
}
fn want_cols(&self) -> usize {
(0 .. self.p.nfaces())
.map(|face| self.face_want_cols(face))
.sum()
}
}
let mut pieces: Vec<(Prep, GPiece)> = items.into_iter().map(|it| {
let spec = ItemSpec::from(&it);
let sortkey = it.sortkey;
(||{
let mut gpc = GPiece::dummy();
let SpecLoaded { p, .. } = spec.clone()
.load(PieceLoadArgs {
ig: &ig_dummy,
gpc: &mut gpc,
depth: SpecDepth::zero(),
i: 0,
})
.context("load")?;
p.loaded_hook_preview(&mut gpc)?;
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_preview()?;
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 }, gpc ))
})().with_context(|| format!("{:?}", &spec))
}).collect::<Result<Vec<_>,_>>()?;
pieces.sort_by_key(|(p,_)| (p.spec.item.clone(), p.spec.lib.clone()));
let total_cols = pieces.iter().map(|s| s.0.want_cols()).max().unwrap_or(1);
let max_uos = pieces.iter().map(|s| s.0.uos.len()).max().unwrap_or(0);
println!("{}", &HTML_PRELUDE);
println!(r#"<table rules="all">"#);
for (s, gpc) in &mut 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())?);
if max_uos > 0 {
println!(r#"<td>{}</td>"#,
uos.iter()
.map(|uo| Html::from_txt(uo))
.collect::<Vec<Html>>()
.iter()
.hjoin(&Html::lit(" ")));
}
let mut cols_done = 0;
let face_span = s.face_want_span();
for face in 0.. s.p.nfaces() {
for inseveral in 0.. s.face_want_several(face) {
print!(r#"<td align="center""#);
if face_span != 1 {
print!(r#" colspan="{}""#, face_span);
}
cols_done += face_span;
print!(r##" bgcolor="#{}""##,
match inseveral {
0 | 1 => "eee",
2 => "555",
_ => panic!(),
});
println!(r#">"#);
let viewport =
[bbox[0].clone(), size.clone()]
.iter().flatten().cloned()
.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]);
let mut html = Html::lit("").into();
gpc.face = face.into();
p.svg_piece(&mut html, gpc, &GameState::dummy(), default())?;
println!("{}", html);
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);
}
println!("</svg>");
println!("</td>");
}}
if cols_done < total_cols {
print!(r#"<td colspan="{}"></td>"#, total_cols - cols_done);
}
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("OTTER_CLI_LOG")
.init();
const SPLIT: &[char] = &[',', ' '];
for libs in opts.libs.split(SPLIT) {
let tlibs = ShapelibConfig1::PathGlob(libs.to_owned());
load_global_libs(&[tlibs.clone()])?;
}
let mut items: Vec<ItemForOutput> = default();
let ig_dummy = Instance::dummy();
let all_registries = ig_dummy.all_shapelibs();
for lib in lib_name_list(&ig_dummy) {
for contents in all_registries.lib_name_lookup(&lib)? {
for pat in opts.items.split(SPLIT) {
for item in contents.list_glob(pat)? {
items.push(item)
}
}
}
}
items.sort();
match opts.outkind {
OutputKind::List => for item in items {
println!("{}", item);
}
OutputKind::Preview => {
preview(&opts, items)?
}
}
}