From fa3b8ecbc28477f39ebc1c230c6178b1d6d64aa1 Mon Sep 17 00:00:00 2001
From: Dongdong Tian <seisman.info@gmail.com>
Date: Wed, 23 Oct 2024 10:55:59 +0800
Subject: [PATCH 1/3] Move psconvert to a separate file

---
 pygmt/figure.py        | 133 +----------------------------------------
 pygmt/src/__init__.py  |   1 +
 pygmt/src/psconvert.py | 133 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 136 insertions(+), 131 deletions(-)
 create mode 100644 pygmt/src/psconvert.py

diff --git a/pygmt/figure.py b/pygmt/figure.py
index 792328ab422..905222b1b97 100644
--- a/pygmt/figure.py
+++ b/pygmt/figure.py
@@ -18,14 +18,7 @@
 import numpy as np
 from pygmt.clib import Session
 from pygmt.exceptions import GMTError, GMTInvalidInput
-from pygmt.helpers import (
-    build_arg_list,
-    fmt_docstring,
-    kwargs_to_strings,
-    launch_external_viewer,
-    unique_name,
-    use_alias,
-)
+from pygmt.helpers import launch_external_viewer, unique_name
 
 
 def _get_default_display_method() -> Literal["external", "notebook", "none"]:
@@ -142,129 +135,6 @@ def region(self) -> np.ndarray:
             wesn = lib.extract_region()
         return wesn
 
