chiark / gitweb /
shape libraries: support for recolouring SVGs
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 10 Jan 2021 10:25:11 +0000 (10:25 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 10 Jan 2021 23:19:44 +0000 (23:19 +0000)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
.gitignore
Makefile
README.md
media-scraper
src/shapelib.rs

index e9780d9750bb428dce1303fef8241c562a17b6df..a8ac9a424aebe260a94ebeee2ed6877cee1e1606 100644 (file)
@@ -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/*
index 457b4dab96462ca0c6d69052de65e9bcce539bff..f8372b6c0abd835eb5b501592d852ab30da4e2ea 100644 (file)
--- 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
index b1b5bd2ae452c8e9b902f578e7d3da2f86220270..9449d8679a05d6771d36525670bbaf709cc96de6 100644 (file)
--- 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:
index 0c1c86fbc2639fc41a8a74b06675998f53540a32..9a6d364c018c9214b18989c5ebd7c8390a1352be 100755 (executable)
@@ -191,11 +191,102 @@ foreach my $groupname (sort keys %$groups) {
 
     print DEBUG "file $lstem ";
 
-    print $makefile <<END or die $!;
+    my $process_colour = sub ($$) {
+      my ($linput, $lprocessed) = @_;
+      print $makefile <<END or die $!;
 LIBRARY_FILES += $lprocessed
-$lprocessed: $lupstream $licpath $input
+$lprocessed: $linput $licpath $input
        \$(LIBRARY_PROCESS_SVG)
 END
+    };
+
+    my $process_colours = sub {
+      my $colours = $gcfg->('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 <<END or die $! if $ci == 0;
+LIBRARY_CLEAN += $ncoloured
+$ncoloured: $lupstream Makefile
+END
+         $ci++;
+         print $makefile <<END or die $!;
+       \$(RECOLOUR_SVG) -f '$from' -t '$to' $cfp >$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 <<END or die $!;
+# cycle: @maybe_cycle
+END
+         } else {
+           $emit_most->();
+         }
+         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 <<END or die $!;
+       test -s $cfp
+       mv -f $cfp \$@
+END
+       }
+
+       $process_colour->($coloured, $outfile);
+      }
+    };
+
+    $process_colours->();
 
     if (stat $lupstream) {
       print DEBUG "already.\n";
index 99674f900a452be4dced80c346290cf949c97f00..45c934a5cf5a1373c134ff15f569b6f30943cf45 100644 (file)
@@ -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<HashMap<String, RecolourData>>,
   #[serde(flatten)] outline: Box<dyn OutlineDefn>,
 }
 
+#[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