fix(render): add lots of SVG optimization

Adds optimization using svgo and scour
This commit is contained in:
Ian Santopietro 2022-04-13 16:40:00 -06:00
parent 04603622da
commit d0c47689e2
3 changed files with 132 additions and 12 deletions

View File

@ -17,14 +17,25 @@
#
# Thanks to the GNOME icon developers for the original version of this script
import glob
import os
import shutil
import sys
import xml.sax
import subprocess
import argparse
INKSCAPE = '/usr/bin/inkscape'
MAINDIR = '../../Pop'
from pathlib import Path
INKSCAPE = Path('/usr/bin/inkscape')
SCOUR = Path('/usr/bin/scour')
HAS_SCOUR = os.path.exists(SCOUR)
SVGO = Path('/usr/local/bin/svgo')
HAS_SVGO = os.path.exists(SVGO)
MAINDIR = Path('../../Pop')
SVGO_CONFIG = MAINDIR / '..' / 'svgo.config.js'
CLI_OUTPUT=subprocess.DEVNULL
SOURCES = ('actions', 'apps', 'categories', 'devices', 'emblems', 'logos', 'mimetypes', 'places', 'preferences', 'status')
# the resolution that non-hi-dpi icons are rendered at
@ -38,15 +49,67 @@ def main(args, SRC):
def inkscape_render_rect(icon_file, rect, dpi, output_file):
# if HAS_SCOUR:
# output_file += 'unoptimized-'
cmd = [
INKSCAPE,
'-d', str(dpi), # export-dpi
'--actions=select-all:all;transform-remove',
'-i', rect, # export-id
'-o', output_file, # export-filename
'-o', f'{output_file}', # export-filename
icon_file # input file
]
print(f'Rendering {output_file}')
subprocess.run(cmd)
if CLI_OUTPUT == None:
print(f'Running {cmd}')
try:
subprocess.run(cmd, check=True, stderr=CLI_OUTPUT, stdout=CLI_OUTPUT)
except subprocess.CalledProcessError:
print(f'Could not render {output_file}: see output')
sys.exit(1)
def scour_clean_svg(icon_file):
out_file = Path(icon_file)
in_file = Path(f'{icon_file}-unop')
shutil.copy(out_file, in_file)
cmd = [
SCOUR,
f'-i', in_file,
f'-o', out_file,
'--enable-viewboxing',
'--enable-id-stripping',
'--enable-comment-stripping',
'--shorten-ids',
'--indent=none'
]
print(f'Cleaning up {out_file}')
if CLI_OUTPUT == None:
print(f'Running {cmd}')
try:
if in_file.exists():
subprocess.run(cmd, check=True, stderr=CLI_OUTPUT, stdout=CLI_OUTPUT)
except subprocess.CalledProcessError:
print(f'Could not clean up {icon_file}: see output')
sys.exit(1)
os.remove(in_file)
def svgo_optimize_svgs(icon_file):
cmd = [
SVGO,
f'--config={SVGO_CONFIG}',
f'--input={icon_file}',
f'--output={icon_file}',
]
print(f'Optimizing {icon_file}')
if CLI_OUTPUT == None:
print(f'Running {cmd}')
try:
subprocess.run(cmd, check=True, stderr=CLI_OUTPUT, stdout=CLI_OUTPUT)
except subprocess.CalledProcessError:
print(f'Could not optimize {icon_file}: see output')
sys.exit(1)
class ContentHandler(xml.sax.ContentHandler):
ROOT = 0
@ -139,12 +202,20 @@ def main(args, SRC):
# Do a time based check!
if self.force or not os.path.exists(outfile):
inkscape_render_rect(self.path, id, dpi, outfile)
if HAS_SCOUR:
scour_clean_svg(outfile)
if HAS_SVGO:
svgo_optimize_svgs(outfile)
sys.stdout.write('.')
else:
stat_in = os.stat(self.path)
stat_out = os.stat(outfile)
if stat_in.st_mtime > stat_out.st_mtime:
inkscape_render_rect(self.path, id, dpi, outfile)
if HAS_SCOUR:
scour_clean_svg(outfile)
if HAS_SVGO:
svgo_optimize_svgs(outfile)
sys.stdout.write('.')
else:
sys.stdout.write('-')

View File

