diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 5da09dc6e24..57319679395 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -150,8 +150,11 @@ def _is_ascii_no_apostrophe_and_backtick(argstr: str) -> bool: Check if a string only contains ASCII characters excluding apostrophe (') and backtick (`). - Apostrophe (') and backtick (`) are excluded because they are typographically - different in Adobe ISOLatin1+ encoding and need special handling. + For typographical reasons, apostrophe (') and backtick (`) are mapped to left and + right single quotation marks (‘ and ’) in Adobe ISOLatin1+ encoding. To ensure what + you type is what you get (https://github.com/GenericMappingTools/pygmt/issues/3476), + they need special handling in the non_ascii_to_octal function. Thus, the two + characters are excluded here. Parameters ---------- @@ -174,7 +177,7 @@ def _is_ascii_no_apostrophe_and_backtick(argstr: str) -> bool: False >>> _is_ascii_no_apostrophe_and_backtick("12AB±β①②") False - """ + """ # noqa: RUF002 return all(32 <= ord(c) <= 126 and c not in "'`" for c in argstr) @@ -211,8 +214,7 @@ def _check_encoding(argstr: str) -> Encoding: 'ISOLatin1+' """ # Return "ascii" if the string only contains ASCII characters. - # Apostrophe (') and backtick (`) are excluded because they are typographically - # different in Adobe ISOLatin1+ encoding and need special handling. + # Apostrophe (') and backtick (`) are excluded. if _is_ascii_no_apostrophe_and_backtick(argstr): return "ascii" # Loop through all supported encodings and check if all characters in the string @@ -412,6 +414,7 @@ def non_ascii_to_octal(argstr: str, encoding: Encoding = "ISOLatin1+") -> str: '12AB\\340\\341\\342\\343\\344\\345@~\\142@~@%34%\\254@%%@%34%\\255@%%' """ # noqa: RUF002 # Return the input string if it only contains ASCII characters. + # Apostrophe (') and backtick (`) are excluded. if encoding == "ascii" or _is_ascii_no_apostrophe_and_backtick(argstr): return argstr @@ -428,6 +431,11 @@ def non_ascii_to_octal(argstr: str, encoding: Encoding = "ISOLatin1+") -> str: # Remove any printable characters. mapping = {k: v for k, v in mapping.items() if k not in string.printable} + + if encoding == "ISOLatin1+": + # Special handling for apostrophe (') and backtick (`). + # See _is_ascii_no_apostrophe_and_backtick for details. + mapping.update({"'": "\\234", "`": "\\140"}) return argstr.translate(str.maketrans(mapping))