From 7f77d1cac3d2796885d3f542a0d141e009b61523 Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Sun, 10 Jan 2021 10:25:11 +0000 Subject: [PATCH] shape libraries: support for recolouring SVGs Signed-off-by: Ian Jackson --- .gitignore | 1 + Makefile | 7 ++++ README.md | 2 +- media-scraper | 95 +++++++++++++++++++++++++++++++++++++++++++++++-- src/shapelib.rs | 68 +++++++++++++++++++++++++++-------- 5 files changed, 155 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index e9780d97..a8ac9a42 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ templates/CC-BY-SA-4.0 templates/otter_wasm.ns.d.ts save/lock /library/*/*.usvg +/library/*/*.coloured.svg *.tmp .tsconfig.*.json stamp/* diff --git a/Makefile b/Makefile index 457b4dab..f8372b6c 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,12 @@ WASM_PACK_OPTIONS = --cargo-path=/bin/echo BUNDLE_SOURCES ?= bundle-rust-sources +ifndef INKSCAPE_EXTENSIONS +INKSCAPE ?= inkscape +INKSCAPE_EXTENSIONS := $(shell $(INKSCAPE) -x) +endif +RECOLOUR_SVG ?= $(INKSCAPE_EXTENSIONS)/color_replace.py + DEPLOY_ARCH=x86_64-unknown-linux-musl DEPLOY_RELEASE=debug DEPLOY_TARGET_DIR=$(TARGET_DIR)/$(addsuffix /,$(DEPLOY_ARCH))$(DEPLOY_RELEASE) @@ -303,5 +309,6 @@ deploy: stamp/cargo.deploy-build bundled-sources assets libraries clean: clean-nailing rm -f templates/script.js library/*/*.usvg stamp/* + rm -rf $(LIBRARY_CLEAN) rm -rf target $(NAILING_CARGO_JUST_RUN) rm -rf target diff --git a/README.md b/README.md index b1b5bd2a..9449d867 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ Setup ``` sudo apt install build-essential cpio git curl \ pkg-config libssl-dev \ - node-typescript + node-typescript inkscape ``` 2. Install Rust. This is most easily done with rustup: diff --git a/media-scraper b/media-scraper index 0c1c86fb..9a6d364c 100755 --- a/media-scraper +++ b/media-scraper @@ -191,11 +191,102 @@ foreach my $groupname (sort keys %$groups) { print DEBUG "file $lstem "; - print $makefile <('colours'); + if (!keys %$colours) { + $process_colour->($lupstream, $lprocessed, ""); + return; + } + foreach my $colour (sort keys %$colours) { + my $cspec = $colours->{$colour}; + my $abbrev = $cspec->{abbrev} or confess; + my $ncoloured = $lupstream; + $ncoloured =~ s/\.svg$/.coloured$&/; + my $outfile = $lprocessed; + $ncoloured =~ s/_c/$abbrev/ or confess "$outfile ?"; + $outfile =~ s/_c/$abbrev/ or confess "$outfile ?"; + my $coloured = $lupstream; + + my $ci = 0; + my $cfp = '$<'; + my $emitmap = sub { + my ($from, $to) = @_; + confess if $from =~ m/\W/ || $to =~ m/\W/; + + my $nfp = "\$@.$ci.tmp"; + $coloured = $ncoloured; + print $makefile <$nfp +END + $cfp = $nfp; + }; + + my %map = %{ $cspec->{map} // { } }; + while (keys %map) { + my $from = (sort keys %map)[0]; + my @maybe_cycle = (); + my $cycle; + for (;;) { + push @maybe_cycle, $from; + my $to = $map{$from}; + if (!exists $map{$to}) { + last; + } + $from = $to; + if (grep { $_ eq $to } @maybe_cycle) { + $cycle = $to; + last; + } + } + my $emit_most = sub ($) { + my ($end) = @_; + $end //= $#maybe_cycle; + foreach my $i (@maybe_cycle[0..$end]) { + $emitmap->($i, $map{$i}); + } + }; + if (defined $cycle) { + my $temp = 'abcbfb'; # chosen at random + my $aside = $maybe_cycle[-1]; + $emitmap->($aside, $temp); + $emit_most->($#maybe_cycle-1); + $emitmap->($temp, $map{$aside}); + print $makefile <(); + } + delete $map{$_} foreach @maybe_cycle; + } + if ($ci) { + # inkscape extensions have a tendency to write an empty + # file when they don't like their input or arguments + print $makefile <($coloured, $outfile); + } + }; + + $process_colours->(); if (stat $lupstream) { print DEBUG "already.\n"; diff --git a/src/shapelib.rs b/src/shapelib.rs index 99674f90..45c934a5 100644 --- a/src/shapelib.rs +++ b/src/shapelib.rs @@ -41,9 +41,16 @@ struct GroupDetails { #[serde(default)] centre: [f64; 2], #[serde(default)] flip: bool, #[serde(default="num_traits::identities::One::one")] scale: f64, + colours: Option>, #[serde(flatten)] outline: Box, } +#[derive(Debug,Deserialize)] +struct RecolourData { + abbrev: String, + // `map` not used by Rust +} + #[derive(Debug)] struct GroupData { groupname: String, @@ -105,6 +112,10 @@ pub enum LibraryLoadError { DuplicateItem { item: String, group1: String, group2: String }, #[error("{:?}",&self)] FilesListLineMissingWhitespace(usize), + #[error("{:?}",&self)] + MissingSubstituionToken(&'static str), + #[error("{:?}",&self)] + RepeatedSubstituionToken(&'static str), } impl LibraryLoadError { @@ -365,24 +376,51 @@ fn load_catalogue(libname: &str, dirname: &str, toml_path: &str) -> Contents { d, }); for fe in gdefn.files.0 { + let mut add1 = |item_name: &str, desc| { + let idata = ItemData { + group: group.clone(), + d: Arc::new(ItemDetails { desc }), + }; + type H<'e,X,Y> = hash_map::Entry<'e,X,Y>; + match l.items.entry(item_name.to_owned()) { + H::Occupied(oe) => throw!(LLE::DuplicateItem { + item: item_name.to_owned(), + group1: oe.get().group.groupname.clone(), + group2: groupname.clone(), + }), + H::Vacant(ve) => { + debug!("loaded shape {} {}", libname, item_name); + ve.insert(idata); + } + }; + Ok::<_,LLE>(()) + }; + let item_name = format!("{}{}{}", gdefn.item_prefix, fe.item_spec, gdefn.item_suffix); - let idata = ItemData { - group: group.clone(), - d: Arc::new(ItemDetails { desc: fe.desc.clone() }), - }; - type H<'e,X,Y> = hash_map::Entry<'e,X,Y>; - match l.items.entry(item_name.clone()) { - H::Occupied(oe) => throw!(LLE::DuplicateItem { - item: item_name.clone(), - group1: oe.get().group.groupname.clone(), - group2: groupname.clone(), - }), - H::Vacant(ve) => { - debug!("loaded shape {} {}", libname, item_name); - ve.insert(idata); + + if let Some(colours) = &group.d.colours { + #[throws(LLE)] + fn subst(before: &str, needle: &'static str, replacement: &str) + -> String { + let mut matches = before.match_indices(needle); + let m1 = matches.next() + .ok_or(LLE::MissingSubstituionToken(needle))?; + if matches.next().is_some() { + Err(LLE::RepeatedSubstituionToken(needle))?; + } + before[0.. m1.0].to_owned() + + replacement + + &before[m1.0 + m1.1.len() ..] } - }; + for (colour, recolourdata) in colours { + let t_item_name = subst(&item_name, "_c", &recolourdata.abbrev)?; + let t_desc = Html(subst(&fe.desc.0, "_colour", colour)?); + add1(&t_item_name, t_desc)?; + } + } else { + add1(&item_name, fe.desc.clone())?; + } } } l -- 2.30.2