From 8ade44cbe1a12eeb3483fd9b63f2064ab4f0535b Mon Sep 17 00:00:00 2001 From: Joaquim Date: Sun, 26 Apr 2026 18:07:42 +0100 Subject: [PATCH] psconvert: restore ability to do in-memory PS manipulations. Externals can as psconvert to process the in-memory PS to call GS to rasterize the in-memory PS. This is still not very efficient and works only on Windows, but even there it was broken. This PR restores the fucntioning and adds an option (-X) to be used only from externals, that applies the equivalent of -A and leaves the modified PS accessible at GMT->PSL->internal.buffer To be followed by future (when?) enhancements. --- doc/rst/source/psconvert.rst | 6 +++ src/postscriptlight.c | 18 ++++---- src/psconvert.c | 82 +++++++++++++++++++++++------------- 3 files changed, 67 insertions(+), 39 deletions(-) diff --git a/doc/rst/source/psconvert.rst b/doc/rst/source/psconvert.rst index 2ab1e473098..43e0ab390aa 100644 --- a/doc/rst/source/psconvert.rst +++ b/doc/rst/source/psconvert.rst @@ -218,6 +218,12 @@ Optional Arguments :start-after: **Syntax** :end-before: **Description** +.. _-X: + +**-X** + For externals only. Apply the crop blanks (-A) option to the in-memory PS + and leave it accessible via GMT->PSL->internal.buffer + .. _-W: **-W**\ [**+a**\ *altmode*\ [*alt*]][**+c**][**+f**\ *minfade/maxfade*][**+g**][**+k**][**+l**\ *minLOD/maxLOD*][**+n**\ *layername*][**+o**\ *foldername*][**+t**\ *docname*][**+u**\ *URL*] diff --git a/src/postscriptlight.c b/src/postscriptlight.c index 0ad399699dc..cc85703752b 100644 --- a/src/postscriptlight.c +++ b/src/postscriptlight.c @@ -600,7 +600,7 @@ static void *psl_memory (struct PSL_CTRL *PSL, void *prev_addr, size_t nelem, si PSL_free (prev_addr); return (NULL); } - if ((tmp = realloc ( prev_addr, nelem * size)) == NULL) { + if ((tmp = realloc (prev_addr, nelem * size + 1)) == NULL) { mem = (double)(nelem * size); k = 0; while (mem >= 1024.0 && k < 3) mem /= 1024.0, k++; @@ -1149,8 +1149,8 @@ static void psl_prepare_buffer (struct PSL_CTRL *C, size_t len) { /* Ensure buffer is large enough to accept additional text of length len */ size_t new_len = C->internal.n + len; /* Need a buffer at least this large */ if (new_len < C->internal.n_alloc) return; /* Already have a buffer that is large enough */ - while (new_len > C->internal.n_alloc) /* Wind past what is needed, growing by 1.75 */ - C->internal.n_alloc = (size_t)(C->internal.n_alloc * 1.75); + while (new_len > C->internal.n_alloc) /* Wind past what is needed, growing by 1.5 */ + C->internal.n_alloc = (size_t)(C->internal.n_alloc * 1.5); if ((C->internal.buffer = PSL_memory (C, C->internal.buffer, C->internal.n_alloc, char)) == NULL) { PSL_message (C, PSL_MSG_ERROR, "Error: Could not allocate %d additional buffer space - this will not end well\n", len); } @@ -4586,17 +4586,17 @@ int PSL_endplot (struct PSL_CTRL *PSL, int lastpage) { return (PSL_NO_ERROR); } -char * PSL_getplot (struct PSL_CTRL *PSL) { +char *PSL_getplot(struct PSL_CTRL *PSL) { /* Simply pass the plot back to caller */ if (!PSL->internal.memory) { - PSL_message (PSL, PSL_MSG_ERROR, "Error: Cannot get a plot since memory output was not activated!\n"); - return (NULL); + PSL_message(PSL, PSL_MSG_ERROR, "Error: Cannot get a plot since memory output was not activated!\n"); + return NULL; } if (!PSL->internal.buffer) { - PSL_message (PSL, PSL_MSG_ERROR, "Error: No plot in memory available!\n"); - return (NULL); + PSL_message(PSL, PSL_MSG_ERROR, "Error: No plot in memory available!\n"); + return NULL; } - return (PSL->internal.buffer); + return PSL->internal.buffer; } int PSL_beginplot (struct PSL_CTRL *PSL, FILE *fp, int orientation, int overlay, int color_mode, char origin[], double offset[], double page_size[], char *title, int font_no[]) { diff --git a/src/psconvert.c b/src/psconvert.c index e3f1e357f18..f14488237ef 100644 --- a/src/psconvert.c +++ b/src/psconvert.c @@ -37,7 +37,7 @@ #define THIS_MODULE_MODERN_NAME "psconvert" #define THIS_MODULE_LIB "core" #define THIS_MODULE_PURPOSE "Convert [E]PS file(s) to other formats using Ghostscript" -#define THIS_MODULE_KEYS " [-A[+i][+r][+u]] [-C] [-D] [-E] " "[-F] [-G] [-H] [-I[+m][+s[m][/]][+S]] [-L] [-Mb|f] " "[-N[+f][+g][+k][+p[]]] [-P] [-Q[g|p|t]1|2|4] [-S] [-Tb|e|E|f|F|g|G|j|m|s|t[+m][+q]] [%s] " - "[-W[+a[]][+c][+f/][+g][+k][+l/][+n][+o][+t][+u<URL>]]%s [-!]" + "[-X] [-W[+a<mode>[<alt>]][+c][+f<minfade>/<maxfade>][+g][+k][+l<lodmin>/<lodmax>][+n<name>][+o<folder>][+t<title>][+u<URL>]]%s [-!]" "[%s]\n", name, GMT_V_OPT, Z, GMT_PAR_OPT); if (level == GMT_SYNOPSIS) return (GMT_MODULE_SYNOPSIS); @@ -777,6 +780,9 @@ static int usage (struct GMTAPI_CTRL *API, int level) { GMT_Option (API, "V"); GMT_Usage (API, -2, "Note: Shows the gdal_translate command, in case you want to use this program " "to create a geoTIFF file."); + GMT_Usage (API, 1, "\n-X"); + GMT_Usage (API, -2, "For externals only. Apply the crop blanks (-A) option to the in-memory PS " + "and leave it accessible via GMT->PSL->internal.buffer"); GMT_Usage (API, 1, "\n-W[+a<mode>[<alt>]][+c][+f<minfade>/<maxfade>][+g][+k][+l<lodmin>/<lodmax>][+n<name>][+o<folder>][+t<title>][+u<URL>]"); GMT_Usage (API, -2, "Write an ESRI type world file suitable to make .tif files " "recognized as geotiff by software that know how to do it. Be aware, " @@ -1016,7 +1022,9 @@ static int parse(struct GMT_CTRL *GMT, struct PSCONVERT_CTRL *Ctrl, struct GMT_O n_errors += gmt_M_repeated_module_option (API, Ctrl->W.active); n_errors += psconvert_parse_GE_settings (GMT, opt->arg, Ctrl); break; - + case 'X': /* No output, but the modified PostScript is returned in externals */ + n_errors += gmt_M_repeated_module_option(API, Ctrl->X.active); + break; case 'Z': n_errors += gmt_M_repeated_module_option (API, Ctrl->Z.active); if (GMT->current.setting.run_mode == GMT_MODERN) { @@ -1188,10 +1196,10 @@ GMT_LOCAL void psconvert_possibly_fill_or_outline_BB (struct GMT_CTRL *GMT, stru /* ---------------------------------------------------------------------------------------------- */ GMT_LOCAL int psconvert_pipe_HR_BB(struct GMTAPI_CTRL *API, struct PSCONVERT_CTRL *Ctrl, char *gs_BB, double margin, double *w, double *h) { /* Do what we do in the main code for the -A (if used here) option but on an in-memory PS 'file' */ - char cmd[GMT_LEN512] = {""}, buf[GMT_LEN128] = {""}, t[32] = {""}, *pch, c; - int fh, r, c_begin = 0; - size_t n; - bool landscape = false; + char cmd[GMT_LEN512] = {""}, buf[GMT_LEN128] = {""}, t[32] = {""}, *pch, *ps_ptr, *line_start, *line_end, *next_line, *next_eol, saved_nl, c; + int fh, r; + size_t n, len_old, len_next; + bool landscape = false, found = false; double x0, y0, x1, y1, xt = 0, yt = 0; struct GMT_POSTSCRIPT *PS = NULL; #ifdef _WIN32 @@ -1313,28 +1321,40 @@ GMT_LOCAL int psconvert_pipe_HR_BB(struct GMTAPI_CTRL *API, struct PSCONVERT_CTR else GMT_Report (API, GMT_MSG_WARNING, "Something very odd the GMT PS does not have a %%HiResBoundingBox\n"); - /* Find where is the setpagedevice line */ - if ((pch = strstr(PS->data, "setpagedevice")) != NULL) { - while (pch[c_begin] != '\n') c_begin--; - c_begin++; /* It receded one too much */ - /* So now we know where the line starts. Put a 'translate' command in its place. */ - (r == 0) ? sprintf(buf, "%.3f %.3f translate", xt, yt) : sprintf(buf, "%d rotate\n%.3f %.3f translate", r, xt, yt); - for (n = 0; n < strlen(buf); n++, c_begin++) pch[c_begin] = buf[n]; - while (pch[c_begin] != '\n') pch[c_begin++] = ' '; /* Make sure there are only spaces till EOL */ - } - else { - if ((pch = strstr(PS->data, " translate")) != NULL) { /* If user runs through this function twice 'setpagedevice' was changed to 'translate' */ - double old_xt, old_yt; - while (pch[c_begin] != '\n') c_begin--; - c_begin++; /* Goto line start but it receded one too much */ - sscanf (&pch[c_begin], "%lf %lf", &old_xt, &old_yt); - (r == 0) ? sprintf(buf, "%.3f %.3f translate", xt + old_xt, yt + old_xt) : sprintf(buf, "%d rotate\n%.3f %.3f translate", r, xt + old_xt, yt + old_xt); - for (n = 0; n < strlen(buf); n++, c_begin++) pch[c_begin] = buf[n]; - while (pch[c_begin] != '\n') pch[c_begin++] = ' '; + /* Find the setpagedevice line that has PageSize, update page size and add rotation/translation on next line */ + ps_ptr = PS->data; + while (!found && (pch = strstr(ps_ptr, "setpagedevice")) != NULL) { + line_start = pch; + while (line_start > PS->data && line_start[-1] != '\n') line_start--; + if ((line_end = strchr(pch, '\n')) == NULL) { ps_ptr = pch + 13; continue; } + saved_nl = *line_end; *line_end = '\0'; + found = (strstr(line_start, "PageSize") != NULL); + *line_end = saved_nl; + if (!found) { ps_ptr = pch + 13; continue; } + /* Replace this line with updated PageSize setpagedevice */ + len_old = (size_t)(line_end - line_start); + sprintf(buf, "PSLevel 1 gt { << /PageSize [%.7g %.7g] /ImagingBBox null >> setpagedevice } if", *w, *h); + for (n = 0; n < strlen(buf) && n < len_old; n++) line_start[n] = buf[n]; + while (n < len_old) line_start[n++] = ' '; + /* Write rotation/translation on next line */ + next_line = line_end + 1; + if ((next_eol = strchr(next_line, '\n')) != NULL) { + len_next = (size_t)(next_eol - next_line); + buf[0] = '\0'; + if (r != 0) { + sprintf(buf, "%d R\n", r); + if (xt != 0.0 || yt != 0.0) { sprintf(t, "%g %g T", xt, yt); strcat(buf, t); } + } + else if (xt != 0.0 || yt != 0.0) + sprintf(buf, "%g %g T", xt, yt); + if (buf[0] != '\0') { + for (n = 0; n < strlen(buf) && n < len_next; n++) next_line[n] = buf[n]; + while (n < len_next) next_line[n++] = ' '; + } } - else - GMT_Report(API, GMT_MSG_WARNING, "Something very odd the GMT PS does not have the setpagedevice line\n"); } + if (!found) + GMT_Report(API, GMT_MSG_WARNING, "Something very odd the GMT PS does not have the setpagedevice line\n"); gmt_M_free (API->GMT, PS); @@ -1537,6 +1557,8 @@ GMT_LOCAL int psconvert_in_mem_PS(struct GMTAPI_CTRL *API, struct PSCONVERT_CTRL if (psconvert_pipe_HR_BB (API, Ctrl, gs_BB, margin, &w, &h)) /* Apply the -A stuff to the in-memory PS */ GMT_Report (API, GMT_MSG_ERROR, "Failed to fish the HiResBoundingBox from PS-in-memory .\n"); + if (Ctrl->X.active) return GMT_NOERROR; /* User wants only the reworked PS */ + if (Ctrl->T.active) { /* Then write the converted file into a file instead of storing it into a Image struct */ char t[PATH_MAX] = {""}; sprintf (t, " -sDEVICE=%s %s -sOutputFile=", device[Ctrl->T.device], device_options[Ctrl->T.device]); @@ -1894,8 +1916,8 @@ EXTERN_MSC int GMT_psconvert(void *V_API, int mode, void *args) { /* -------------------- Special case of in-memory PS. Process it and return ------------------- */ if (API->external && Ctrl->In.n_files == 1 && ps_names[0][0] == '=') { - if (!return_image && !Ctrl->T.active) { - GMT_Report (API, GMT_MSG_ERROR, "Internal PSL PostScript rip requires output file via -F\n"); + if (!return_image && !Ctrl->T.active && !Ctrl->X.active) { + GMT_Report (API, GMT_MSG_ERROR, "Internal PSL PostScript rip requires output file via -F or -X\n"); error++; } else @@ -2231,7 +2253,7 @@ EXTERN_MSC int GMT_psconvert(void *V_API, int mode, void *args) { else if ((strstr(line, "%%Orientation:")) &&!strncmp(&line[15], "Landscape", 9)) { landscape = landscape_orig = true; if (Ctrl->O.active && (Ctrl->P.active || Ctrl->A.crop)) { - /* The case here is that the on a first time all wet well, but on a second run the Orientation + /* The case here is that the on a first time all went well, but on a second run the Orientation was still Landscape and later on the w(idth) and h(eight) were swapped because they are set after value that were edit by the first run. The trick is then to set the Orientation to Portrait even before the rotation had been applied.