diff --git a/lib/matplotlib/backends/_backend_gtk.py b/lib/matplotlib/backends/_backend_gtk.py index 0491db40e565..39c490a97f27 100644 --- a/lib/matplotlib/backends/_backend_gtk.py +++ b/lib/matplotlib/backends/_backend_gtk.py @@ -16,7 +16,7 @@ import gi # The GTK3/GTK4 backends will have already called `gi.require_version` to set # the desired GTK. -from gi.repository import Gdk, Gio, GLib, Gtk +from gi.repository import Gdk, Gio, GLib, Gtk, GdkPixbuf try: @@ -144,8 +144,11 @@ def __init__(self, canvas, num): if gtk_ver == 3: icon_ext = "png" if sys.platform == "win32" else "svg" - self.window.set_icon_from_file( + small_icon = GdkPixbuf.Pixbuf.new_from_file( + str(cbook._get_data_path(f"images/matplotlib_small.{icon_ext}"))) + large_icon = GdkPixbuf.Pixbuf.new_from_file( str(cbook._get_data_path(f"images/matplotlib.{icon_ext}"))) + self.window.set_icon_list([small_icon, large_icon]) self.vbox = Gtk.Box() self.vbox.set_property("orientation", Gtk.Orientation.VERTICAL) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index a18a9bd8660f..08d929c3fd1a 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -552,11 +552,11 @@ def create_with_canvas(cls, canvas_class, figure, num): # supported Tk version is increased to 8.6, as Tk 8.6+ natively # supports PNG images. icon_fname = str(cbook._get_data_path( - 'images/matplotlib.png')) + 'images/matplotlib_small.png')) icon_img = ImageTk.PhotoImage(file=icon_fname, master=window) icon_fname_large = str(cbook._get_data_path( - 'images/matplotlib_large.png')) + 'images/matplotlib.png')) icon_img_large = ImageTk.PhotoImage( file=icon_fname_large, master=window) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 39450ee32065..7ae0f7d5120c 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -87,6 +87,13 @@ } +def _create_WindowIcon(): + icon = QtGui.QIcon() + icon.addFile(str(cbook._get_data_path('images/matplotlib_small.svg'))) + icon.addFile(str(cbook._get_data_path('images/matplotlib.svg'))) + return icon + + # lru_cache keeps a reference to the QApplication instance, keeping it from # being GC'd. @functools.lru_cache(1) @@ -136,9 +143,7 @@ def _create_qApp(): pass app = QtWidgets.QApplication(["matplotlib"]) if sys.platform == "darwin": - image = str(cbook._get_data_path('images/matplotlib.svg')) - icon = QtGui.QIcon(image) - app.setWindowIcon(icon) + app.setWindowIcon(_create_WindowIcon()) app.setQuitOnLastWindowClosed(True) cbook._setup_new_guiapp() if qt_version == 5: @@ -590,9 +595,7 @@ def __init__(self, canvas, num): self.window.closing.connect(self._widgetclosed) if sys.platform != "darwin": - image = str(cbook._get_data_path('images/matplotlib.svg')) - icon = QtGui.QIcon(image) - self.window.setWindowIcon(icon) + self.window.setWindowIcon(_create_WindowIcon()) self.window._destroying = False @@ -983,8 +986,7 @@ def set_history_buttons(self): class SubplotToolQt(QtWidgets.QDialog): def __init__(self, targetfig, parent): super().__init__(parent) - self.setWindowIcon(QtGui.QIcon( - str(cbook._get_data_path("images/matplotlib.png")))) + self.setWindowIcon(_create_WindowIcon()) self.setObjectName("SubplotTool") self._spinboxes = {} main_layout = QtWidgets.QHBoxLayout() diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 930a944b5274..42183b8dd316 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1043,7 +1043,7 @@ def _load_bitmap(filename): def _set_frame_icon(frame): bundle = wx.IconBundle() - for image in ('matplotlib.png', 'matplotlib_large.png'): + for image in ('matplotlib_small.png', 'matplotlib.png'): icon = wx.Icon(_load_bitmap(image)) if not icon.IsOk(): return diff --git a/lib/matplotlib/mpl-data/images/back.pdf b/lib/matplotlib/mpl-data/images/back.pdf index 79709d8f435e..4bb01898022f 100644 Binary files a/lib/matplotlib/mpl-data/images/back.pdf and b/lib/matplotlib/mpl-data/images/back.pdf differ diff --git a/lib/matplotlib/mpl-data/images/back.png b/lib/matplotlib/mpl-data/images/back.png index e3c4b5815487..8536649d16ca 100644 Binary files a/lib/matplotlib/mpl-data/images/back.png and b/lib/matplotlib/mpl-data/images/back.png differ diff --git a/lib/matplotlib/mpl-data/images/back.svg b/lib/matplotlib/mpl-data/images/back.svg index 0c2d653cbe8f..f4d6e919f047 100644 --- a/lib/matplotlib/mpl-data/images/back.svg +++ b/lib/matplotlib/mpl-data/images/back.svg @@ -1,46 +1,7 @@ - - - - - - - - - - - - - - - + + + + + + diff --git a/lib/matplotlib/mpl-data/images/back_large.png b/lib/matplotlib/mpl-data/images/back_large.png index e44a70a9cd23..280ad57a4139 100644 Binary files a/lib/matplotlib/mpl-data/images/back_large.png and b/lib/matplotlib/mpl-data/images/back_large.png differ diff --git a/lib/matplotlib/mpl-data/images/filesave.pdf b/lib/matplotlib/mpl-data/images/filesave.pdf index 794a1152f602..59e178529b9e 100644 Binary files a/lib/matplotlib/mpl-data/images/filesave.pdf and b/lib/matplotlib/mpl-data/images/filesave.pdf differ diff --git a/lib/matplotlib/mpl-data/images/filesave.png b/lib/matplotlib/mpl-data/images/filesave.png index 919e40bf5829..38545ec3c467 100644 Binary files a/lib/matplotlib/mpl-data/images/filesave.png and b/lib/matplotlib/mpl-data/images/filesave.png differ diff --git a/lib/matplotlib/mpl-data/images/filesave.svg b/lib/matplotlib/mpl-data/images/filesave.svg index 856721b6b5e2..613efd973508 100644 --- a/lib/matplotlib/mpl-data/images/filesave.svg +++ b/lib/matplotlib/mpl-data/images/filesave.svg @@ -1,68 +1,6 @@ - - - - - - - - - - - - - - - + + + + + diff --git a/lib/matplotlib/mpl-data/images/filesave_large.png b/lib/matplotlib/mpl-data/images/filesave_large.png index a39b55a6166b..3fc796d6f39e 100644 Binary files a/lib/matplotlib/mpl-data/images/filesave_large.png and b/lib/matplotlib/mpl-data/images/filesave_large.png differ diff --git a/lib/matplotlib/mpl-data/images/forward.pdf b/lib/matplotlib/mpl-data/images/forward.pdf index ce4a1bcb1321..55de41cd0cc3 100644 Binary files a/lib/matplotlib/mpl-data/images/forward.pdf and b/lib/matplotlib/mpl-data/images/forward.pdf differ diff --git a/lib/matplotlib/mpl-data/images/forward.png b/lib/matplotlib/mpl-data/images/forward.png index 59400feb49df..d875cdcb4d30 100644 Binary files a/lib/matplotlib/mpl-data/images/forward.png and b/lib/matplotlib/mpl-data/images/forward.png differ diff --git a/lib/matplotlib/mpl-data/images/forward.svg b/lib/matplotlib/mpl-data/images/forward.svg index 08b6fe174602..27b50728f29c 100644 --- a/lib/matplotlib/mpl-data/images/forward.svg +++ b/lib/matplotlib/mpl-data/images/forward.svg @@ -1,46 +1,7 @@ - - - - - - - - - - - - - - - + + + + + + diff --git a/lib/matplotlib/mpl-data/images/forward_large.png b/lib/matplotlib/mpl-data/images/forward_large.png index de65815bba20..227186ce9d1c 100644 Binary files a/lib/matplotlib/mpl-data/images/forward_large.png and b/lib/matplotlib/mpl-data/images/forward_large.png differ diff --git a/lib/matplotlib/mpl-data/images/help.pdf b/lib/matplotlib/mpl-data/images/help.pdf index 38178d05b272..756181678e7d 100644 Binary files a/lib/matplotlib/mpl-data/images/help.pdf and b/lib/matplotlib/mpl-data/images/help.pdf differ diff --git a/lib/matplotlib/mpl-data/images/help.png b/lib/matplotlib/mpl-data/images/help.png index a52fbbe819e2..55c27e48361a 100644 Binary files a/lib/matplotlib/mpl-data/images/help.png and b/lib/matplotlib/mpl-data/images/help.png differ diff --git a/lib/matplotlib/mpl-data/images/help.svg b/lib/matplotlib/mpl-data/images/help.svg index 260528d607aa..e6161d1cb197 100644 --- a/lib/matplotlib/mpl-data/images/help.svg +++ b/lib/matplotlib/mpl-data/images/help.svg @@ -1,52 +1,7 @@ - - - - - - - - - - - - - - - + + + + + + diff --git a/lib/matplotlib/mpl-data/images/help_large.png b/lib/matplotlib/mpl-data/images/help_large.png index 3f3d4dfed7e9..92c7258339d7 100644 Binary files a/lib/matplotlib/mpl-data/images/help_large.png and b/lib/matplotlib/mpl-data/images/help_large.png differ diff --git a/lib/matplotlib/mpl-data/images/home.pdf b/lib/matplotlib/mpl-data/images/home.pdf index f9c6439b9b84..47d22ab7d669 100644 Binary files a/lib/matplotlib/mpl-data/images/home.pdf and b/lib/matplotlib/mpl-data/images/home.pdf differ diff --git a/lib/matplotlib/mpl-data/images/home.png b/lib/matplotlib/mpl-data/images/home.png index 6e5fdebb357c..ad63d5a40144 100644 Binary files a/lib/matplotlib/mpl-data/images/home.png and b/lib/matplotlib/mpl-data/images/home.png differ diff --git a/lib/matplotlib/mpl-data/images/home.svg b/lib/matplotlib/mpl-data/images/home.svg index db140d43d156..ba27209b12c1 100644 --- a/lib/matplotlib/mpl-data/images/home.svg +++ b/lib/matplotlib/mpl-data/images/home.svg @@ -1,59 +1,7 @@ - - - - - - - - - - - - - - - + + + + + + diff --git a/lib/matplotlib/mpl-data/images/home_large.png b/lib/matplotlib/mpl-data/images/home_large.png index 3357bfeb9011..e0860d2b1a20 100644 Binary files a/lib/matplotlib/mpl-data/images/home_large.png and b/lib/matplotlib/mpl-data/images/home_large.png differ diff --git a/lib/matplotlib/mpl-data/images/matplotlib.pdf b/lib/matplotlib/mpl-data/images/matplotlib.pdf index 6c44566ad819..2aa4974e771f 100644 Binary files a/lib/matplotlib/mpl-data/images/matplotlib.pdf and b/lib/matplotlib/mpl-data/images/matplotlib.pdf differ diff --git a/lib/matplotlib/mpl-data/images/matplotlib.png b/lib/matplotlib/mpl-data/images/matplotlib.png index 8eedfa7cc8fc..fa60d7d86c93 100644 Binary files a/lib/matplotlib/mpl-data/images/matplotlib.png and b/lib/matplotlib/mpl-data/images/matplotlib.png differ diff --git a/lib/matplotlib/mpl-data/images/matplotlib.svg b/lib/matplotlib/mpl-data/images/matplotlib.svg index 95d1b61203bf..d87270ac0c81 100644 --- a/lib/matplotlib/mpl-data/images/matplotlib.svg +++ b/lib/matplotlib/mpl-data/images/matplotlib.svg @@ -1,3171 +1,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/mpl-data/images/matplotlib_large.png b/lib/matplotlib/mpl-data/images/matplotlib_large.png deleted file mode 100644 index c7dcfe6cb825..000000000000 Binary files a/lib/matplotlib/mpl-data/images/matplotlib_large.png and /dev/null differ diff --git a/lib/matplotlib/mpl-data/images/matplotlib_small.pdf b/lib/matplotlib/mpl-data/images/matplotlib_small.pdf new file mode 100644 index 000000000000..07dcc1d2d432 Binary files /dev/null and b/lib/matplotlib/mpl-data/images/matplotlib_small.pdf differ diff --git a/lib/matplotlib/mpl-data/images/matplotlib_small.png b/lib/matplotlib/mpl-data/images/matplotlib_small.png new file mode 100644 index 000000000000..b4d29971a583 Binary files /dev/null and b/lib/matplotlib/mpl-data/images/matplotlib_small.png differ diff --git a/lib/matplotlib/mpl-data/images/matplotlib_small.svg b/lib/matplotlib/mpl-data/images/matplotlib_small.svg new file mode 100644 index 000000000000..8911144550c1 --- /dev/null +++ b/lib/matplotlib/mpl-data/images/matplotlib_small.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/matplotlib/mpl-data/images/move.pdf b/lib/matplotlib/mpl-data/images/move.pdf index d883d9224a6e..658a7a63844e 100644 Binary files a/lib/matplotlib/mpl-data/images/move.pdf and b/lib/matplotlib/mpl-data/images/move.pdf differ diff --git a/lib/matplotlib/mpl-data/images/move.png b/lib/matplotlib/mpl-data/images/move.png index 4fbbaef41bba..3646eb4780ce 100644 Binary files a/lib/matplotlib/mpl-data/images/move.png and b/lib/matplotlib/mpl-data/images/move.png differ diff --git a/lib/matplotlib/mpl-data/images/move.svg b/lib/matplotlib/mpl-data/images/move.svg index f7e23ab0451c..993261877005 100644 --- a/lib/matplotlib/mpl-data/images/move.svg +++ b/lib/matplotlib/mpl-data/images/move.svg @@ -1,73 +1,7 @@ - - - - - - - - - - - - - - - - + + + + + + + \ No newline at end of file diff --git a/lib/matplotlib/mpl-data/images/move_large.png b/lib/matplotlib/mpl-data/images/move_large.png index 96351c115f0b..915a4ddcf53a 100644 Binary files a/lib/matplotlib/mpl-data/images/move_large.png and b/lib/matplotlib/mpl-data/images/move_large.png differ diff --git a/lib/matplotlib/mpl-data/images/qt4_editor_options.pdf b/lib/matplotlib/mpl-data/images/qt4_editor_options.pdf index a92f2cc3aaee..0637424888db 100644 Binary files a/lib/matplotlib/mpl-data/images/qt4_editor_options.pdf and b/lib/matplotlib/mpl-data/images/qt4_editor_options.pdf differ diff --git a/lib/matplotlib/mpl-data/images/qt4_editor_options.png b/lib/matplotlib/mpl-data/images/qt4_editor_options.png index 792ec81244e3..d7aa16bd8219 100644 Binary files a/lib/matplotlib/mpl-data/images/qt4_editor_options.png and b/lib/matplotlib/mpl-data/images/qt4_editor_options.png differ diff --git a/lib/matplotlib/mpl-data/images/qt4_editor_options.svg b/lib/matplotlib/mpl-data/images/qt4_editor_options.svg index 02adfbc4ae11..acc08fb3ea94 100644 --- a/lib/matplotlib/mpl-data/images/qt4_editor_options.svg +++ b/lib/matplotlib/mpl-data/images/qt4_editor_options.svg @@ -1,48 +1,7 @@ - - - - - - - - - - - - - - - + + + + + + diff --git a/lib/matplotlib/mpl-data/images/qt4_editor_options_large.png b/lib/matplotlib/mpl-data/images/qt4_editor_options_large.png index 46d52c91c9bf..669443612fd4 100644 Binary files a/lib/matplotlib/mpl-data/images/qt4_editor_options_large.png and b/lib/matplotlib/mpl-data/images/qt4_editor_options_large.png differ diff --git a/lib/matplotlib/mpl-data/images/subplots.pdf b/lib/matplotlib/mpl-data/images/subplots.pdf index f404665579a0..374a86a357fe 100644 Binary files a/lib/matplotlib/mpl-data/images/subplots.pdf and b/lib/matplotlib/mpl-data/images/subplots.pdf differ diff --git a/lib/matplotlib/mpl-data/images/subplots.png b/lib/matplotlib/mpl-data/images/subplots.png index bb0318c40e72..601a03ca746b 100644 Binary files a/lib/matplotlib/mpl-data/images/subplots.png and b/lib/matplotlib/mpl-data/images/subplots.png differ diff --git a/lib/matplotlib/mpl-data/images/subplots.svg b/lib/matplotlib/mpl-data/images/subplots.svg index 9a0fa90972ff..aec67ebaa37b 100644 --- a/lib/matplotlib/mpl-data/images/subplots.svg +++ b/lib/matplotlib/mpl-data/images/subplots.svg @@ -1,81 +1,9 @@ - - - - - - - - - - - - - - - + + + + + + + + diff --git a/lib/matplotlib/mpl-data/images/subplots_large.png b/lib/matplotlib/mpl-data/images/subplots_large.png index 4440af1759ee..cee7f070e572 100644 Binary files a/lib/matplotlib/mpl-data/images/subplots_large.png and b/lib/matplotlib/mpl-data/images/subplots_large.png differ diff --git a/lib/matplotlib/mpl-data/images/zoom_to_rect.pdf b/lib/matplotlib/mpl-data/images/zoom_to_rect.pdf index 22add33bb8f9..34a938e7171f 100644 Binary files a/lib/matplotlib/mpl-data/images/zoom_to_rect.pdf and b/lib/matplotlib/mpl-data/images/zoom_to_rect.pdf differ diff --git a/lib/matplotlib/mpl-data/images/zoom_to_rect.png b/lib/matplotlib/mpl-data/images/zoom_to_rect.png index 12afa252481c..51684384800a 100644 Binary files a/lib/matplotlib/mpl-data/images/zoom_to_rect.png and b/lib/matplotlib/mpl-data/images/zoom_to_rect.png differ diff --git a/lib/matplotlib/mpl-data/images/zoom_to_rect.svg b/lib/matplotlib/mpl-data/images/zoom_to_rect.svg index 44e59a8a4fdc..ac07a910f2a9 100644 --- a/lib/matplotlib/mpl-data/images/zoom_to_rect.svg +++ b/lib/matplotlib/mpl-data/images/zoom_to_rect.svg @@ -1,40 +1,6 @@ - - - - - - - - - - - - - - - + + + + + diff --git a/lib/matplotlib/mpl-data/images/zoom_to_rect_large.png b/lib/matplotlib/mpl-data/images/zoom_to_rect_large.png index 5963603bb681..9162c6698e30 100644 Binary files a/lib/matplotlib/mpl-data/images/zoom_to_rect_large.png and b/lib/matplotlib/mpl-data/images/zoom_to_rect_large.png differ diff --git a/tools/make_icons.py b/tools/make_icons.py index b253c0517c43..d2794baf6c1f 100755 --- a/tools/make_icons.py +++ b/tools/make_icons.py @@ -1,120 +1,249 @@ #!/usr/bin/env python """ -Generates the Matplotlib icon, and the toolbar icon images from the FontAwesome -font. - -Generates SVG, PDF in one size (since they are vectors), and PNG in 24x24 and -48x48. +Generates PDF and PNG variants of the Matplotlib app icon and toolbar icons. """ -from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter -from io import BytesIO +# ------------------------------------------------------------------------------------ +# +# Toolbar Icon Notes: +# +# Historically, the toolbar icons were generated directly from the FontAwesome 4.7 +# font (released under the SIL OFL 1.1 license): +# +# Code Point | Icon Name +# ------------+--------------------- +# 0xf015 | home +# 0xf060 | back +# 0xf061 | forward +# 0xf002 | zoom_to_rect +# 0xf047 | move +# 0xf0c7 | filesave +# 0xf1de | subplots +# 0xf201 | qt4_editor_options +# 0xf128 | help +# +# This resulted in fuzzy edges (due to scaling) and vertical misalignment. +# +# The glyphs were redrawn by hand and pixel-aligned to a 48x48 canvas. +# +# ------------------------------------------------------------------------------------ +# +# App Icon Notes: +# +# The app icons were generated using a modified logos2.py. +# +# For the 256px icon, the following parameters were used: +# height_px=256, lw_bars=1.5, lw_grid=2, lw_border=4, rgrid=[1, 3, 5, 7], +# dpi=72, ax_positions=(0.0891, 0.0891, 0.8218, 0.8218) +# +# For the 32px icon: +# height_px=32, lw_bars=1, lw_grid=0, lw_border=0, rgrid=[1, 3, 5, 7], +# dpi=72, ax_positions=(0.0891, 0.0891, 0.8218, 0.8218) +# +# Both matplotlib.svg and matplotlib_small.svg were manually cleaned up and +# optimized using svgo +# +# ------------------------------------------------------------------------------------ +# +# Conversion and Compression Notes: +# +# This script requires `inkscape` and `optipng` binaries. These were chosen due to +# being dependencies of the documentation build process. +# +# Optionally, this script uses `pngcrush` if present to further optimize PNG files. +# +# Output PNG files have the following pixel dimensions: +# +# matplotlib.png 256 +# matplotlib_small.png 32 +# {toolbar_name}.png 24 +# {toolbar_name}_large.png 48 +# +# ------------------------------------------------------------------------------------ + +import argparse +import subprocess +import shutil from pathlib import Path -import tarfile -import urllib.request - -import matplotlib as mpl -import matplotlib.pyplot as plt -import numpy as np - - -plt.rcdefaults() -plt.rcParams['svg.fonttype'] = 'path' -plt.rcParams['pdf.fonttype'] = 3 -plt.rcParams['pdf.compression'] = 9 - - -def get_fontawesome(): - cached_path = Path(mpl.get_cachedir(), "FontAwesome.otf") - if not cached_path.exists(): - with urllib.request.urlopen( - "https://github.com/FortAwesome/Font-Awesome" - "/archive/v4.7.0.tar.gz") as req, \ - tarfile.open(fileobj=BytesIO(req.read()), mode="r:gz") as tf: - cached_path.write_bytes(tf.extractfile(tf.getmember( - "Font-Awesome-4.7.0/fonts/FontAwesome.otf")).read()) - return cached_path - - -def save_icon(fig, dest_dir, name, add_black_fg_color): - if add_black_fg_color: - # Add explicit black foreground color to monochromatic svg icons - # so it can be replaced by backends to add dark theme support - svg_bytes_io = BytesIO() - fig.savefig(svg_bytes_io, format='svg') - svg = svg_bytes_io.getvalue() - before, sep, after = svg.rpartition(b'\nz\n"') - svg = before + sep + b' style="fill:black;"' + after - (dest_dir / (name + '.svg')).write_bytes(svg) - else: - fig.savefig(dest_dir / (name + '.svg')) - fig.savefig(dest_dir / (name + '.pdf')) - for dpi, suffix in [(24, ''), (48, '_large')]: - fig.savefig(dest_dir / (name + suffix + '.png'), dpi=dpi) - - -def make_icon(font_path, ccode): - fig = plt.figure(figsize=(1, 1)) - fig.patch.set_alpha(0.0) - fig.text(0.5, 0.48, chr(ccode), ha='center', va='center', - font=font_path, fontsize=68) - return fig - - -def make_matplotlib_icon(): - fig = plt.figure(figsize=(1, 1)) - fig.patch.set_alpha(0.0) - ax = fig.add_axes((0.025, 0.025, 0.95, 0.95), projection='polar') - ax.set_axisbelow(True) - - N = 7 - arc = 2 * np.pi - theta = np.arange(0, arc, arc / N) - radii = 10 * np.array([0.2, 0.6, 0.8, 0.7, 0.4, 0.5, 0.8]) - width = np.pi / 4 * np.array([0.4, 0.4, 0.6, 0.8, 0.2, 0.5, 0.3]) - bars = ax.bar(theta, radii, width=width, bottom=0.0, linewidth=1, - edgecolor='k') - - for r, bar in zip(radii, bars): - bar.set_facecolor(mpl.cm.jet(r / 10)) - - ax.tick_params(labelleft=False, labelright=False, - labelbottom=False, labeltop=False) - ax.grid(lw=0.0) - - ax.set_yticks(np.arange(1, 9, 2)) - ax.set_rmax(9) - - return fig - - -icon_defs = [ - ('home', 0xf015), - ('back', 0xf060), - ('forward', 0xf061), - ('zoom_to_rect', 0xf002), - ('move', 0xf047), - ('filesave', 0xf0c7), - ('subplots', 0xf1de), - ('qt4_editor_options', 0xf201), - ('help', 0xf128), + +from lxml import etree +import pikepdf +from PIL import Image + + +INKSCAPE_BIN = "inkscape" +OPTIPNG_BIN = "optipng" + +DEFAULT_IMAGES_PATH = "../lib/matplotlib/mpl-data/images" + +TOOLBAR_ICON_NAMES = [ + "home", + "back", + "forward", + "zoom_to_rect", + "move", + "filesave", + "subplots", + "qt4_editor_options", + "help" ] -def make_icons(): - parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) +def run_command(*args: str) -> None: + """Run a subprocess and raise RuntimeError with stderr on failure.""" + try: + subprocess.run(args, check=True, capture_output=True, text=True) + except subprocess.CalledProcessError as err: + raise RuntimeError(err.stderr.strip()) from err + + +def run_inkscape(actions: list[str]) -> None: + actions_str = "; ".join(actions) + ";" + run_command(INKSCAPE_BIN, "--actions", actions_str) + + +def optimize_png(png_path: Path) -> None: + """Removes all metadata from a PNG file and then runs `optipng`""" + img = Image.open(png_path) + img.save(png_path) # Should save without metadata + run_command(OPTIPNG_BIN, str(png_path)) + + +def optimize_pdf(pdf_path: Path) -> None: + """Removes all metadata from a PDF file.""" + with pikepdf.Pdf.open(pdf_path, allow_overwriting_input=True) as pdf: + # Each PDF gets a fresh CreationDate when we run make_icons.py + # This dirties the git working tree directory, so strip all metadata + # that could change. + for key in ("/CreationDate", "/ModDate", "/Creator", "/Producer"): + if key in pdf.docinfo: + del pdf.docinfo[key] + + if "/Metadata" in pdf.Root: + del pdf.Root["/Metadata"] + + pdf.save(pdf_path, deterministic_id=True) + + +def has_black_fill_group(svg_path: Path) -> bool: + """Checks that the SVG file has a root-level group with 'fill:black'""" + tree = etree.parse(svg_path) + root = tree.getroot() + for child in root: + if etree.QName(child).localname != "g": + continue + style = child.get("style", "") + declarations = [d.strip() for d in style.split(";") if d.strip()] + if "fill:black" in declarations or style.strip() == "fill:black": + return True + return False + + +def process_toolbar_icons( + source_dir: Path, + dest_dir: Path, + png_paths: list[Path], + pdf_paths: list[Path], +) -> None: + actions = [] + + def add_actions(name: str) -> None: + svg_path = source_dir / f"{name}.svg" + pdf_path = dest_dir / f"{name}.pdf" + png_large_path = dest_dir / f"{name}_large.png" + png_small_path = dest_dir / f"{name}.png" + + if not has_black_fill_group(svg_path): + raise ValueError( + f"SVG file missing with style='fill:black': {svg_path}") + + actions.extend([ + f"file-open:{svg_path}", + f"export-filename:{pdf_path}; export-do", + f"export-filename:{png_large_path}; " + f"export-png-color-mode:GrayAlpha_8; " + f"export-width:48; export-height:48; export-do", + f"export-filename:{png_small_path}; " + f"export-png-color-mode:GrayAlpha_8; " + f"export-width:24; export-height:24; export-do", + ]) + + png_paths.extend([png_small_path, png_large_path]) + pdf_paths.append(pdf_path) + + for toolbar_icon_name in TOOLBAR_ICON_NAMES: + add_actions(toolbar_icon_name) + + run_inkscape(actions) + + +def process_matplotlib_icons( + source_dir: Path, + dest_dir: Path, + png_paths: list[Path], + pdf_paths: list[Path] +) -> None: + actions = [] + + def add_actions(name: str) -> None: + svg_path = source_dir / f"{name}.svg" + pdf_path = dest_dir / f"{name}.pdf" + png_path = dest_dir / f"{name}.png" + + actions.extend([ + f"file-open:{svg_path}", + f"export-filename:{pdf_path}; export-do;", + f"export-filename:{png_path}; export-do;" + ]) + + png_paths.append(png_path) + pdf_paths.append(pdf_path) + + add_actions("matplotlib") + add_actions("matplotlib_small") + + run_inkscape(actions) + + +def make_icons() -> None: + parser = argparse.ArgumentParser( + description="Validate and convert SVG icons to PNG/PDF.") + + parser.add_argument( + "-s", "--source-dir", + type=Path, + default=Path(__file__).parent / DEFAULT_IMAGES_PATH, + help="Directory where to read the SVG files.") parser.add_argument( "-d", "--dest-dir", type=Path, - default=Path(__file__).parent / "../lib/matplotlib/mpl-data/images", - help="Directory where to store the images.") + default=Path(__file__).parent / DEFAULT_IMAGES_PATH, + help="Directory where to write the PNG/PDF files.") args = parser.parse_args() - font_path = get_fontawesome() - for name, ccode in icon_defs: - fig = make_icon(font_path, ccode) - save_icon(fig, args.dest_dir, name, True) - fig = make_matplotlib_icon() - save_icon(fig, args.dest_dir, 'matplotlib', False) + + source_dir = args.source_dir + dest_dir = args.dest_dir + + if not source_dir.is_dir(): + raise NotADirectoryError(f"Not a directory: {source_dir}") + if not dest_dir.is_dir(): + raise NotADirectoryError(f"Not a directory: {dest_dir}") + + if shutil.which(INKSCAPE_BIN) is None: + raise FileNotFoundError(f"Could not locate the `{INKSCAPE_BIN}` binary") + if shutil.which(OPTIPNG_BIN) is None: + raise FileNotFoundError(f"Could not locate the `{OPTIPNG_BIN}` binary") + + png_paths = [] + pdf_paths = [] + process_toolbar_icons(source_dir, dest_dir, png_paths, pdf_paths) + process_matplotlib_icons(source_dir, dest_dir, png_paths, pdf_paths) + + for png_path in png_paths: + optimize_png(png_path) + + for pdf_path in pdf_paths: + optimize_pdf(pdf_path) if __name__ == "__main__":