diff --git a/src/begin.c b/src/begin.c index 25826912f09..f7e391820e0 100644 --- a/src/begin.c +++ b/src/begin.c @@ -152,6 +152,8 @@ EXTERN_MSC int GMT_begin (void *V_API, int mode, void *args) { /*----------------------- Standard module initialization and parsing ----------------------*/ if (API == NULL) return (GMT_NOT_A_SESSION); + if (gmt_modern_in_classic_session (API, "begin")) /* If not command-line or external it means use of C API directly, so GMT_Create_Session needs to set modern mode first */ + return (GMT_RUNTIME_ERROR); if (mode == GMT_MODULE_PURPOSE) return (usage (API, GMT_MODULE_PURPOSE)); /* Return the purpose of program */ options = GMT_Create_Options (API, mode, args); if (API->error) return (API->error); /* Set or get option list */ diff --git a/src/end.c b/src/end.c index c5001c456e6..3db3d06e076 100644 --- a/src/end.c +++ b/src/end.c @@ -96,6 +96,8 @@ EXTERN_MSC int GMT_end (void *V_API, int mode, void *args) { /*----------------------- Standard module initialization and parsing ----------------------*/ if (API == NULL) return (GMT_NOT_A_SESSION); + if (gmt_modern_in_classic_session (API, "end")) /* If not command-line or external it means use of C API directly, so GMT_Create_Session needs to set modern mode first */ + return (GMT_RUNTIME_ERROR); if (mode == GMT_MODULE_PURPOSE) return (usage (API, GMT_MODULE_PURPOSE)); /* Return the purpose of program */ options = GMT_Create_Options (API, mode, args); if (API->error) return (API->error); /* Set or get option list */ diff --git a/src/figure.c b/src/figure.c index 69c6a2dec26..d2847365068 100644 --- a/src/figure.c +++ b/src/figure.c @@ -144,6 +144,8 @@ EXTERN_MSC int GMT_figure (void *V_API, int mode, void *args) { /*----------------------- Standard module initialization and parsing ----------------------*/ if (API == NULL) return (GMT_NOT_A_SESSION); + if (gmt_modern_in_classic_session (API, "figure")) /* If not command-line or external it means use of C API directly, so GMT_Create_Session needs to set modern mode first */ + return (GMT_RUNTIME_ERROR); if (mode == GMT_MODULE_PURPOSE) return (usage (API, GMT_MODULE_PURPOSE)); /* Return the purpose of program */ options = GMT_Create_Options (API, mode, args); if (API->error) return (API->error); /* Set or get option list */ diff --git a/src/gmt.c b/src/gmt.c index 5a5390c331a..88f09a78ed4 100644 --- a/src/gmt.c +++ b/src/gmt.c @@ -83,7 +83,7 @@ int main (int argc, char *argv[]) { gmt_main = !strcmp (module, PROGRAM_NAME); /* true if running the main program, false otherwise */ /* Initialize new GMT session */ - if ((api_ctrl = GMT_Create_Session (argv[0], GMT_PAD_DEFAULT, mode, NULL)) == NULL) + if ((api_ctrl = GMT_Create_Session (argv[0], GMT_PAD_DEFAULT, mode | GMT_SESSION_CMDLINE, NULL)) == NULL) return GMT_RUNTIME_ERROR; api_ctrl->internal = true; /* This is a proper GMT commandline session (external programs will default to false) */ diff --git a/src/gmt_api.c b/src/gmt_api.c index 4cac6adbc6a..b09d0f0b221 100644 --- a/src/gmt_api.c +++ b/src/gmt_api.c @@ -15942,3 +15942,12 @@ int64_t gmt_eliminate_duplicates (struct GMTAPI_CTRL *API, struct GMT_DATASET *D return (n_dup); } + +bool gmt_modern_in_classic_session (struct GMTAPI_CTRL *API, const char *module) { + /* If not commandline or external it means use of C API directly, so GMT_Create_Session needs to set modern mode first */ + if (API->runmode) return false; /* Already in modern mode */ + if (API->external) return false; /* An external call from MATLAB, Julia, or Python */ + if (API->cmdline) return false; /* A standard command-line call via gmt program */ + GMT_Report (API, GMT_MSG_ERROR, "Cannot call module \"%s\" in a classic session created via a call to GMT_Create_Session\n", module); + return true; /* Sorry, calling a modern-mode only module from a classic session started from GMT_Create_Session in a C/C++ program is not allowed */ +} \ No newline at end of file diff --git a/src/gmt_constants.h b/src/gmt_constants.h index e6a4cd30348..742171678a2 100644 --- a/src/gmt_constants.h +++ b/src/gmt_constants.h @@ -184,6 +184,8 @@ enum GMT_enum_basemap { GMT_BASEMAP_ANNOT_BEFORE = 0, GMT_BASEMAP_ANNOT_AFTER = 4}; +#define GMT_SESSION_CMDLINE 64 /* Passed when GMT_Create_Session is called from the command-line gmt.c driver only */ + /*! Handling of periodic data */ enum GMT_time_period { GMT_CYCLE_SEC = 1, diff --git a/src/gmt_init.c b/src/gmt_init.c index 185f0f00533..d3e331e898e 100644 --- a/src/gmt_init.c +++ b/src/gmt_init.c @@ -18342,8 +18342,29 @@ struct GMT_CTRL *gmt_begin (struct GMTAPI_CTRL *API, const char *session, unsign return NULL; } + /* Set up hash table for GMT_keywords (used in gmt_conf) */ + + if (gmt_hash_init (GMT, keys_hashnode, GMT_keywords, GMT_N_KEYS, GMT_N_KEYS)) { /* Initialize hash table for GMT defaults */ + gmtinit_free_GMT_ctrl (GMT); /* Deallocate control structure */ + return NULL; + } + + /* Set up hash table for colornames (used to convert to ) */ + + if (gmt_hash_init (GMT, GMT->session.rgb_hashnode, gmt_M_color_name, GMT_N_COLOR_NAMES, GMT_N_COLOR_NAMES)) { + gmtinit_free_GMT_ctrl (GMT); /* Deallocate control structure */ + return NULL; + } + GMT_Report (API, GMT_MSG_DEBUG, "Enter: gmt_manage_workflow\n"); - if (gmt_manage_workflow (API, GMT_USE_WORKFLOW, NULL)) { + if (API->runmode && !API->external && !API->cmdline) { /* Special case */ + /* If external C call then there is no gmt.c driver and only one GMT_Create_Session, so we must init a new session here, if in modern mode */ + mode = GMT_BEGIN_WORKFLOW; + GMT->current.setting.run_mode = GMT_MODERN; /* Set here so we get the benefits of a "gmt module" initialization for PSL */ + } + else /* Regular use */ + mode = GMT_USE_WORKFLOW; + if (gmt_manage_workflow (API, mode, NULL)) { GMT_Report (API, GMT_MSG_ERROR, "Could not initialize the GMT workflow - Aborting.\n"); gmtinit_free_GMT_ctrl (GMT); /* Deallocate control structure */ return NULL; @@ -18384,7 +18405,7 @@ struct GMT_CTRL *gmt_begin (struct GMTAPI_CTRL *API, const char *session, unsign gmt_reload_settings (GMT); /* Initialize the standard GMT system default settings and overload with user's settings */ GMT_Report (API, GMT_MSG_DEBUG, "Exit: gmt_reload_settings\n"); - if (API->runmode) GMT->current.setting.run_mode = GMT_MODERN; /* Enforced at API Creation */ + if (API->runmode) GMT->current.setting.run_mode = GMT_MODERN; /* Enforced at API Creation but set AFTER gmt_reload_settings */ /* There is no longer a -m option in GMT so multi segments are now always true. However, in GMT_COMPAT mode the -mi and -mo options WILL turn off multi in the other direction. */ @@ -18753,19 +18774,20 @@ GMT_LOCAL int gmtinit_get_graphics_formats (struct GMT_CTRL *GMT, char *formats, } GMT_LOCAL bool gmtinit_check_if_autosize (struct GMTAPI_CTRL *API, int ID) { - /* Check if the BoundingBox line in the half-baked PostScript file has max dimension (32767x32767) + /* Check if the BoundingBox line in the half-baked PostScript file has max dimension (GMT_PAPER_DIM x GMT_PAPER_DIM) * which we used to enforce automatic cropping to actual size [and possible extra margins] */ - char file[PATH_MAX] = {""}; + char file[PATH_MAX] = {""}, def_dim[GMT_LEN32] = {""}; FILE *fp; snprintf (file, PATH_MAX, "%s/gmt_%d.ps-", API->gwf_dir, ID); /* Current half-baked PostScript file */ if ((fp = fopen (file, "r")) == NULL) { /* This is an unmitigated disaster */ GMT_Report (API, GMT_MSG_ERROR, "Failed to open half-baked PostScript file %s\n", file); return false; } + sprintf (def_dim, "%d %d", GMT_PAPER_DIM, GMT_PAPER_DIM); /* Create the comparison string */ gmt_fgets (API->GMT, file, PATH_MAX, fp); /* Skip first line */ gmt_fgets (API->GMT, file, PATH_MAX, fp); /* Get second line with BoundingBox code */ fclose (fp); - if (strstr (file, "32767 32767")) return true; /* Max paper size means auto-sized media */ + if (strstr (file, def_dim)) return true; /* Max paper size means auto-sized media */ return false; } @@ -19578,7 +19600,7 @@ int gmt_manage_workflow (struct GMTAPI_CTRL *API, unsigned int mode, char *text) gmtinit_setautopagesize (API->GMT); /* Reset to auto */ } snprintf (dir, PATH_MAX, "%s/%s", API->gwf_dir, GMT_SETTINGS_FILE); /* Reuse dir string for saving gmt.conf to this dir */ - API->GMT->current.setting.run_mode = GMT_MODERN; /* Enable modern mode here so putdefaults can skip writing PS_MEDIA if not PostScript output */ + API->GMT->current.setting.run_mode = GMT_MODERN; /* Enable modern mode here AFTER gmt_conf call so putdefaults can skip writing PS_MEDIA if not PostScript output */ error = gmtinit_put_session_name (API, text); /* Store session name, possibly setting psconvert options */ gmt_putdefaults (API->GMT, dir); /* Write current GMT defaults to this sessions gmt.conf file in the workflow directory */ API->GMT->current.setting.history_orig = API->GMT->current.setting.history; /* Temporarily turn off history so nothing is copied into the workflow dir */ diff --git a/src/gmt_private.h b/src/gmt_private.h index b7249c78b20..e1b46e1c9cb 100644 --- a/src/gmt_private.h +++ b/src/gmt_private.h @@ -133,6 +133,7 @@ struct GMTAPI_CTRL { int current_item[2]; /* Array number of current dataset being processed (in and out)*/ unsigned int pad; /* Session default for number of rows/cols padding for grids [2] */ unsigned int external; /* 1 if called via external API (MATLAB, Python) [0] */ + unsigned int cmdline; /* 1 if called via gmt.c [0] */ unsigned int runmode; /* nonzero for GMT modern runmode [0 = classic] */ enum GMT_enum_fmt shape; /* GMT_IS_COL_FORMAT (2) if column-major (MATLAB, Fortran), GMT_IS_ROW_FORMAT (1) if row-major (Python, C/C++) [1] */ unsigned int leave_grid_scaled; /* 1 if we don't want to unpack a grid after we packed it for writing [0] */ diff --git a/src/gmt_prototypes.h b/src/gmt_prototypes.h index 33a8c3b9dfb..b63504d7136 100644 --- a/src/gmt_prototypes.h +++ b/src/gmt_prototypes.h @@ -746,6 +746,7 @@ EXTERN_MSC void gmt_polar_to_cart (struct GMT_CTRL *GMT, double r, double theta, EXTERN_MSC void gmt_cart_to_polar (struct GMT_CTRL *GMT, double *r, double *theta, double *a, bool degrees); /* From gmt_api.c */ +EXTERN_MSC bool gmt_modern_in_classic_session (struct GMTAPI_CTRL *API, const char *module); EXTERN_MSC int64_t gmt_eliminate_duplicates (struct GMTAPI_CTRL *API, struct GMT_DATASET *D, uint64_t cols[], uint64_t ncols, bool text); EXTERN_MSC unsigned int gmt_whole_earth (struct GMT_CTRL *GMT, double we_in[], double we_out[]); EXTERN_MSC int gmt_copy (struct GMTAPI_CTRL *API, enum GMT_enum_family family, unsigned int direction, char *ifile, char *ofile); diff --git a/src/inset.c b/src/inset.c index b7f3ab510fe..5dd56815dde 100644 --- a/src/inset.c +++ b/src/inset.c @@ -257,6 +257,8 @@ EXTERN_MSC int GMT_inset (void *V_API, int mode, void *args) { /*----------------------- Standard module initialization and parsing ----------------------*/ if (API == NULL) return (GMT_NOT_A_SESSION); + if (gmt_modern_in_classic_session (API, "inset")) /* If not command-line or external it means use of C API directly, so GMT_Create_Session needs to set modern mode first */ + return (GMT_RUNTIME_ERROR); if (mode == GMT_MODULE_PURPOSE) return (usage (API, GMT_MODULE_PURPOSE)); /* Return the purpose of program */ options = GMT_Create_Options (API, mode, args); if (API->error) return (API->error); /* Set or get option list */ diff --git a/src/subplot.c b/src/subplot.c index 789c183fa47..a594c87a7e0 100644 --- a/src/subplot.c +++ b/src/subplot.c @@ -839,6 +839,8 @@ EXTERN_MSC int GMT_subplot (void *V_API, int mode, void *args) { /*----------------------- Standard module initialization and parsing ----------------------*/ if (API == NULL) return (GMT_NOT_A_SESSION); + if (gmt_modern_in_classic_session (API, "subplot")) /* If not command-line or external it means use of C API directly, so GMT_Create_Session needs to set modern mode first */ + return (GMT_RUNTIME_ERROR); if (mode == GMT_MODULE_PURPOSE) return (usage (API, GMT_MODULE_PURPOSE)); /* Return the purpose of program */ options = GMT_Create_Options (API, mode, args); if (API->error) return (API->error); /* Set or get option list */ diff --git a/src/testapi_map.c b/src/testapi_map.c new file mode 100644 index 00000000000..9b99f610577 --- /dev/null +++ b/src/testapi_map.c @@ -0,0 +1,29 @@ +#include "gmt.h" +/* Used to examine https://github.com/GenericMappingTools/gmt/issues/4518 + * Give an graphic extension to replicate that, otherwise we make a PS that can be + * compared with the original PS so the test can run as normal. + * Run testapi_map jpg to show the result is properly cropped. + * [add "| 458752" to GMT_SESSION_RUNMODE to simulate -Vd ] + */ +int main (int argc, char *argv[]) { + void *API; + + /* Initialize the GMT session */ + if ((API = GMT_Create_Session ("GMT_plot", 2, GMT_SESSION_RUNMODE, NULL)) == NULL) + return EXIT_FAILURE; + if (argc > 1) { /* Gave a particular graphics format (we hope - no checking here) */ + char string[64] = {""}; + sprintf (string, "apimap %s", argv[1]); + GMT_Call_Module (API, "begin", GMT_MODULE_CMD, string); + } + else /* Default to PostScript */ + GMT_Call_Module (API, "begin", GMT_MODULE_CMD, "apimap ps"); + GMT_Call_Module (API, "basemap", GMT_MODULE_CMD, "-BWESN -Bxa30mg30m -Bya20mg20m -JM7.27/42.27/15c -R5.5/41.425/9.0/43.1r"); + if (argc > 1) + GMT_Call_Module (API, "end", GMT_MODULE_CMD, "show"); + else + GMT_Call_Module (API, "end", GMT_MODULE_CMD, NULL); + /* Destroy session */ + if (GMT_Destroy_Session (API)) + return EXIT_FAILURE; +} diff --git a/test/api/apimap.ps b/test/api/apimap.ps new file mode 100644 index 00000000000..8a68fe3e6ac Binary files /dev/null and b/test/api/apimap.ps differ diff --git a/test/api/apimap.sh b/test/api/apimap.sh new file mode 100755 index 00000000000..abed0911e29 --- /dev/null +++ b/test/api/apimap.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# +# Test the C API for simulating a modern mode basemap plot +# See https://github.com/GenericMappingTools/gmt/issues/4518 +ps=apimap.ps +testapi_map > $ps diff --git a/test/gmtest.in b/test/gmtest.in index 4cc4235d99e..c1d25d95635 100755 --- a/test/gmtest.in +++ b/test/gmtest.in @@ -110,6 +110,7 @@ for apiprog in \ testapi_cube \ testapi_makecpt \ testapi_matrix \ + testapi_map \ testapi_matrix_pad \ testapi_matrix_plot \ testapi_mixmatrix \