From bc6e367bb5a7c33519ef4e73aa79df10699cd4b5 Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Tue, 25 Oct 2016 11:13:00 -0500 Subject: [PATCH 01/21] Allow to store metadata in png files --- src/_png.cpp | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/_png.cpp b/src/_png.cpp index 955a2188af75..e93bcf641e3e 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -124,10 +124,16 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) { numpy::array_view buffer; PyObject *filein; + PyObject *metadata; + PyObject *meta_key, *meta_val; + png_text *text; + Py_ssize_t pos = 0; + int meta_pos = 0; + Py_ssize_t meta_size; double dpi = 0; int compression = 6; int filter = -1; - const char *names[] = { "buffer", "file", "dpi", "compression", "filter", NULL }; + const char *names[] = { "buffer", "file", "dpi", "compression", "filter", "metadata", NULL }; // We don't need strict contiguity, just for each row to be // contiguous, and libpng has special handling for getting RGB out @@ -135,14 +141,15 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) // enforce contiguity using array_view::converter_contiguous. if (!PyArg_ParseTupleAndKeywords(args, kwds, - "O&O|dii:write_png", + "O&O|diiO:write_png", (char **)names, &buffer.converter_contiguous, &buffer, &filein, &dpi, &compression, - &filter)) { + &filter, + &metadata)) { return NULL; } @@ -276,6 +283,36 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) png_set_pHYs(png_ptr, info_ptr, dots_per_meter, dots_per_meter, PNG_RESOLUTION_METER); } +#ifdef PNG_TEXT_SUPPORTED + // Save the metadata + if (metadata != NULL) + { + meta_size = PyDict_Size(metadata); + text = new png_text[meta_size]; + + printf("meta_size = %d\n", meta_size); + while (PyDict_Next(metadata, &pos, &meta_key, &meta_val)) { + printf("pos = %i\n", meta_pos); + printf("key = %s\n", PyBytes_AsString(meta_key)); + printf("val = %s\n", PyBytes_AsString(meta_val)); + text[meta_pos].compression = PNG_TEXT_COMPRESSION_NONE; +#if PY3K + text[meta_pos].key = PyBytes_AsString(meta_key); + text[meta_pos].text = PyBytes_AsString(meta_val); +#else + text[meta_pos].key = PyString_AsString(meta_key); + text[meta_pos].text = PyString_AsString(meta_val); +#endif +#ifdef PNG_iTXt_SUPPORTED + text[meta_pos].lang = NULL; +#endif + meta_pos++; + } + png_set_text(png_ptr, info_ptr, text, meta_size); + delete[] text; + } +#endif + sig_bit.alpha = 0; switch (png_color_type) { case PNG_COLOR_TYPE_GRAY: From 5200afff883c6ab86dd53a8aaeef7a7d4d9c6c34 Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Tue, 25 Oct 2016 11:45:34 -0500 Subject: [PATCH 02/21] Ensure that metadata pointer is initially set to NULL --- src/_png.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_png.cpp b/src/_png.cpp index e93bcf641e3e..b8954af61378 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -124,7 +124,7 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) { numpy::array_view buffer; PyObject *filein; - PyObject *metadata; + PyObject *metadata = NULL; PyObject *meta_key, *meta_val; png_text *text; Py_ssize_t pos = 0; From 5a5bd9621fe89f35d57f3e6ba6d32abfc722886c Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Tue, 25 Oct 2016 12:44:36 -0500 Subject: [PATCH 03/21] Allow to pass custom infoDict to images created with pdf backend --- lib/matplotlib/backends/backend_pdf.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 8f0a3a295bc1..20c93ed30067 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -428,7 +428,7 @@ def _flush(self): class PdfFile(object): """PDF file object.""" - def __init__(self, filename): + def __init__(self, filename, metadata=None): self.nextObject = 1 # next free object id self.xrefTable = [[0, 65535, 'the zero object']] self.passed_in_file_object = False @@ -482,10 +482,13 @@ def __init__(self, filename): else: source_date = datetime.today() - self.infoDict = { - 'Creator': 'matplotlib %s, http://matplotlib.org' % __version__, - 'Producer': 'matplotlib pdf backend%s' % revision, - 'CreationDate': source_date + if metadata is not None: + self.infoDict = metadata + else: + self.infoDict = { + 'Creator': 'matplotlib %s, http://matplotlib.org' % __version__, + 'Producer': 'matplotlib pdf backend%s' % revision, + 'CreationDate': source_date } self.fontNames = {} # maps filenames to internal font names @@ -2438,7 +2441,7 @@ class PdfPages(object): """ __slots__ = ('_file', 'keep_empty') - def __init__(self, filename, keep_empty=True): + def __init__(self, filename, keep_empty=True, metadata=None): """ Create a new PdfPages object. @@ -2452,8 +2455,11 @@ def __init__(self, filename, keep_empty=True): keep_empty: bool, optional If set to False, then empty pdf files will be deleted automatically when closed. + metadata: dictionary, optional + Information dictionary object (see PDF reference section 10.2.1 + 'Document Information Dictionary'). """ - self._file = PdfFile(filename) + self._file = PdfFile(filename, metadata=metadata) self.keep_empty = keep_empty def __enter__(self): @@ -2556,7 +2562,7 @@ def print_pdf(self, filename, **kwargs): if isinstance(filename, PdfPages): file = filename._file else: - file = PdfFile(filename) + file = PdfFile(filename, metadata=kwargs.pop("metadata", None)) try: file.newPage(width, height) _bbox_inches_restore = kwargs.pop("bbox_inches_restore", None) From d8cac234387c1bc50b4686b9568b4f5e43149957 Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Tue, 25 Oct 2016 12:47:45 -0500 Subject: [PATCH 04/21] Allow to pass custom metadata to png images created with agg backend --- lib/matplotlib/backends/backend_agg.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 7a19aebc3a29..54a1f6cf14c8 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -27,7 +27,7 @@ import threading import numpy as np from math import radians, cos, sin -from matplotlib import verbose, rcParams +from matplotlib import verbose, rcParams, __version__ from matplotlib.backend_bases import (RendererBase, FigureManagerBase, FigureCanvasBase) from matplotlib.cbook import is_string_like, maxdict, restrict_dict @@ -554,8 +554,14 @@ def print_png(self, filename_or_obj, *args, **kwargs): else: close = False + metadata = kwargs.pop("metadata", None) + if metadata is None: + version_str = 'matplotlib version ' + __version__ + \ + ', http://matplotlib.org/' + metadata = {six.b('software'): six.b(version_str)} try: - _png.write_png(renderer._renderer, filename_or_obj, self.figure.dpi) + _png.write_png(renderer._renderer, filename_or_obj, self.figure.dpi, + metadata=metadata) finally: if close: filename_or_obj.close() From 83058684de4943fe72ac00ed060898b93beb27eb Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Tue, 25 Oct 2016 13:04:24 -0500 Subject: [PATCH 05/21] Allow to pass custom Creator to images created with ps backend --- lib/matplotlib/backends/backend_ps.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 871f2a95b927..5788b67ef92f 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -1059,13 +1059,19 @@ def write(self, *kl, **kwargs): self.figure.set_facecolor(origfacecolor) self.figure.set_edgecolor(origedgecolor) + # check for custom metadata + metadata = kwargs.pop("metadata", None) + if metadata is not None and 'Creator' in metadata: + creator_str = metadata['Creator'] + else: + creator_str = "matplotlib version " + __version__ + \ + ", http://matplotlib.org/" def print_figure_impl(): # write the PostScript headers if isEPSF: print("%!PS-Adobe-3.0 EPSF-3.0", file=fh) else: print("%!PS-Adobe-3.0", file=fh) if title: print("%%Title: "+title, file=fh) - print(("%%Creator: matplotlib version " - +__version__+", http://matplotlib.org/"), file=fh) + print("%%Creator: " + creator_str, file=fh) # get source date from SOURCE_DATE_EPOCH, if set # See https://reproducible-builds.org/specs/source-date-epoch/ source_date_epoch = os.getenv("SOURCE_DATE_EPOCH") @@ -1249,12 +1255,21 @@ def write(self, *kl, **kwargs): self.figure.set_facecolor(origfacecolor) self.figure.set_edgecolor(origedgecolor) + # check for custom metadata + metadata = kwargs.pop("metadata", None) + if metadata is not None and 'Creator' in metadata: + creator_str = metadata['Creator'] + else: + creator_str = "matplotlib version " + __version__ + \ + ", http://matplotlib.org/" + # write to a temp file, we'll move it to outfile when done fd, tmpfile = mkstemp() with io.open(fd, 'w', encoding='latin-1') as fh: # write the Encapsulated PostScript headers print("%!PS-Adobe-3.0 EPSF-3.0", file=fh) if title: print("%%Title: "+title, file=fh) +<<<<<<< d8cac234387c1bc50b4686b9568b4f5e43149957 print(("%%Creator: matplotlib version " +__version__+", http://matplotlib.org/"), file=fh) # get source date from SOURCE_DATE_EPOCH, if set @@ -1266,6 +1281,10 @@ def write(self, *kl, **kwargs): else: source_date = time.ctime() print("%%CreationDate: "+source_date, file=fh) +======= + print("%%Creator: " + creator_str, file=fh) + print("%%CreationDate: "+time.ctime(time.time()), file=fh) +>>>>>>> Allow to pass custom Creator to images created with ps backend print("%%%%BoundingBox: %d %d %d %d" % bbox, file=fh) print("%%EndComments", file=fh) From 0d814e0f6969a19fa98363fe4d163f8b26ea8578 Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Tue, 25 Oct 2016 13:29:24 -0500 Subject: [PATCH 06/21] 'Software' starts with capital letter as per PNG specification --- lib/matplotlib/backends/backend_agg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 54a1f6cf14c8..f36c571c079b 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -558,7 +558,7 @@ def print_png(self, filename_or_obj, *args, **kwargs): if metadata is None: version_str = 'matplotlib version ' + __version__ + \ ', http://matplotlib.org/' - metadata = {six.b('software'): six.b(version_str)} + metadata = {six.b('Software'): six.b(version_str)} try: _png.write_png(renderer._renderer, filename_or_obj, self.figure.dpi, metadata=metadata) From 112e38a29401cc010521cc4b5b366b5bdf5da697 Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Tue, 25 Oct 2016 14:18:12 -0500 Subject: [PATCH 07/21] fix pep8 issue --- lib/matplotlib/backends/backend_pdf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 20c93ed30067..7891689f0629 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -486,7 +486,8 @@ def __init__(self, filename, metadata=None): self.infoDict = metadata else: self.infoDict = { - 'Creator': 'matplotlib %s, http://matplotlib.org' % __version__, + 'Creator': 'matplotlib ' + __version__ + + ', http://matplotlib.org', 'Producer': 'matplotlib pdf backend%s' % revision, 'CreationDate': source_date } From 0f006e30a8469c1484011d67eab9e1eb84b3b08f Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Tue, 25 Oct 2016 14:19:40 -0500 Subject: [PATCH 08/21] Drop debug statements --- src/_png.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/_png.cpp b/src/_png.cpp index b8954af61378..1e9858987510 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -290,11 +290,7 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) meta_size = PyDict_Size(metadata); text = new png_text[meta_size]; - printf("meta_size = %d\n", meta_size); while (PyDict_Next(metadata, &pos, &meta_key, &meta_val)) { - printf("pos = %i\n", meta_pos); - printf("key = %s\n", PyBytes_AsString(meta_key)); - printf("val = %s\n", PyBytes_AsString(meta_val)); text[meta_pos].compression = PNG_TEXT_COMPRESSION_NONE; #if PY3K text[meta_pos].key = PyBytes_AsString(meta_key); From 12b0120d8d563572e17b195c969aa0429016fada Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Tue, 25 Oct 2016 16:40:38 -0500 Subject: [PATCH 09/21] Preserve the default values of the metadata. Allow user to update if necessary --- lib/matplotlib/backends/backend_agg.py | 16 +++++++++------- lib/matplotlib/backends/backend_pdf.py | 15 +++++++-------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index f36c571c079b..f1b8d70b0578 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -554,14 +554,16 @@ def print_png(self, filename_or_obj, *args, **kwargs): else: close = False - metadata = kwargs.pop("metadata", None) - if metadata is None: - version_str = 'matplotlib version ' + __version__ + \ - ', http://matplotlib.org/' - metadata = {six.b('Software'): six.b(version_str)} + version_str = 'matplotlib version ' + __version__ + \ + ', http://matplotlib.org/' + metadata = {six.b('Software'): six.b(version_str)} + user_metadata = kwargs.pop("metadata", None) + if user_metadata is not None: + metadata.update(user_metadata) + try: - _png.write_png(renderer._renderer, filename_or_obj, self.figure.dpi, - metadata=metadata) + _png.write_png(renderer._renderer, filename_or_obj, + self.figure.dpi, metadata=metadata) finally: if close: filename_or_obj.close() diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 7891689f0629..86f5349b6451 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -482,15 +482,14 @@ def __init__(self, filename, metadata=None): else: source_date = datetime.today() + self.infoDict = { + 'Creator': 'matplotlib ' + __version__ + + ', http://matplotlib.org', + 'Producer': 'matplotlib pdf backend%s' % revision, + 'CreationDate': source_date + } if metadata is not None: - self.infoDict = metadata - else: - self.infoDict = { - 'Creator': 'matplotlib ' + __version__ + - ', http://matplotlib.org', - 'Producer': 'matplotlib pdf backend%s' % revision, - 'CreationDate': source_date - } + self.infoDict.update(metadata) self.fontNames = {} # maps filenames to internal font names self.nextFont = 1 # next free internal font name From d75b068212f634aa7afcbf07c9a3bcefc93483c3 Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Thu, 27 Oct 2016 09:47:10 -0500 Subject: [PATCH 10/21] Revert accidental changes and fix indentation --- lib/matplotlib/backends/backend_pdf.py | 3 +-- src/_png.cpp | 29 +++++++++++++------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 86f5349b6451..7b3e48853b0d 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -483,8 +483,7 @@ def __init__(self, filename, metadata=None): source_date = datetime.today() self.infoDict = { - 'Creator': 'matplotlib ' + __version__ + - ', http://matplotlib.org', + 'Creator': 'matplotlib %s, http://matplotlib.org' % __version__, 'Producer': 'matplotlib pdf backend%s' % revision, 'CreationDate': source_date } diff --git a/src/_png.cpp b/src/_png.cpp index 1e9858987510..269e16c288ca 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -285,27 +285,26 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) #ifdef PNG_TEXT_SUPPORTED // Save the metadata - if (metadata != NULL) - { - meta_size = PyDict_Size(metadata); - text = new png_text[meta_size]; + if (metadata != NULL) { + meta_size = PyDict_Size(metadata); + text = new png_text[meta_size]; - while (PyDict_Next(metadata, &pos, &meta_key, &meta_val)) { - text[meta_pos].compression = PNG_TEXT_COMPRESSION_NONE; + while (PyDict_Next(metadata, &pos, &meta_key, &meta_val)) { + text[meta_pos].compression = PNG_TEXT_COMPRESSION_NONE; #if PY3K - text[meta_pos].key = PyBytes_AsString(meta_key); - text[meta_pos].text = PyBytes_AsString(meta_val); + text[meta_pos].key = PyBytes_AsString(meta_key); + text[meta_pos].text = PyBytes_AsString(meta_val); #else - text[meta_pos].key = PyString_AsString(meta_key); - text[meta_pos].text = PyString_AsString(meta_val); + text[meta_pos].key = PyString_AsString(meta_key); + text[meta_pos].text = PyString_AsString(meta_val); #endif #ifdef PNG_iTXt_SUPPORTED - text[meta_pos].lang = NULL; + text[meta_pos].lang = NULL; #endif - meta_pos++; - } - png_set_text(png_ptr, info_ptr, text, meta_size); - delete[] text; + meta_pos++; + } + png_set_text(png_ptr, info_ptr, text, meta_size); + delete[] text; } #endif From 92bccd2f76ca0ef4c1f3a634bb4d7b6d6399e8a6 Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Thu, 27 Oct 2016 10:23:02 -0500 Subject: [PATCH 11/21] Handle unicode/bytes directly in the C extension --- lib/matplotlib/backends/backend_agg.py | 2 +- src/_png.cpp | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index f1b8d70b0578..b246a10e8919 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -556,7 +556,7 @@ def print_png(self, filename_or_obj, *args, **kwargs): version_str = 'matplotlib version ' + __version__ + \ ', http://matplotlib.org/' - metadata = {six.b('Software'): six.b(version_str)} + metadata = {'Software': version_str} user_metadata = kwargs.pop("metadata", None) if user_metadata is not None: metadata.update(user_metadata) diff --git a/src/_png.cpp b/src/_png.cpp index 269e16c288ca..f014ad592bae 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -292,8 +292,26 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) while (PyDict_Next(metadata, &pos, &meta_key, &meta_val)) { text[meta_pos].compression = PNG_TEXT_COMPRESSION_NONE; #if PY3K - text[meta_pos].key = PyBytes_AsString(meta_key); - text[meta_pos].text = PyBytes_AsString(meta_val); + if (PyUnicode_Check(meta_key)) { + PyObject *temp_key = PyUnicode_AsEncodedString(meta_key, "ASCII", "strict"); + if (temp_key != NULL) { + text[meta_pos].key = PyBytes_AsString(temp_key); + } + } else if (PyBytes_Check(meta_key)) { + text[meta_pos].key = PyBytes_AsString(meta_key); + } else { + text[meta_pos].key = NULL; // Silently drops entry + } + if (PyUnicode_Check(meta_val)) { + PyObject *temp_val = PyUnicode_AsEncodedString(meta_val, "ASCII", "strict"); + if (temp_val != NULL) { + text[meta_pos].text = PyBytes_AsString(temp_val); + } + } else if (PyBytes_Check(meta_val)) { + text[meta_pos].text = PyBytes_AsString(meta_val); + } else { + text[meta_pos].text = (char *)"Invalid value in metadata"; + } #else text[meta_pos].key = PyString_AsString(meta_key); text[meta_pos].text = PyString_AsString(meta_val); From e174284396cdd82590dc33daf58350caa28b6531 Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Sat, 29 Oct 2016 13:07:54 -0500 Subject: [PATCH 12/21] Use 'latin_1' encoding instead of ascii --- src/_png.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_png.cpp b/src/_png.cpp index f014ad592bae..12879695920d 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -293,7 +293,7 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) text[meta_pos].compression = PNG_TEXT_COMPRESSION_NONE; #if PY3K if (PyUnicode_Check(meta_key)) { - PyObject *temp_key = PyUnicode_AsEncodedString(meta_key, "ASCII", "strict"); + PyObject *temp_key = PyUnicode_AsEncodedString(meta_key, "latin_1", "strict"); if (temp_key != NULL) { text[meta_pos].key = PyBytes_AsString(temp_key); } @@ -303,7 +303,7 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) text[meta_pos].key = NULL; // Silently drops entry } if (PyUnicode_Check(meta_val)) { - PyObject *temp_val = PyUnicode_AsEncodedString(meta_val, "ASCII", "strict"); + PyObject *temp_val = PyUnicode_AsEncodedString(meta_val, "latin_1", "strict"); if (temp_val != NULL) { text[meta_pos].text = PyBytes_AsString(temp_val); } From af4c1d130c1e40d9d7cab5abb0f0cda6e2812e8f Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Sat, 29 Oct 2016 13:10:56 -0500 Subject: [PATCH 13/21] Fix numpydoc format issues --- lib/matplotlib/backends/backend_pdf.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 7b3e48853b0d..1279cf41ec66 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -2447,14 +2447,14 @@ def __init__(self, filename, keep_empty=True, metadata=None): Parameters ---------- - filename: str + filename : str Plots using :meth:`PdfPages.savefig` will be written to a file at this location. The file is opened at once and any older file with the same name is overwritten. - keep_empty: bool, optional + keep_empty : bool, optional If set to False, then empty pdf files will be deleted automatically when closed. - metadata: dictionary, optional + metadata : dictionary, optional Information dictionary object (see PDF reference section 10.2.1 'Document Information Dictionary'). """ @@ -2497,7 +2497,7 @@ def savefig(self, figure=None, **kwargs): Parameters ---------- - figure: :class:`~matplotlib.figure.Figure` or int, optional + figure : :class:`~matplotlib.figure.Figure` or int, optional Specifies what figure is saved to file. If not specified, the active figure is saved. If a :class:`~matplotlib.figure.Figure` instance is provided, this figure is saved. If an int is specified, From 98b5587b9a6e38fc4eea61d5547820d412a5247d Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Sat, 29 Oct 2016 13:22:39 -0500 Subject: [PATCH 14/21] Add example info dictionary --- lib/matplotlib/backends/backend_pdf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 1279cf41ec66..f6fa7ea12345 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -2456,7 +2456,9 @@ def __init__(self, filename, keep_empty=True, metadata=None): when closed. metadata : dictionary, optional Information dictionary object (see PDF reference section 10.2.1 - 'Document Information Dictionary'). + 'Document Information Dictionary'), e.g.: + `{'Creator': 'My software', 'Author': 'Me', + 'Title': 'Awesome fig'}` """ self._file = PdfFile(filename, metadata=metadata) self.keep_empty = keep_empty From a2d2c37d5500064f038bd283a6bdead99d9c4843 Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Sun, 30 Oct 2016 21:46:25 -0500 Subject: [PATCH 15/21] Add a description for the 'metadata' keyword to the docstring --- src/_png.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/_png.cpp b/src/_png.cpp index 12879695920d..b100cd8e291a 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -114,6 +114,24 @@ const char *Py_write_png__doc__ = " If not provided, libpng will try to automatically determine the\n" " best filter on a line-by-line basis.\n" "\n" + "metadata : dictionary\n" + " The keyword-text pairs that are stored as comments in the image.\n" + " Keys must be shorter than 79 chars. The only supported encoding\n" + " for both keywords and values is Latin-1 (ISO 8859-1).\n" + " Examples given in the PNG Specification are:\n" + " - Title: Short (one line) title or caption for image\n" + " - Author: Name of image's creator\n" + " - Description: Description of image (possibly long)\n" + " - Copyright: Copyright notice\n" + " - Creation Time: Time of original image creation\n" + " (usually RFC 1123 format, see below)\n" + " - Software: Software used to create the image\n" + " - Disclaimer: Legal disclaimer\n" + " - Warning: Warning of nature of content\n" + " - Source: Device used to create the image\n" + " - Comment: Miscellaneous comment; conversion\n" + " from other image format\n" + "\n" "Returns\n" "-------\n" "buffer : bytes or None\n" From 06ad9fb090f2a59b5cc421defe0bf78428cba611 Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Sun, 30 Oct 2016 21:51:00 -0500 Subject: [PATCH 16/21] Explicitly define 'metadata' kwarg in '_print_figure' --- lib/matplotlib/backends/backend_ps.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 5788b67ef92f..5160aa271b6f 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -965,7 +965,7 @@ def _print_ps(self, outfile, format, *args, **kwargs): def _print_figure(self, outfile, format, dpi=72, facecolor='w', edgecolor='w', orientation='portrait', isLandscape=False, papertype=None, - **kwargs): + metadata=None, **kwargs): """ Render the figure to hardcopy. Set the figure patch face and edge colors. This is useful because some of the GUIs have a @@ -978,6 +978,9 @@ def _print_figure(self, outfile, format, dpi=72, facecolor='w', edgecolor='w', If outfile is a file object, a stand-alone PostScript file is written into this file object. + + metadata must be a dictionary. Currently, only the value for + the key 'Creator' is used. """ isEPSF = format == 'eps' passed_in_file_object = False @@ -1060,7 +1063,6 @@ def write(self, *kl, **kwargs): self.figure.set_edgecolor(origedgecolor) # check for custom metadata - metadata = kwargs.pop("metadata", None) if metadata is not None and 'Creator' in metadata: creator_str = metadata['Creator'] else: From 6916e776e6d3e04701e86d650f1ed23870041116 Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Sun, 30 Oct 2016 23:40:15 -0500 Subject: [PATCH 17/21] Define 'metadata' as kwarg in _print_figure_tex for consistency --- lib/matplotlib/backends/backend_ps.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 5160aa271b6f..82c22adaadf5 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -1197,12 +1197,15 @@ def do_nothing(): os.chmod(outfile, mode) def _print_figure_tex(self, outfile, format, dpi, facecolor, edgecolor, - orientation, isLandscape, papertype, + orientation, isLandscape, papertype, metadata=None, **kwargs): """ If text.usetex is True in rc, a temporary pair of tex/eps files are created to allow tex to manage the text layout via the PSFrags package. These files are processed to yield the final ps or eps file. + + metadata must be a dictionary. Currently, only the value for + the key 'Creator' is used. """ isEPSF = format == 'eps' if is_string_like(outfile): @@ -1258,7 +1261,6 @@ def write(self, *kl, **kwargs): self.figure.set_edgecolor(origedgecolor) # check for custom metadata - metadata = kwargs.pop("metadata", None) if metadata is not None and 'Creator' in metadata: creator_str = metadata['Creator'] else: From 8bb0ae3360502480ff87e31d3e57a795552a3dae Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Sun, 30 Oct 2016 23:45:07 -0500 Subject: [PATCH 18/21] Use default value for invalid key instead of NULL --- src/_png.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_png.cpp b/src/_png.cpp index b100cd8e291a..f5c25deeb2fc 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -318,7 +318,9 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) } else if (PyBytes_Check(meta_key)) { text[meta_pos].key = PyBytes_AsString(meta_key); } else { - text[meta_pos].key = NULL; // Silently drops entry + char invalid_key[79]; + sprintf(invalid_key,"INVALID KEY %d", meta_pos); + text[meta_pos].key = invalid_key; } if (PyUnicode_Check(meta_val)) { PyObject *temp_val = PyUnicode_AsEncodedString(meta_val, "latin_1", "strict"); From 66f6e2d32b71b1abc7b2482a4d01e4c9262d6b63 Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Wed, 16 Nov 2016 20:04:09 -0600 Subject: [PATCH 19/21] Use OrderedDict for metadata --- lib/matplotlib/backends/backend_agg.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index b246a10e8919..8f3bce4396b5 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -26,6 +26,7 @@ import threading import numpy as np +from collections import OrderedDict from math import radians, cos, sin from matplotlib import verbose, rcParams, __version__ from matplotlib.backend_bases import (RendererBase, FigureManagerBase, @@ -556,7 +557,7 @@ def print_png(self, filename_or_obj, *args, **kwargs): version_str = 'matplotlib version ' + __version__ + \ ', http://matplotlib.org/' - metadata = {'Software': version_str} + metadata = OrderedDict({'Software': version_str}) user_metadata = kwargs.pop("metadata", None) if user_metadata is not None: metadata.update(user_metadata) From 8897f0fb8c359a767ca6b40c10f109a05fb39a55 Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Wed, 16 Nov 2016 21:36:14 -0600 Subject: [PATCH 20/21] Add 'what is new' entry for metadata kwarg in matplotlib.pyplot.savefig --- .../whats_new/metadata_savefig_kwarg.rst | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 doc/users/whats_new/metadata_savefig_kwarg.rst diff --git a/doc/users/whats_new/metadata_savefig_kwarg.rst b/doc/users/whats_new/metadata_savefig_kwarg.rst new file mode 100644 index 000000000000..3167cac2c4f9 --- /dev/null +++ b/doc/users/whats_new/metadata_savefig_kwarg.rst @@ -0,0 +1,20 @@ +Metadata savefig kwarg +---------------------- + +:func:`~matplotlib.pyplot.savefig` now accepts `metadata` as a keyword argument. +It can be used to store key/value pairs in the image metadata. + +Supported formats and backends +`````````````````````````````` +* 'png' with Agg backend +* 'pdf' with PDF backend (see + :func:`~matplotlib.backends.backend_pdf.PdfFile.writeInfoDict` for a list of + supported keywords) +* 'eps' and 'ps' with PS backend (only 'Creator' key is accepted) + +Example +``````` +:: + + plt.savefig('test.png', metadata={'Software': 'My awesome software'}) + From 935e02ff5fc9bc22e45870b815ef33ba9353ab79 Mon Sep 17 00:00:00 2001 From: "Kacper Kowalik (Xarthisius)" Date: Sun, 25 Dec 2016 12:11:14 -0600 Subject: [PATCH 21/21] Fix merge --- lib/matplotlib/backends/backend_ps.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 82c22adaadf5..d5591049578c 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -1273,9 +1273,7 @@ def write(self, *kl, **kwargs): # write the Encapsulated PostScript headers print("%!PS-Adobe-3.0 EPSF-3.0", file=fh) if title: print("%%Title: "+title, file=fh) -<<<<<<< d8cac234387c1bc50b4686b9568b4f5e43149957 - print(("%%Creator: matplotlib version " - +__version__+", http://matplotlib.org/"), file=fh) + print("%%Creator: " + creator_str, file=fh) # get source date from SOURCE_DATE_EPOCH, if set # See https://reproducible-builds.org/specs/source-date-epoch/ source_date_epoch = os.getenv("SOURCE_DATE_EPOCH") @@ -1285,10 +1283,6 @@ def write(self, *kl, **kwargs): else: source_date = time.ctime() print("%%CreationDate: "+source_date, file=fh) -======= - print("%%Creator: " + creator_str, file=fh) - print("%%CreationDate: "+time.ctime(time.time()), file=fh) ->>>>>>> Allow to pass custom Creator to images created with ps backend print("%%%%BoundingBox: %d %d %d %d" % bbox, file=fh) print("%%EndComments", file=fh)