@ -23,33 +23,60 @@ require "fileutils"
include REXML
VERBOSE = FALSE
VERBOSE = false
# INKSCAPE = 'flatpak run org.inkscape.Inkscape'
INKSCAPE = '/usr/bin/inkscape'
SRC = "./source-symbolic.svg"
PREFIX = "../../Pop/scalable"
SVGO_CONFIG = "../../svgo.config.js"
# install with `sudo npm install -g svgo`
SVGO = '/usr/local/bin/svgo'
SCOUR = '/usr/bin/scour'
def chopSVG(icon)
FileUtils.mkdir_p(icon[:dir]) unless File.exists?(icon[:dir])
unless (File.exists?(icon[:file]) && !icon[:forcerender])
FileUtils.cp(SRC,icon[:file])
puts " >> #{icon[:name]}"
cmd = "#{INKSCAPE} #{icon[:file]} -g --select #{icon[:id]} "
cmd += '--verb="FitCanvasToSelection;EditCopy;EditSelectAllInAllLayers;'\
'EditDelete;EditPasteInPlace;EditSelectAll;FileVacuum;FileSave;FileQuit"'
puts " >> #{icon[:name]} from #{icon[:file]} as #{icon[:id]}"
# cmd = "#{INKSCAPE} #{icon[:file]} --select #{icon[:id]} "
# cmd += '--verb="FitCanvasToSelection;EditCopy;EditSelectAllInAllLayers;'\
# 'EditDelete;EditPasteInPlace;EditSelectAll;FileVacuum;FileSave;FileQuit"'
cmd = "#{INKSCAPE} -i #{icon[:id]} -o #{icon[:file]} --export-id-only #{SRC}"
cmd += " > /dev/null 2>&1" unless VERBOSE
puts " Running '#{cmd}'" if VERBOSE
system(cmd)
#saving as plain SVG gets rid of the classes :/
cmd = "#{INKSCAPE} --vacuum-defs -z #{icon[:file]} --export-plain-svg=#{icon[:file]}"
cmd += " > /dev/null 2>&1" unless VERBOSE
system(cmd)
#completely vaccuum with svgo
cmd = "#{SVGO} --pretty -i #{icon[:file]} -o #{icon[:file]}"
system(cmd)
# #completely vaccuum with svgo
# cmd = "#{SVGO} --pretty -i #{icon[:file]} -o #{icon[:file]}"
if (File.exist?(SCOUR)) #clean up SVGs with scour
puts " !! #{icon[:name]} in #{icon[:file]}"
FileUtils.copy(icon[:file], "#{icon[:file]}-unop")
cmd = "#{SCOUR} -i #{icon[:file]}-unop -o #{icon[:file]} "
cmd += "--enable-viewboxing --enable-id-stripping --enable-comment-stripping --shorten-ids --indent=none"
cmd += " > /dev/null 2>&1" unless VERBOSE
puts " Running '#{cmd}'" if VERBOSE
system(cmd)
FileUtils.remove("#{icon[:file]}-unop")
end
if (File.exists?(SVGO)) #optimize SVGs
puts " !! #{icon[:name]} in #{icon[:file]}"
FileUtils.copy(icon[:file], "#{icon[:file]}-unop")
cmd = "#{SVGO} --config=#{SVGO_CONFIG} --input=#{icon[:file]} --output=#{icon[:file]}"
cmd += " > /dev/null 2>&1" unless VERBOSE
puts " Running '#{cmd}'" if VERBOSE
system(cmd)
FileUtils.remove("#{icon[:file]}-unop")
end
# crop
svgcrop = Document.new(File.new(icon[:file], 'r'))
svgcrop.root.each_element("//rect") do |rect|

22
svgo.config.js Normal file
View File

@ -0,0 +1,22 @@
module.exports = {
// GENERAL OPTIONS
multipass: true,
//****** PLUGINS ORDER MATTERS ******** //
plugins:[
// MANAGE BUILT-IN DEFAULT PLUGINS
{
name: 'preset-default',
params: {
overrides: {
// Set default plugins as disabled with boolean 'false'
removeViewBox:false, // <-- important
mergePaths:false, // <-- important
},
},
},
// MANAGE BUILT-IN NON-DEFAULT PLUGINS
// Enable non-default plugins
{ name: 'convertTransform' },
{ name: 'removeOffCanvasPaths' }, // last plugin
]
};