-    @fmt_docstring
-    @use_alias(
-        A="crop",
-        C="gs_option",
-        E="dpi",
-        F="prefix",
-        G="gs_path",
-        I="resize",
-        N="bb_style",
-        T="fmt",
-        Q="anti_aliasing",
-        V="verbose",
-    )
-    @kwargs_to_strings()
-    def psconvert(self, **kwargs):
-        r"""
-        Convert [E]PS file(s) to other formats.
-
-        Converts one or more PostScript files to other formats (BMP, EPS, JPEG,
-        PDF, PNG, PPM, TIFF) using Ghostscript.
-
-        If no input files are given, will convert the current active figure
-        (see :class:`pygmt.Figure`). In this case, an output name must be given
-        using parameter ``prefix``.
-
-        Full option list at :gmt-docs:`psconvert.html`
-
-        {aliases}
-
-        Parameters
-        ----------
-        crop : str or bool
-            Adjust the BoundingBox and HiResBoundingBox to the minimum
-            required by the image content. Default is ``True``. Append **+u** to
-            first remove any GMT-produced time-stamps. Append **+r** to
-            *round* the HighResBoundingBox instead of using the ``ceil``
-            function. This is going against Adobe Law but can be useful when
-            creating very small images where the difference of one pixel
-            might matter. If ``verbose`` is used we also report the
-            dimensions of the final illustration.
-        gs_path : str
-            Full path to the Ghostscript executable.
-        gs_option : str
-            Specify a single, custom option that will be passed on to
-            Ghostscript as is.
-        dpi : int
-            Set raster resolution in dpi. Default is 720 for PDF, 300 for
-            others.
-        prefix : str
-            Force the output file name. By default output names are constructed
-            using the input names as base, which are appended with an
-            appropriate extension. Use this option to provide a different name,
-            but without extension. Extension is still determined automatically.
-        resize : str
-            [**+m**\ *margins*][**+s**\ [**m**]\ *width*\
-            [/\ *height*]][**+S**\ *scale*].
-            Adjust the BoundingBox and HiResBoundingBox by scaling and/or
-            adding margins. Append **+m** to specify extra margins to extend
-            the bounding box. Give either one (uniform), two (x and y) or four
-            (individual sides) margins; append unit [Default is set by
-            :gmt-term:`PROJ_LENGTH_UNIT`]. Append **+s**\ *width* to resize the
-            output image to exactly *width* units. The default unit is set by
-            :gmt-term:`PROJ_LENGTH_UNIT` but you can append a new unit and/or
-            impose different width and height (**Note**: This may change the
-            image aspect ratio). What happens here is that Ghostscript will do
-            the re-interpolation work and the final image will retain the DPI
-            resolution set by ``dpi``.  Append **+sm** to set a maximum size
-            and the new *width* is only imposed if the original figure width
-            exceeds it. Append /\ *height* to also impose a maximum height in
-            addition to the width. Alternatively, append **+S**\ *scale* to
-            scale the image by a constant factor.
-        bb_style : str
-            Set optional BoundingBox fill color, fading, or draw the outline
-            of the BoundingBox. Append **+f**\ *fade* to fade the entire plot
-            towards black (100%) [no fading, 0]. Append **+g**\ *paint* to
-            paint the BoundingBox behind the illustration and append **+p**\
-            [*pen*] to draw the BoundingBox outline (append a pen or accept
-            the default pen of ``"0.25p,black,solid"``). **Note**: If both **+g** and
-            **+f** are used then we use *paint* as the fade color instead of
-            black. Append **+i** to enforce gray-shades by using ICC profiles.
-        anti_aliasing : str
-            [**g**\|\ **p**\|\ **t**\][**1**\|\ **2**\|\ **4**].
-            Set the anti-aliasing options for **g**\ raphics or **t**\ ext.
-            Append the size of the subsample box (1, 2, or 4) [Default is
-            ``"4"``]. [Default is no anti-aliasing (same as bits = 1).]
-        fmt : str
-            Set the output format, where **b** means BMP, **e** means EPS,
-            **E** means EPS with PageSize command, **f** means PDF, **F** means
-            multi-page PDF, **j** means JPEG, **g** means PNG, **G** means
-            transparent PNG (untouched regions are transparent), **m** means
-            PPM, and **t** means TIFF [Default is JPEG]. To
-            **b**\|\ **j**\|\ **g**\|\ **t**\ , optionally append **+m** in
-            order to get a monochrome (grayscale) image. The EPS format can be
-            combined with any of the other formats. For example, **ef** creates
-            both an EPS and a PDF file. Using **F** creates a multi-page PDF
-            file from the list of input PS or PDF files. It requires the
-            ``prefix`` parameter.
-        {verbose}
-        """
-        kwargs = self._preprocess(**kwargs)
-        # pytest-mpl v0.17.0 added the "metadata" parameter to Figure.savefig, which
-        # is not recognized. So remove it before calling Figure.psconvert.
-        kwargs.pop("metadata", None)
-        # Default cropping the figure to True
-        if kwargs.get("A") is None:
-            kwargs["A"] = ""
-
-        prefix = kwargs.get("F")
-        if prefix in {"", None, False, True}:
-            raise GMTInvalidInput(
-                "The 'prefix' parameter must be specified with a valid value."
-            )
-
-        # check if the parent directory exists
-        prefix_path = Path(prefix).parent
-        if not prefix_path.exists():
-            raise FileNotFoundError(
-                f"No such directory: '{prefix_path}', please create it first."
-            )
-
-        with Session() as lib:
-            lib.call_module(module="psconvert", args=build_arg_list(kwargs))
-
     def savefig(  # noqa: PLR0912
         self,
         fname: str | PurePath,
@@ -543,6 +413,7 @@ def _repr_html_(self):
         meca,
         plot,
         plot3d,
+        psconvert,
         rose,
         set_panel,
         shift_origin,
diff --git a/pygmt/src/__init__.py b/pygmt/src/__init__.py
index 1d113e5dd30..e4db7321963 100644
--- a/pygmt/src/__init__.py
+++ b/pygmt/src/__init__.py
@@ -40,6 +40,7 @@
 from pygmt.src.plot import plot
 from pygmt.src.plot3d import plot3d
 from pygmt.src.project import project
+from pygmt.src.psconvert import psconvert
 from pygmt.src.rose import rose
 from pygmt.src.select import select
 from pygmt.src.shift_origin import shift_origin
diff --git a/pygmt/src/psconvert.py b/pygmt/src/psconvert.py
new file mode 100644
index 00000000000..1c2599a7ce7
--- /dev/null
+++ b/pygmt/src/psconvert.py
@@ -0,0 +1,133 @@
+"""
+psconvert - Convert [E]PS file(s) to other formats.
+"""
+
+from pathlib import Path
+
+from pygmt.clib import Session
+from pygmt.exceptions import GMTInvalidInput
+from pygmt.helpers import build_arg_list, fmt_docstring, kwargs_to_strings, use_alias
+
+
+@fmt_docstring
+@use_alias(
+    A="crop",
+    C="gs_option",
+    E="dpi",
+    F="prefix",
+    G="gs_path",
+    I="resize",
+    N="bb_style",
+    T="fmt",
+    Q="anti_aliasing",
+    V="verbose",
+)
+@kwargs_to_strings()
+def psconvert(self, **kwargs):
+    r"""
+    Convert [E]PS file(s) to other formats.
+
+    Converts one or more PostScript files to other formats (BMP, EPS, JPEG,
+    PDF, PNG, PPM, TIFF) using Ghostscript.
+
+    If no input files are given, will convert the current active figure
+    (see :class:`pygmt.Figure`). In this case, an output name must be given
+    using parameter ``prefix``.
+
+    Full option list at :gmt-docs:`psconvert.html`
+
+    {aliases}
+
+    Parameters
+    ----------
+    crop : str or bool
+        Adjust the BoundingBox and HiResBoundingBox to the minimum
+        required by the image content. Default is ``True``. Append **+u** to
+        first remove any GMT-produced time-stamps. Append **+r** to
+        *round* the HighResBoundingBox instead of using the ``ceil``
+        function. This is going against Adobe Law but can be useful when
+        creating very small images where the difference of one pixel
+        might matter. If ``verbose`` is used we also report the
+        dimensions of the final illustration.
+    gs_path : str
+        Full path to the Ghostscript executable.
+    gs_option : str
+        Specify a single, custom option that will be passed on to
+        Ghostscript as is.
+    dpi : int
+        Set raster resolution in dpi. Default is 720 for PDF, 300 for
+        others.
+    prefix : str
+        Force the output file name. By default output names are constructed
+        using the input names as base, which are appended with an
+        appropriate extension. Use this option to provide a different name,
+        but without extension. Extension is still determined automatically.
+    resize : str
+        [**+m**\ *margins*][**+s**\ [**m**]\ *width*\
+        [/\ *height*]][**+S**\ *scale*].
+        Adjust the BoundingBox and HiResBoundingBox by scaling and/or
+        adding margins. Append **+m** to specify extra margins to extend
+        the bounding box. Give either one (uniform), two (x and y) or four
+        (individual sides) margins; append unit [Default is set by
+        :gmt-term:`PROJ_LENGTH_UNIT`]. Append **+s**\ *width* to resize the
+        output image to exactly *width* units. The default unit is set by
+        :gmt-term:`PROJ_LENGTH_UNIT` but you can append a new unit and/or
+        impose different width and height (**Note**: This may change the
+        image aspect ratio). What happens here is that Ghostscript will do
+        the re-interpolation work and the final image will retain the DPI
+        resolution set by ``dpi``.  Append **+sm** to set a maximum size
+        and the new *width* is only imposed if the original figure width
+        exceeds it. Append /\ *height* to also impose a maximum height in
+        addition to the width. Alternatively, append **+S**\ *scale* to
+        scale the image by a constant factor.
+    bb_style : str
+        Set optional BoundingBox fill color, fading, or draw the outline
+        of the BoundingBox. Append **+f**\ *fade* to fade the entire plot
+        towards black (100%) [no fading, 0]. Append **+g**\ *paint* to
+        paint the BoundingBox behind the illustration and append **+p**\
+        [*pen*] to draw the BoundingBox outline (append a pen or accept
+        the default pen of ``"0.25p,black,solid"``). **Note**: If both **+g** and
+        **+f** are used then we use *paint* as the fade color instead of
+        black. Append **+i** to enforce gray-shades by using ICC profiles.
+    anti_aliasing : str
+        [**g**\|\ **p**\|\ **t**\][**1**\|\ **2**\|\ **4**].
+        Set the anti-aliasing options for **g**\ raphics or **t**\ ext.
+        Append the size of the subsample box (1, 2, or 4) [Default is
+        ``"4"``]. [Default is no anti-aliasing (same as bits = 1).]
+    fmt : str
+        Set the output format, where **b** means BMP, **e** means EPS,
+        **E** means EPS with PageSize command, **f** means PDF, **F** means
+        multi-page PDF, **j** means JPEG, **g** means PNG, **G** means
+        transparent PNG (untouched regions are transparent), **m** means
+        PPM, and **t** means TIFF [Default is JPEG]. To
+        **b**\|\ **j**\|\ **g**\|\ **t**\ , optionally append **+m** in
+        order to get a monochrome (grayscale) image. The EPS format can be
+        combined with any of the other formats. For example, **ef** creates
+        both an EPS and a PDF file. Using **F** creates a multi-page PDF
+        file from the list of input PS or PDF files. It requires the
+        ``prefix`` parameter.
+    {verbose}
+    """
+    kwargs = self._preprocess(**kwargs)
+    # pytest-mpl v0.17.0 added the "metadata" parameter to Figure.savefig, which
+    # is not recognized. So remove it before calling Figure.psconvert.
+    kwargs.pop("metadata", None)
+    # Default cropping the figure to True
+    if kwargs.get("A") is None:
+        kwargs["A"] = ""
+
+    prefix = kwargs.get("F")
+    if prefix in {"", None, False, True}:
+        raise GMTInvalidInput(
+            "The 'prefix' parameter must be specified with a valid value."
+        )
+
+    # check if the parent directory exists
+    prefix_path = Path(prefix).parent
+    if not prefix_path.exists():
+        raise FileNotFoundError(
+            f"No such directory: '{prefix_path}', please create it first."
+        )
+
+    with Session() as lib:
+        lib.call_module(module="psconvert", args=build_arg_list(kwargs))

From 9996adcfa12f380be28f7dd2725c36d1c7fe4e86 Mon Sep 17 00:00:00 2001
From: Dongdong Tian <seisman.info@gmail.com>
Date: Thu, 24 Oct 2024 12:43:45 +0800
Subject: [PATCH 2/3] Move the codes for pytest-mpl v0.17.0 to Figure.savefig

---
 pygmt/figure.py        | 3 +++
 pygmt/src/psconvert.py | 3 ---
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/pygmt/figure.py b/pygmt/figure.py
index 905222b1b97..12aaa67f722 100644
--- a/pygmt/figure.py
+++ b/pygmt/figure.py
@@ -244,6 +244,9 @@ def savefig(  # noqa: PLR0912
                 raise GMTInvalidInput(msg)
             kwargs["W"] = True
 
+        # pytest-mpl v0.17.0 added the "metadata" parameter to Figure.savefig, which
+        # is not recognized. So remove it before calling Figure.psconvert.
+        kwargs.pop("metadata", None)
         self.psconvert(prefix=prefix, fmt=fmt, crop=crop, **kwargs)
 
         # Remove the .pgw world file if exists.
diff --git a/pygmt/src/psconvert.py b/pygmt/src/psconvert.py
index 1c2599a7ce7..01cb3b2adb5 100644
--- a/pygmt/src/psconvert.py
+++ b/pygmt/src/psconvert.py
@@ -109,9 +109,6 @@ def psconvert(self, **kwargs):
     {verbose}
     """
     kwargs = self._preprocess(**kwargs)
-    # pytest-mpl v0.17.0 added the "metadata" parameter to Figure.savefig, which
-    # is not recognized. So remove it before calling Figure.psconvert.
-    kwargs.pop("metadata", None)
     # Default cropping the figure to True
     if kwargs.get("A") is None:
         kwargs["A"] = ""

From a0284539ea4adfd1784be95574070f7a5f1a6900 Mon Sep 17 00:00:00 2001
From: Dongdong Tian <seisman.info@gmail.com>
Date: Sun, 27 Oct 2024 12:02:40 +0800
Subject: [PATCH 3/3] Fix a typo [skip ci]

Co-authored-by: Michael Grund <23025878+michaelgrund@users.noreply.github.com>
---
 pygmt/src/psconvert.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pygmt/src/psconvert.py b/pygmt/src/psconvert.py
index 01cb3b2adb5..1a1c43f2e86 100644
--- a/pygmt/src/psconvert.py
+++ b/pygmt/src/psconvert.py
@@ -119,7 +119,7 @@ def psconvert(self, **kwargs):
             "The 'prefix' parameter must be specified with a valid value."
         )
 
-    # check if the parent directory exists
+    # Check if the parent directory exists
     prefix_path = Path(prefix).parent
     if not prefix_path.exists():
         raise FileNotFoundError(
