use crate::prelude::*;
use piece_specs::SimpleCommon;
type ColourMap = IndexVec<FaceId, Colour>;
#[derive(Debug,Serialize,Deserialize)]
pub struct GenericSimpleShape<Desc, Outl> {
pub desc: Desc,
colours: ColourMap,
#[serde(default)] pub edges: ColourMap,
#[serde(default="default_edge_width")] pub edge_width: f64,
pub itemname: String,
pub outline: Outl,
}
pub type SimpleShape = GenericSimpleShape<Html, Outline>;
pub const SELECT_SCALE: f64 = 1.1;
pub const DEFAULT_EDGE_WIDTH: f64 = 0.2;
pub const INVISIBLE_EDGE_SENSITIVE: f64 = 2.;
fn default_edge_width() -> f64 { DEFAULT_EDGE_WIDTH }
#[derive(Copy,Clone,Debug,Error,Serialize,Deserialize)]
pub enum SVGProcessingError {
UnknownOperator,
BadNumber,
WriteFail,
NegativeDragraise,
}
display_as_debug!{SVGProcessingError}
error_from_losedetails!{SVGProcessingError,WriteFail,fmt::Error}
error_from_losedetails!{SVGProcessingError,BadNumber,std::num::ParseFloatError}
impl From<SVGProcessingError> for MgmtError {
fn from(se: SVGProcessingError) -> MgmtError { se.into() }
}
#[throws(SvgE)]
pub fn svg_rescale_path(input: &HtmlStr, scale: f64) -> Html {
type BM = u64;
type BI = u32;
#[derive(Debug,Copy,Clone)]
struct RotatingBitmap {
bits: BM,
len: BI,
index: BI,
}
impl RotatingBitmap {
const fn new(bits: BM, len: BI) -> Self { Self{ bits, len, index:0 } }
fn reset(&mut self) { self.index= 0; }
fn next(&mut self) -> bool {
let r = (self.bits >> (self.len-1 - self.index)) & 1 != 0;
self.index += 1;
if self.index == self.len { self.index = 0; }
r
}
}
const ALWAYS_MAP: RotatingBitmap = RotatingBitmap::new(0x01, 1);
let mut out = String::new();
let mut map = ALWAYS_MAP;
let mut first = iter::once(());
for w in input.as_html_str().split_ascii_whitespace() {
if first.next().is_none() { write!(&mut out, " ")?; }
match w {
"L" | "l" | "M" | "m" |
"V" | "v" | "H" | "h" => map = ALWAYS_MAP,
"A" | "a" => map = RotatingBitmap::new(0x63, 7),
"z" => map.reset(),
v if v.starts_with(|s:char| s=='-' || s=='.' || s.is_ascii_digit()) => {
if map.next() {
let v: f64 = v.parse()?;
write!(&mut out, "{}", v * scale)?;
continue;
}
}
_ => throw!(SvgE::UnknownOperator),
};
write!(&mut out, "{}", w)?;
}
trace!("rescaled by {}: {:?} as {:?}", scale, input, &out);
Html::from_html_string(out)
}
#[throws(SvgE)]
pub fn svg_circle_path(diam: f64) -> Html {
let unit_path = Html::lit(
"M 0 1 a 1 1 0 1 0 0 -2 \
a 1 1 0 1 0 0 2 z"
);
let scale = diam * 0.5;
let path = svg_rescale_path(unit_path.into(), scale)?;
path
}
#[throws(SvgE)]
pub fn svg_rectangle_path(PosC{coords: [x,y]}: PosC<f64>) -> Html {
hformat!("M {} {} h {} v {} h {} z",
-x*0.5, -y*0.5, x, y, -x)
}
#[dyn_upcast]
impl<Desc, Outl:'static> OutlineTrait for GenericSimpleShape<Desc, Outl>
where Desc: Debug + Send + Sync + 'static,
Outl: OutlineTrait,
{
delegate! {
to self.outline {
fn outline_path(&self, scale: f64) -> Result<Html,IE>;
fn thresh_dragraise(&self) -> Result<Option<Coord>,IE>;
fn bbox_approx(&self) -> Result<Rect, IE>;
}
}
}
#[typetag::serde]
impl PieceTrait for SimpleShape {
#[throws(IE)]
fn svg_piece(&self, f: &mut Html, gpc: &GPiece,
_gs: &GameState, _vpid: VisiblePieceId) {
self.svg_piece_raw(f, gpc.face, &mut |_|Ok(()))?;
}
fn describe_html(&self, gpc: &GPiece, _goccults: &GameOccults)
-> Result<Html, IE>
{
Ok::<_,IE>(
if_chain! {
if let face = gpc.face;
if let Some(colour) = self.colours.get(face);
then { hformat!("a {} {}", colour, self.desc) }
else { hformat!("a {}", self.desc) }
}
)
}
fn nfaces(&self) -> RawFaceId {
self.count_faces().try_into().unwrap()
}
fn itemname(&self) -> &str { self.itemname() }
}
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct PieceLabelLoaded {
#[serde(default)] pub place: piece_specs::PieceLabelPlace,
pub colour: Option<Colour>,
}
impl piece_specs::PieceLabel {
#[throws(SpecError)]
pub fn load(&self) -> PieceLabelLoaded {
let Self { place, ref colour } = *self;
let colour = colour.as_ref().map(|c| c.try_into()).transpose()?;
PieceLabelLoaded { place, colour }
}
}
#[ext(pub)]
impl Option<piece_specs::PieceLabel> {
#[throws(SpecError)]
fn load(&self) -> Option<PieceLabelLoaded> {
self.as_ref().map(|l| l.load()).transpose()?
}
}
impl PieceLabelLoaded {
#[throws(IE)]
pub fn svg(&self, f: &mut Html,
outline: &RectShape,
def_colour: Option<&Colour>,
text: &Html) {
let colour = {
if let Some(c) = &self.colour { c.borrow() }
else if let Some(c) = def_colour { c.borrow() }
else { Html::lit("black").into() }
};
let fontsz = 4.;
let PosC{ coords: [x,y] } = {
use piece_specs::PieceLabelPlace::*;
let inout = match self.place {
BottomLeft | TopLeft => 1.,
BottomLeftOutside | TopLeftOutside => -1.,
};
let eff_size = (outline.xy - PosC::new(2., inout * 2.))?;
let mut pos = (eff_size * -0.5)?;
let y = &mut pos.coords[1];
*y += 0.5 * fontsz * inout;
match self.place {
BottomLeft | BottomLeftOutside => { *y *= -1. },
TopLeft | TopLeftOutside => { },
};
*y += 0.5 * fontsz;
pos
};
hwrite!(f,
r##"<text x="{}" y="{}" font-size="{}" fill="{}">{}</text>"##,
x, y, fontsz, colour, &text
)?;
}
}
impl<Desc, Outl:'static> GenericSimpleShape<Desc, Outl>
where Desc: Debug + Send + Sync + 'static,
Outl: OutlineTrait,
{
pub fn count_faces(&self) -> usize {
max(self.colours.len(), self.edges.len())
}
pub fn itemname(&self) -> &str { &self.itemname }
#[throws(SpecError)]
pub fn new(desc: Desc, outline: Outl,
def_itemname: &'_ str,
common: &SimpleCommon)
-> GenericSimpleShape<Desc, Outl>
{
let itemname = common.itemname.clone()
.unwrap_or_else(|| def_itemname.to_string());
let cmap = |spec: &FaceColourSpecs| Ok::<_,SpecError>(
spec
.iter()
.map(|s| s.try_into())
.collect::<Result<_,_>>()?
);
if common.edge_width.is_some() && common.edges.len() == 0 {
throw!(SpecError::SpecifiedWidthOfNoEdges);
}
let shape = GenericSimpleShape {
desc, itemname, outline,
colours: cmap(&common.faces)?,
edges: cmap(&common.edges)?,
edge_width: common.edge_width.unwrap_or(DEFAULT_EDGE_WIDTH),
};
let count = shape.count_faces();
if count == 0 { throw!(SpecError::ZeroFaces) }
let check = |colours: &ColourMap| {
let x = colours.len();
if x == 0 || x == count { Ok(()) }
else { Err(SpecError::InconsistentFacesEdgecoloursCount) }
};
check(&shape.colours)?;
check(&shape.edges)?;
shape
}
#[throws(IE)]
pub fn svg_piece_raw(
&self, f: &mut Html, face: FaceId,
stroke_attrs_hook: &mut dyn FnMut(&mut Html) -> Result<(),IE>,
) {
let ef = |f: &mut Html, cmap: &ColourMap, attrname: &str, otherwise| {
if let Some(colour) = cmap.get(face) {
hwrite!(f, r##" {}="{}""##, attrname, colour)
} else {
hwrite!(f, "{}", otherwise)
}
};
let path = self.outline_path(1.0)?;
if self.colours.len() == 0 {
hwrite!(f,
r##"<path fill="none" \
stroke-width="{}" stroke="transparent" d="{}"/>"##,
INVISIBLE_EDGE_SENSITIVE,
&path)?;
}
hwrite!(f, r##"<path"##)?;
ef(f, &self.colours, "fill", r##" fill="none""##)?;
if self.edges.len() != 0 {
hwrite!(f, r##" stroke-width="{}""##, &self.edge_width)?;
}
stroke_attrs_hook(f)?;
ef(f, &self.edges, "stroke", "")?;
hwrite!(f, r##" d="{}"/>"##, &path)?;
}
}
#[typetag::serde(tag="type")]
pub trait SimplePieceSpec: Debug {
fn load_raw(&self) -> Result<(SimpleShape, &SimpleCommon), SpecError>;
#[throws(SpecError)]
fn load(&self) -> PieceSpecLoaded {
PieceSpecLoaded {
p: Box::new(self.load_raw()?.0),
loaded_via_alias: None,
occultable: None,
}
}
}
#[typetag::serde]
impl SimplePieceSpec for piece_specs::Disc {
#[throws(SpecError)]
fn load_raw(&self) -> (SimpleShape, &SimpleCommon) {
let outline = CircleShape { diam: self.diam as f64 };
(SimpleShape::new(
Html::lit("disc").into(),
outline.into(),
"simple-disc",
&self.common,
)?, &self.common)
}
}
#[typetag::serde]
impl PieceSpec for piece_specs::Disc {
#[throws(SpecError)]
fn load(&self, _: usize, _: &mut GPiece,
_pcaliases: &PieceAliases, _ir: &InstanceRef)
-> PieceSpecLoaded {
SimplePieceSpec::load(self)?
}
}
impl piece_specs::Rect {
#[throws(SpecError)]
fn xy(&self) -> Pos {
match *self.size.as_slice() {
[s,] => PosC::new(s,s),
[x,y] => PosC::new(x,y),
_ => throw!(SpecError::ImproperSizeSpec),
}
}
}
#[typetag::serde]
impl SimplePieceSpec for piece_specs::Rect {
#[throws(SpecError)]
fn load_raw(&self) -> (SimpleShape, &SimpleCommon) {
let outline = RectShape { xy: self.xy()?.map(|v| v as f64) };
let desc = Html::lit(
if outline.xy.x() == outline.xy.y()
{ "square" } else { "rectangle" }
).into();
(SimpleShape::new(
desc,
outline.into(),
"simple-rect",
&self.common,
)?, &self.common)
}
}
#[typetag::serde]
impl PieceSpec for piece_specs::Rect {
#[throws(SpecError)]
fn load(&self, _: usize, _: &mut GPiece,
_pcaliases: &PieceAliases, _ir: &InstanceRef)
-> PieceSpecLoaded {
SimplePieceSpec::load(self)?
}
}