[SCM] PostGIS branch stable-3.6 updated. 3.6.4-74-g5c0b2776b
git at osgeo.org
git at osgeo.org
Sun Jul 19 18:45:13 PDT 2026
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "PostGIS".
The branch, stable-3.6 has been updated
via 5c0b2776b11cbe3a5d9889d5caf941e58004f09d (commit)
via 07343177f02efcccc10d445819a47ef3a0651b01 (commit)
from d402f622e40deafeb5146b79599c965cecb79697 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 5c0b2776b11cbe3a5d9889d5caf941e58004f09d
Merge: d402f622e 07343177f
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date: Sun Jul 19 18:45:12 2026 -0700
Merge pull request 'Backport doc image generator policy fix to stable 3.6' (!455) from Komzpa/postgis:fix/stable-3.6-doc-image-policy into stable-3.6
Reviewed-on: https://gitea.osgeo.org/postgis/postgis/pulls/455
commit 07343177f02efcccc10d445819a47ef3a0651b01
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sun Jun 21 05:01:14 2026 +0400
Avoid ImageMagick policy errors in doc images
Backport the documentation image generator changes that avoid ImageMagick @file draw indirection and prefer GraphicsMagick when it is available. This keeps stable-3.4 documentation builds working on hosts whose ImageMagick policy blocks @* path reads.
Closes #6046 for PostGIS 3.4
(cherry picked from commit 441805b9615b6f3e4045a9a64ae56b14643f1615)
diff --git a/NEWS b/NEWS
index cecfe2fad..ef3cfab6a 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.6.5
* Fixes *
+- #6046, Prefer GraphicsMagick for documentation image generation
+ (Darafei Praliaskouski)
- #5989, CurvePolygon distance corner case (Paul Ramsey)
- #5357, ST_LineFromEncodedPolyline dropping close points (Paul Ramsey)
- OSSFuzz 525554772, avoid signed integer overflow (Even Rouault)
diff --git a/configure.ac b/configure.ac
index 12e26a06b..43a39660e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -258,10 +258,19 @@ fi
CAN_BUILD_PDF=${CAN_BUILD_COMMENTS}
+AC_PATH_PROG([GM], [gm], [])
+AC_PATH_PROG([MAGICK], [magick], [])
AC_PATH_PROG([CONVERT], [convert], [])
-if test "x$CONVERT" = "x"; then
- AC_MSG_WARN([ImageMagick does not seem to be installed. Documentation cannot be built])
- CAN_BUILD_PDF=no
+if test "x$GM" != "x"; then
+ DOC_IMAGE_CONVERTER="$GM convert"
+elif test "x$MAGICK" != "x"; then
+ DOC_IMAGE_CONVERTER="$MAGICK convert"
+else
+ DOC_IMAGE_CONVERTER="$CONVERT"
+fi
+if test "x$DOC_IMAGE_CONVERTER" = "x"; then
+ AC_MSG_WARN([GraphicsMagick or ImageMagick does not seem to be installed. PDF documentation cannot be built])
+ CAN_BUILD_PDF=no
fi
AC_PATH_PROG([DBLATEX], [dblatex], [])
@@ -2056,7 +2065,7 @@ AC_MSG_RESULT([ -------- Documentation Generation -------- ])
AC_MSG_RESULT([ xsltproc: ${XSLTPROC}])
AC_MSG_RESULT([ docbook xsl base: ${XSLBASE}])
AC_MSG_RESULT([ dblatex: ${DBLATEX}])
-AC_MSG_RESULT([ convert: ${CONVERT}])
+AC_MSG_RESULT([ doc image converter: ${DOC_IMAGE_CONVERTER}])
AC_MSG_RESULT()
if test "$POSTGIS_GEOS_VERSION" -lt 31400; then
diff --git a/doc/README b/doc/README
index d08d9afb8..106086ad2 100644
--- a/doc/README
+++ b/doc/README
@@ -85,7 +85,9 @@ To generate the html files and the comments:
* xmllint - http://xmlsoft.org
To generate the images:
- * ImageMagick - http://www.imagemagick.org/
+ * GraphicsMagick - http://www.graphicsmagick.org/ (preferred)
+ * ImageMagick - https://imagemagick.org/script/download.php ("convert" or
+ "magick convert" binaries)
To generate the PDFs:
* dblatex - http://dblatex.sourceforge.net
diff --git a/doc/html/images/generator.c b/doc/html/images/generator.c
index c6e4bf164..0ada4e581 100644
--- a/doc/html/images/generator.c
+++ b/doc/html/images/generator.c
@@ -3,6 +3,7 @@
* PostGIS - Spatial Types for PostgreSQL
* http://postgis.net
*
+ * Copyright (C) 2025 Darafei Praliaskouski <me at komzpa.net>
* Copyright (C) 2022-2023 Sandro Santilli <strk at kbt.io>
* Copyright (C) 2022 Martin Davis
* Copyright (C) 2008 Kevin Neufeld
@@ -17,10 +18,11 @@
* fill color, etc).
* The styles are specified in the adjacent styles.conf file.
*
- * In order to generate a png file, ImageMagicK must be installed in the
- * user's path as system calls are invoked to "convert". In this manner,
- * WKT files are converted into SVG syntax and rasterized as png. (PostGIS's
- * internal SVG methods could not be used dues to syntax issues with ImageMagick)
+ * In order to generate a png file, GraphicsMagick or ImageMagick must be
+ * installed in the user's path as system calls are invoked to "gm convert",
+ * "magick convert", or the classic "convert" binary. In this manner, WKT
+ * files are converted into SVG syntax and rasterized as png using their
+ * command-line interfaces.
*
* The goal of this application is to dynamically generate all the spatial
* pictures used in PostGIS's documentation pages.
@@ -33,7 +35,7 @@
* Usage:
* generator [-v] [-s <width>x<height>] <source_wktfile> [<output_pngfile>]
*
- * -v - show generated Imagemagick commands
+ * -v - show generated GraphicsMagick commands
* -s - output dimension, if omitted defaults to 200x200
*
* If <output_pngfile> is omitted the output image PNG file has the
@@ -44,459 +46,783 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <unistd.h> /* for rmdir */
#include <ctype.h>
-#include <sys/wait.h> /* for WEXITSTATUS */
+#include <math.h>
#include <stdbool.h>
-#include <sys/types.h>
-#include <dirent.h>
+#include <sys/wait.h> /* for WEXITSTATUS */
+#ifndef _WIN32
+#include <unistd.h>
+#else
+#include <io.h>
+#ifndef X_OK
+#define X_OK 0
+#endif
+#define access _access
+#endif
#include "liblwgeom_internal.h"
#include "lwgeom_log.h"
+#include "stringbuffer.h"
#include "styles.h"
-#define SHOW_DIGS_DOUBLE 15
-#define MAX_DOUBLE_PRECISION 15
-#define MAX_DIGS_DOUBLE (SHOW_DIGS_DOUBLE + 2) /* +2 for dot and sign */
+typedef struct generator_options {
+ bool verbose;
+ const char *image_size;
+} generator_options;
-bool optionVerbose = false;
+/**
+ * Emit the command-line synopsis expected by documentation maintainers.
+ *
+ * The helper keeps the main routine compact and ensures both -h and --help
+ * share the exact same prose, which simplifies future updates to the option
+ * set.
+ */
+static void
+print_usage(FILE *stream, const char *progname)
+{
+ fprintf(stream,
+ "Usage: %s [-v] [-s <width>x<height>] <source_wktfile> [<output_pngfile>]\n"
+ "\n"
+ "Options:\n"
+ " -v Emit the assembled GraphicsMagick/ImageMagick command.\n"
+ " -s <WxH> Override the output canvas size (default: 200x200).\n"
+ " -h, --help Display this help text and exit.\n"
+ "\n"
+ "If <output_pngfile> is omitted the generator derives it from the source\n"
+ "WKT filename.\n",
+ progname);
+}
-// Some global styling variables
-const char *imageSize = "200x200";
-
-char tempdir_template[] = "generator-XXXXXX";
-char *tmpdir = NULL;
-
-typedef struct draw_context_t {
- LAYERSTYLE *style;
- const char *tmpdir;
- int drawNum; /* number of draw commands */
-} GEOMETRY_DRAW_CONTEXT;
+typedef struct generator_job {
+ generator_options options;
+ const char *converter_cli;
+ LAYERSTYLE *styles;
+ stringbuffer_t command;
+} generator_job;
static void
-initializeGeometryDrawContext(GEOMETRY_DRAW_CONTEXT *ctx) {
- ctx->style = NULL;
- ctx->tmpdir = NULL;
- ctx->drawNum = 0;
+generator_job_init(generator_job *job, const generator_options *options)
+{
+ job->options = *options;
+ job->converter_cli = NULL;
+ job->styles = NULL;
+ stringbuffer_init(&job->command);
}
static void
-checked_system(const char* cmd)
+generator_job_reset(generator_job *job)
{
- int ret = system(cmd);
- if ( WEXITSTATUS(ret) != 0 ) {
- fprintf(stderr, "Failure return code (%d) from command: %s", WEXITSTATUS(ret), cmd);
+ if (job->styles)
+ {
+ freeStyles(&job->styles);
+ job->styles = NULL;
+ }
+
+ stringbuffer_release(&job->command);
+}
+
+typedef struct draw_context_t {
+ LAYERSTYLE *style;
+} GEOMETRY_DRAW_CONTEXT;
+
+static GEOMETRY_DRAW_CONTEXT
+geometry_draw_context_init(void)
+{
+ GEOMETRY_DRAW_CONTEXT ctx;
+ ctx.style = NULL;
+ return ctx;
+}
+
+/**
+ * Execute an external command and abort on failure.
+ *
+ * Several raster utilities also rely on libc's \c system() to orchestrate
+ * external programs, so keeping this helper self-contained makes it trivial to
+ * promote into a shared header if we ever need the same GraphicsMagick
+ * pipeline while testing rasters.
+ */
+static void
+checked_system(const char *cmd)
+{
+ int ret = system(cmd);
+
+ if (ret == -1)
+ {
+ perror("system");
+ fprintf(stderr, "Unable to execute command: %s\n", cmd);
+ exit(EXIT_FAILURE);
+ }
+
+ if (!WIFEXITED(ret) || WEXITSTATUS(ret) != 0)
+ {
+ fprintf(stderr, "Failure return code (%d) from command: %s\n", WEXITSTATUS(ret), cmd);
exit(EXIT_FAILURE);
}
}
-static void
-cleanupTempDir(const char *dir)
+/*
+ * Locate executables in PATH using liblwgeom's stringbuffer helpers so the
+ * probing logic can be promoted to the raster tooling if it ever needs the
+ * same GraphicsMagick/ImageMagick detection.
+ */
+static bool
+command_exists(const char *cmd)
{
- struct dirent *p;
- DIR *d = opendir(dir);
- char *buf;
- size_t maxlen;
+ const char *path_env;
+ const char *cursor;
- if ( NULL == d ) {
- perror( dir );
- exit(EXIT_FAILURE); /* or be tolerant ? */
+ if (cmd == NULL || *cmd == '\0')
+ return false;
+
+#ifdef _WIN32
+ if (strchr(cmd, ':') || strchr(cmd, '\\'))
+ {
+ return access(cmd, X_OK) == 0;
+ }
+#else
+ if (strchr(cmd, '/'))
+ {
+ return access(cmd, X_OK) == 0;
+ }
+#endif
+
+ path_env = getenv("PATH");
+ if (!path_env || !*path_env)
+ return false;
+
+ cursor = path_env;
+ while (*cursor)
+ {
+ const char *sep;
+ size_t dir_len;
+ stringbuffer_t candidate;
+
+ sep = strchr(cursor,
+#ifdef _WIN32
+ ';'
+#else
+ ':'
+#endif
+ );
+ dir_len = sep ? (size_t)(sep - cursor) : strlen(cursor);
+
+ stringbuffer_init(&candidate);
+ if (dir_len == 0)
+ {
+ stringbuffer_append(&candidate, ".");
+ }
+ else
+ {
+ stringbuffer_append_len(&candidate, cursor, dir_len);
+ }
+
+ if (stringbuffer_getlength(&candidate) > 0)
+ {
+ char last = stringbuffer_lastchar(&candidate);
+#ifdef _WIN32
+ if (last != '/' && last != '\\')
+ stringbuffer_append_char(&candidate, '\\');
+#else
+ if (last != '/')
+ stringbuffer_append_char(&candidate, '/');
+#endif
+ }
+
+ stringbuffer_append(&candidate, cmd);
+
+ if (access(stringbuffer_getstring(&candidate), X_OK) == 0)
+ {
+ stringbuffer_release(&candidate);
+ return true;
+ }
+
+#ifdef _WIN32
+ stringbuffer_append(&candidate, ".exe");
+ if (access(stringbuffer_getstring(&candidate), X_OK) == 0)
+ {
+ stringbuffer_release(&candidate);
+ return true;
+ }
+#endif
+
+ stringbuffer_release(&candidate);
+
+ if (!sep)
+ break;
+ cursor = sep + 1;
}
- maxlen = strlen(dir) + 64;
- buf = malloc(maxlen);
+ return false;
+}
- while ( (p=readdir(d)) ) {
- if ( strcmp(p->d_name, ".") == 0 ) continue;
- if ( strcmp(p->d_name, ".." ) == 0) continue;
- snprintf(buf, maxlen-1, "%s/%s", dir, p->d_name);
- remove(buf);
+/*
+ * Prefer GraphicsMagick but gracefully fall back to ImageMagick 7 ("magick")
+ * or the legacy "convert" binary so older documentation builds keep working.
+ */
+static const char *
+select_converter_cli(void)
+{
+ const char *override = getenv("POSTGIS_DOC_CONVERTER");
+ if (override && *override)
+ return override;
+
+ if (command_exists("gm"))
+ return "gm convert";
+
+ if (command_exists("magick"))
+ return "magick convert";
+
+ if (command_exists("convert"))
+ return "convert";
+
+ return NULL;
+}
+
+static char *
+derive_styles_path(const char *source_path)
+{
+ const char *styles_basename = "styles.conf";
+ const char *slash = strrchr(source_path, '/');
+ char *resolved;
+
+ if (!slash)
+ return lwstrdup(styles_basename);
+
+ {
+ size_t dir_len = (size_t)(slash - source_path);
+ size_t basename_len = strlen(styles_basename);
+ size_t total = dir_len + 1 + basename_len + 1;
+
+ resolved = lwalloc(total);
+ if (!resolved)
+ return NULL;
+
+ memcpy(resolved, source_path, dir_len);
+ resolved[dir_len] = '/';
+ memcpy(resolved + dir_len + 1, styles_basename, basename_len + 1);
}
- closedir(d);
- rmdir(dir);
+ return resolved;
+}
+
+static char *
+derive_output_path(const char *source_path, const char *override_path)
+{
+ size_t len;
+ char *result;
+
+ if (override_path && *override_path)
+ return lwstrdup(override_path);
+
+ len = strlen(source_path);
+ result = lwstrdup(source_path);
+ if (!result)
+ return NULL;
+
+ if (len >= 3)
+ memcpy(result + len - 3, "png", 3);
+
+ return result;
}
/**
- * Writes the coordinates of a POINTARRAY to a FILE* where ordinates are
- * separated by a comma and coordinates by a space so that the coordinate
- * pairs can be interpreted by ImageMagick's SVG draw command.
- *
- * @param output a file to write the POINTARRAY to
- * @param pa a reference to a POINTARRAY
- * @return the numbers of character written to *output
+ * Append a coordinate pair as "x,y" so the resulting buffer can be reused by
+ * both the documentation generator and raster command builders that already
+ * rely on liblwgeom's stringbuffer helpers.
*/
-static size_t
-pointarrayToFile(FILE *output, POINTARRAY *pa)
+static void
+append_coord_pair(stringbuffer_t *sb, double x, double y)
{
- char x[OUT_DOUBLE_BUFFER_SIZE];
- char y[OUT_DOUBLE_BUFFER_SIZE];
- unsigned int i, written = 0;
+ stringbuffer_append_double(sb, x, 10);
+ stringbuffer_append_char(sb, ',');
+ stringbuffer_append_double(sb, y, 10);
+}
- for ( i=0; i < pa->npoints; i++ )
+static void
+pointarrayToBuffer(stringbuffer_t *output, POINTARRAY *pa)
+{
+ unsigned int i;
+
+ for (i = 0; i < pa->npoints; i++)
{
POINT2D pt;
getPoint2d_p(pa, i, &pt);
- lwprint_double(pt.x, 10, x);
- lwprint_double(pt.y, 10, y);
-
- if ( i ) written += fprintf(output, " ");
- written += fprintf(output, "%s,%s", x, y);
+ if (i)
+ stringbuffer_append_char(output, ' ');
+ append_coord_pair(output, pt.x, pt.y);
}
-
- return written;
}
/**
- * Draws a point in a POINTARRAY to a char* using ImageMagick SVG for styling.
+ * Draws a point in a POINTARRAY to a char* using GraphicsMagick SVG for styling.
* @param output a char reference to write the LWPOINT to
* @param lwp a reference to a LWPOINT
- * @return the numbers of character written to *output
+ *
+ * The drawing commands are appended directly to the supplied buffer.
*/
-static size_t
-drawPointSymbol(char *output, POINTARRAY *pa, unsigned int index, int size, char* color)
+static void
+drawPointSymbol(stringbuffer_t *output, POINTARRAY *pa, unsigned int index, int size, const char *color)
{
// short-circuit no-op
- if (size <= 0) return 0;
-
- char x[OUT_DOUBLE_BUFFER_SIZE];
- char y1[OUT_DOUBLE_BUFFER_SIZE];
- char y2[OUT_DOUBLE_BUFFER_SIZE];
- char *ptr = output;
+ if (size <= 0)
+ return;
POINT2D p;
getPoint2d_p(pa, index, &p);
- lwprint_double(p.x, 10, x);
- lwprint_double(p.y, 10, y1);
- lwprint_double(p.y + size, 10, y2);
-
- ptr += sprintf(ptr, "-fill %s -strokewidth 0 ", color);
- ptr += sprintf(ptr, "-draw \"circle %s,%s %s,%s", x, y1, x, y2);
- ptr += sprintf(ptr, "'\" ");
-
- return (ptr - output);
+ stringbuffer_aprintf(output, "-fill %s -strokewidth 0 ", color);
+ stringbuffer_append(output, "-draw \"circle ");
+ append_coord_pair(output, p.x, p.y);
+ stringbuffer_append_char(output, ' ');
+ append_coord_pair(output, p.x, p.y + size);
+ stringbuffer_append(output, "\" ");
}
/**
- * Draws a point in a POINTARRAY to a char* using ImageMagick SVG for styling.
+ * Draws a point in a POINTARRAY to a char* using GraphicsMagick SVG for styling.
* @param output a char reference to write the LWPOINT to
* @param lwp a reference to a LWPOINT
- * @return the numbers of character written to *output
+ *
+ * The drawing commands are appended directly to the supplied buffer.
*/
-static size_t
-drawLineArrow(char *output, POINTARRAY *pa, int size, int strokeWidth, char* color)
+static void
+drawLineArrow(stringbuffer_t *output, POINTARRAY *pa, int size, int strokeWidth, const char *color)
{
// short-circuit no-op
- if (size <= 0) return 0;
- if (pa->npoints <= 1) return 0;
-
- char s0x[OUT_DOUBLE_BUFFER_SIZE];
- char s0y[OUT_DOUBLE_BUFFER_SIZE];
- char s1x[OUT_DOUBLE_BUFFER_SIZE];
- char s1y[OUT_DOUBLE_BUFFER_SIZE];
- char s2x[OUT_DOUBLE_BUFFER_SIZE];
- char s2y[OUT_DOUBLE_BUFFER_SIZE];
+ if (size <= 0)
+ return;
+ if (pa->npoints <= 1)
+ return;
POINT2D pn;
- getPoint2d_p(pa, pa->npoints-1, &pn);
+ getPoint2d_p(pa, pa->npoints - 1, &pn);
POINT2D pn1;
- getPoint2d_p(pa, pa->npoints-2, &pn1);
+ getPoint2d_p(pa, pa->npoints - 2, &pn1);
double dx = pn1.x - pn.x;
double dy = pn1.y - pn.y;
- double len = sqrt(dx*dx + dy*dy);
+ double len = sqrt(dx * dx + dy * dy);
//-- abort if final line segment has length 0
- if (len <= 0) return 0;
-
- double offx = -0.5 * size * dy/len;
- double offy = 0.5 * size * dx/len;
+ if (len <= 0)
+ return;
+ double offx = -0.5 * size * dy / len;
+ double offy = 0.5 * size * dx / len;
double p1x = pn.x + size * dx / len + offx;
double p1y = pn.y + size * dy / len + offy;
double p2x = pn.x + size * dx / len - offx;
double p2y = pn.y + size * dy / len - offy;
- lwprint_double(pn.x, 10, s0x);
- lwprint_double(pn.y, 10, s0y);
- lwprint_double(p1x, 10, s1x);
- lwprint_double(p1y, 10, s1y);
- lwprint_double(p2x, 10, s2x);
- lwprint_double(p2y, 10, s2y);
-
- char *ptr = output;
- ptr += sprintf(ptr, "-fill %s -strokewidth %d ", color, 2);
- ptr += sprintf(ptr, "-draw \"path 'M %s,%s %s,%s %s,%s %s,%s'\" ", s0x, s0y, s1x, s1y, s2x, s2y, s0x, s0y);
-
- return (ptr - output);
+ stringbuffer_aprintf(output, "-fill %s -strokewidth %d ", color, 2);
+ stringbuffer_append(output, "-draw \"path 'M ");
+ append_coord_pair(output, pn.x, pn.y);
+ stringbuffer_append_char(output, ' ');
+ append_coord_pair(output, p1x, p1y);
+ stringbuffer_append_char(output, ' ');
+ append_coord_pair(output, p2x, p2y);
+ stringbuffer_append_char(output, ' ');
+ append_coord_pair(output, pn.x, pn.y);
+ stringbuffer_append(output, "'\" ");
}
/**
* Serializes a LWPOINT to a char*. This is a helper function that partially
* writes the appropriate draw and fill commands used to generate an SVG image
- * using ImageMagick's "convert" command.
+ * using GraphicsMagick's "gm convert" command.
* @param output a char reference to write the LWPOINT to
* @param lwp a reference to a LWPOINT
- * @return the numbers of character written to *output
+ *
+ * The drawing commands are appended directly to the supplied buffer.
*/
-static size_t
-drawPoint(char *output, LWPOINT *lwp, GEOMETRY_DRAW_CONTEXT *ctx)
+static void
+drawPoint(stringbuffer_t *output, LWPOINT *lwp, GEOMETRY_DRAW_CONTEXT *ctx)
{
- char x[OUT_DOUBLE_BUFFER_SIZE];
- char y1[OUT_DOUBLE_BUFFER_SIZE];
- char y2[OUT_DOUBLE_BUFFER_SIZE];
- char *ptr = output;
LAYERSTYLE *styles = ctx->style;
POINTARRAY *pa = lwp->point;
POINT2D p;
getPoint2d_p(pa, 0, &p);
LWDEBUGF(4, "%s", "drawPoint called");
- LWDEBUGF( 4, "point = %s", lwgeom_to_ewkt((LWGEOM*)lwp) );
+ LWDEBUGF(4, "point = %s", lwgeom_to_ewkt((LWGEOM *)lwp));
- lwprint_double(p.x, 10, x);
- lwprint_double(p.y, 10, y1);
- lwprint_double(p.y + styles->pointSize, 10, y2);
-
- ptr += sprintf(ptr, "-fill %s -strokewidth 0 ", styles->pointColor);
- ptr += sprintf(ptr, "-draw \"circle %s,%s %s,%s", x, y1, x, y2);
- ptr += sprintf(ptr, "'\" ");
-
- return (ptr - output);
+ stringbuffer_aprintf(output, "-fill %s -strokewidth 0 ", styles->pointColor);
+ stringbuffer_append(output, "-draw \"circle ");
+ append_coord_pair(output, p.x, p.y);
+ stringbuffer_append_char(output, ' ');
+ append_coord_pair(output, p.x, p.y + styles->pointSize);
+ stringbuffer_append(output, "\" ");
}
/**
* Serializes a LWLINE to a char*. This is a helper function that partially
* writes the appropriate draw and stroke commands used to generate an SVG image
- * using ImageMagick's "convert" command.
+ * using GraphicsMagick's "gm convert" command.
* @param output a char reference to write the LWLINE to
* @param lwl a reference to a LWLINE
- * @return the numbers of character written to *output
+ *
+ * The drawing commands are appended directly to the supplied buffer.
*/
-static size_t
-drawLineString(char *output, LWLINE *lwl, GEOMETRY_DRAW_CONTEXT *ctx)
+static void
+drawLineString(stringbuffer_t *output, LWLINE *lwl, GEOMETRY_DRAW_CONTEXT *ctx)
{
- char *ptr = output;
LAYERSTYLE *style = ctx->style;
- char *drawFname;
- FILE *drawFile;
-
LWDEBUGF(4, "%s", "drawLineString called");
- LWDEBUGF( 4, "line = %s", lwgeom_to_ewkt((LWGEOM*)lwl) );
+ LWDEBUGF(4, "line = %s", lwgeom_to_ewkt((LWGEOM *)lwl));
- ptr += sprintf(ptr, "-fill none -stroke %s -strokewidth %d ", style->lineColor, style->lineWidth);
+ stringbuffer_aprintf(output, "-fill none -stroke %s -strokewidth %d ", style->lineColor, style->lineWidth);
- ptr += sprintf(ptr, "-draw '@");
- drawFname = ptr; /* hack to save allocating a new string just for the filename */
- ptr += sprintf(ptr, "%s/draw%d", ctx->tmpdir, ctx->drawNum++);
- drawFile = fopen(drawFname, "w");
- if ( NULL == drawFile ) {
- perror( drawFname );
- exit(EXIT_FAILURE); /* or be tolerant ? */
- }
- ptr += sprintf(ptr, "' "); /* from now on drawFname is invalid */
+ stringbuffer_t path;
+ stringbuffer_init(&path);
+ stringbuffer_append(&path, "stroke-linecap round stroke-linejoin round path 'M ");
+ pointarrayToBuffer(&path, lwl->points);
+ stringbuffer_append(&path, "'");
- fprintf(drawFile, "stroke-linecap round stroke-linejoin round path 'M ");
- pointarrayToFile(drawFile, lwl->points );
- fprintf(drawFile, "'");
+ stringbuffer_append(output, "-draw \"");
+ stringbuffer_append(output, stringbuffer_getstring(&path));
+ stringbuffer_append(output, "\" ");
- fclose(drawFile);
+ stringbuffer_release(&path);
- ptr += drawPointSymbol(ptr, lwl->points, 0, style->lineStartSize, style->lineColor);
- ptr += drawPointSymbol(ptr, lwl->points, lwl->points->npoints-1, style->lineEndSize, style->lineColor);
- ptr += drawLineArrow(ptr, lwl->points, style->lineArrowSize, style->lineWidth, style->lineColor);
-
- return (ptr - output);
+ drawPointSymbol(output, lwl->points, 0, style->lineStartSize, style->lineColor);
+ drawPointSymbol(output, lwl->points, lwl->points->npoints - 1, style->lineEndSize, style->lineColor);
+ drawLineArrow(output, lwl->points, style->lineArrowSize, style->lineWidth, style->lineColor);
}
/**
* Serializes a LWPOLY to a char*. This is a helper function that partially
* writes the appropriate draw and fill commands used to generate an SVG image
- * using ImageMagick's "convert" command.
+ * using GraphicsMagick's "gm convert" command.
* @param output a char reference to write the LWPOLY to
* @param lwp a reference to a LWPOLY
- * @return the numbers of character written to *output
+ *
+ * The drawing commands are appended directly to the supplied buffer.
*/
-static size_t
-drawPolygon(char *output, LWPOLY *lwp, GEOMETRY_DRAW_CONTEXT *ctx)
+static void
+drawPolygon(stringbuffer_t *output, LWPOLY *lwp, GEOMETRY_DRAW_CONTEXT *ctx)
{
- char *ptr = output;
unsigned int i;
LAYERSTYLE *style = ctx->style;
- char *drawFname;
- FILE *drawFile;
LWDEBUGF(4, "%s", "drawPolygon called");
- LWDEBUGF( 4, "poly = %s", lwgeom_to_ewkt((LWGEOM*)lwp) );
+ LWDEBUGF(4, "poly = %s", lwgeom_to_ewkt((LWGEOM *)lwp));
- ptr += sprintf(ptr, "-fill %s -stroke %s -strokewidth %d ", style->polygonFillColor, style->polygonStrokeColor, style->polygonStrokeWidth );
+ stringbuffer_aprintf(output,
+ "-fill %s -stroke %s -strokewidth %d ",
+ style->polygonFillColor,
+ style->polygonStrokeColor,
+ style->polygonStrokeWidth);
- ptr += sprintf(ptr, "-draw '@");
- drawFname = ptr; /* hack to save allocating a new string just for the filename */
- ptr += sprintf(ptr, "%s/draw%d", ctx->tmpdir, ctx->drawNum++);
- drawFile = fopen(drawFname, "w");
- if ( NULL == drawFile ) {
- perror( drawFname );
- exit(EXIT_FAILURE); /* or be tolerant ? */
- }
- ptr += sprintf(ptr, "' "); /* from now on drawFname is invalid */
-
- fprintf(drawFile, "path '");
- for (i=0; i<lwp->nrings; i++)
+ stringbuffer_t path;
+ stringbuffer_init(&path);
+ stringbuffer_append(&path, "path '");
+ for (i = 0; i < lwp->nrings; i++)
{
- fprintf(drawFile, "M ");
- pointarrayToFile(drawFile, lwp->rings[i] );
- fprintf(drawFile, " ");
+ stringbuffer_append(&path, "M ");
+ pointarrayToBuffer(&path, lwp->rings[i]);
+ stringbuffer_append_char(&path, ' ');
}
- fprintf(drawFile, "'");
+ stringbuffer_append(&path, "'");
- fclose(drawFile);
+ stringbuffer_append(output, "-draw \"");
+ stringbuffer_append(output, stringbuffer_getstring(&path));
+ stringbuffer_append(output, "\" ");
- return (ptr - output);
+ stringbuffer_release(&path);
}
/**
* Serializes a LWGEOM to a char*. This is a helper function that partially
* writes the appropriate draw, stroke, and fill commands used to generate an
- * SVG image using ImageMagick's "convert" command.
+ * SVG image using GraphicsMagick's "gm convert" command.
* @param output a char reference to write the LWGEOM to
* @param lwgeom a reference to a LWGEOM
* @param ctx drawing context
- * @return the numbers of character written to *output
+ *
+ * The drawing commands are appended directly to the supplied buffer.
*/
-static size_t
-drawGeometry(char *output, const LWGEOM *lwgeom, GEOMETRY_DRAW_CONTEXT *ctx)
+static void
+drawGeometry(stringbuffer_t *output, const LWGEOM *lwgeom, GEOMETRY_DRAW_CONTEXT *ctx)
{
- char *ptr = output;
unsigned int i;
int type = lwgeom->type;
switch (type)
{
case POINTTYPE:
- ptr += drawPoint(ptr, (LWPOINT*)lwgeom, ctx );
+ drawPoint(output, (LWPOINT *)lwgeom, ctx);
break;
case LINETYPE:
- ptr += drawLineString(ptr, (LWLINE*)lwgeom, ctx );
+ drawLineString(output, (LWLINE *)lwgeom, ctx);
break;
case POLYGONTYPE:
- ptr += drawPolygon(ptr, (LWPOLY*)lwgeom, ctx );
+ drawPolygon(output, (LWPOLY *)lwgeom, ctx);
break;
case MULTIPOINTTYPE:
case MULTILINETYPE:
case MULTIPOLYGONTYPE:
case COLLECTIONTYPE:
- for (i=0; i<((LWCOLLECTION*)lwgeom)->ngeoms; i++)
+ for (i = 0; i < ((LWCOLLECTION *)lwgeom)->ngeoms; i++)
{
- ptr += drawGeometry( ptr, lwcollection_getsubgeom ((LWCOLLECTION*)lwgeom, i), ctx );
+ drawGeometry(output, lwcollection_getsubgeom((LWCOLLECTION *)lwgeom, i), ctx);
}
break;
}
-
- return (ptr - output);
}
/**
- * Invokes a system call to ImageMagick's "convert" command that reduces
- * the overall filesize
- *
- * @param filename the current working image.
+ * Extract an optional "style;WKT" prefix and return both pieces. When the
+ * prefix is absent the caller falls back to the "Default" block in
+ * styles.conf while keeping the full line as WKT input.
*/
static void
-optimizeImage(char* filename)
+parse_layer_line(const char *line, char **style_name, const char **wkt_literal, bool *uses_default)
{
- char *str;
- str = malloc( (18 + (2*strlen(filename)) + 1) * sizeof(char) );
- sprintf(str, "convert %s -depth 8 %s", filename, filename);
- if (optionVerbose) {
- puts(str);
+ const char *separator = strrchr(line, ';');
+
+ if (separator == NULL)
+ {
+ *style_name = lwstrdup("Default");
+ if (!*style_name)
+ {
+ lwerror("Out of memory while reading style name");
+ *wkt_literal = NULL;
+ *uses_default = true;
+ return;
+ }
+ *wkt_literal = line;
+ *uses_default = true;
+ return;
}
- checked_system(str);
- free(str);
+
+ {
+ size_t style_len = (size_t)(separator - line);
+ *style_name = lwalloc(style_len + 1);
+ if (!*style_name)
+ {
+ lwerror("Out of memory while reading style name");
+ *style_name = NULL;
+ *wkt_literal = NULL;
+ *uses_default = true;
+ return;
+ }
+
+ memcpy(*style_name, line, style_len);
+ (*style_name)[style_len] = '\0';
+ }
+
+ *wkt_literal = separator + 1;
+ *uses_default = false;
}
/**
- * Flattens all the temporary processing png files into a single image
+ * Stream all non-empty layers from @a source into @a job's command buffer.
+ * The helper mirrors the raster tooling, which prefers to marshal draw
+ * commands in memory before forking external utilities.
*/
-static void
-flattenLayers(char* filename)
-{
- char *str = malloc( (48 + strlen(filename) + strlen(tmpdir) + 2) * sizeof(char) );
- sprintf(str, "convert %s/tmp*.png -background white -flatten %s", tmpdir, filename);
- if (optionVerbose) {
- puts(str);
- }
-
- LWDEBUGF(4, "%s", str);
- checked_system(str);
- free(str);
-}
-
-
-// TODO: comments
static int
-getStyleName(char **styleName, const char* line)
+append_layers(generator_job *job, FILE *source)
{
- char *ptr = strrchr(line, ';');
- if (ptr == NULL)
+ char line[65536];
+ int layer_index = 0;
+
+ while (fgets(line, sizeof line, source) != NULL)
{
- *styleName = strdup("Default");
- return 1;
- }
- else
- {
- *styleName = malloc( ptr - line + 1);
- strncpy(*styleName, line, ptr - line);
- (*styleName)[ptr - line] = '\0';
- LWDEBUGF( 4, "%s", *styleName );
- return 0;
+ if (isspace((unsigned char)line[0]))
+ break;
+
+ GEOMETRY_DRAW_CONTEXT ctx = geometry_draw_context_init();
+ char *style_name = NULL;
+ const char *wkt_literal = NULL;
+ bool uses_default_style = false;
+ LWGEOM *lwgeom;
+
+ parse_layer_line(line, &style_name, &wkt_literal, &uses_default_style);
+
+ if (!style_name || !wkt_literal)
+ {
+ lwfree(style_name);
+ return -1;
+ }
+
+ if (uses_default_style)
+ printf(" Warning: using Default style for layer %d\n", layer_index);
+
+ lwgeom = lwgeom_from_wkt(wkt_literal, LW_PARSER_CHECK_NONE);
+ if (!lwgeom)
+ {
+ lwerror("Could not parse geometry for layer %d", layer_index);
+ lwfree(style_name);
+ return -1;
+ }
+
+ LWDEBUGF(4, "geom = %s", lwgeom_to_ewkt(lwgeom));
+
+ ctx.style = getStyle(job->styles, style_name);
+ if (!ctx.style)
+ {
+ lwgeom_free(lwgeom);
+ lwerror("Could not find style named %s", style_name);
+ lwfree(style_name);
+ return -1;
+ }
+
+ drawGeometry(&job->command, lwgeom, &ctx);
+
+ lwgeom_free(lwgeom);
+ lwfree(style_name);
+ layer_index++;
}
+
+ return layer_index;
}
-int parseOptions(int argc, const char* argv[] )
+/**
+ * Parse command-line switches shared with the historical ImageMagick driver.
+ * The generator continues to accept the same options so existing Makefile
+ * rules and scripts do not need updates.
+ */
+static int
+parse_options(int argc, const char *argv[], generator_options *opts, bool *show_help)
{
- if (argc <= 1) return 1;
+ int arg_pos = 1;
- int argPos = 1;
- while (argPos < argc && strncmp(argv[argPos], "-", 1) == 0) {
- if (strncmp(argv[argPos], "-v", 2) == 0) {
- optionVerbose = true;
+ opts->verbose = false;
+ opts->image_size = "200x200";
+ *show_help = false;
+
+ if (argc <= 1)
+ return -1;
+
+ while (arg_pos < argc && argv[arg_pos][0] == '-')
+ {
+ if (strcmp(argv[arg_pos], "-h") == 0 || strcmp(argv[arg_pos], "--help") == 0)
+ {
+ *show_help = true;
+ return argc;
}
- if (strncmp(argv[argPos], "-s", 2) == 0) {
- if ( ++argPos >= argc ) return 1;
- imageSize = argv[argPos];
+
+ if (strncmp(argv[arg_pos], "-v", 2) == 0)
+ {
+ opts->verbose = true;
+ arg_pos++;
+ continue;
}
- argPos++;
+
+ if (strncmp(argv[arg_pos], "-s", 2) == 0)
+ {
+ if (++arg_pos >= argc)
+ return -1;
+ opts->image_size = argv[arg_pos];
+ arg_pos++;
+ continue;
+ }
+
+ return -1;
}
- return argPos;
+
+ return arg_pos;
+}
+
+static int
+generator_render(const generator_options *options, const char *source_path, const char *target_override)
+{
+ generator_job job;
+ FILE *source = NULL;
+ char *styles_path = NULL;
+ char *target_path = NULL;
+ int rc = -1;
+ const char *converter_cli;
+
+ generator_job_init(&job, options);
+
+ converter_cli = select_converter_cli();
+ if (!converter_cli)
+ {
+ lwerror(
+ "Could not find GraphicsMagick or ImageMagick executables (gm, magick, convert). Set POSTGIS_DOC_CONVERTER to the desired command.");
+ goto cleanup;
+ }
+ job.converter_cli = converter_cli;
+
+ source = fopen(source_path, "r");
+ if (!source)
+ {
+ perror(source_path);
+ goto cleanup;
+ }
+
+ styles_path = derive_styles_path(source_path);
+ if (!styles_path)
+ {
+ lwerror("Out of memory while resolving styles");
+ goto cleanup;
+ }
+
+ printf("reading styles from %s\n", styles_path);
+ getStyles(styles_path, &job.styles);
+
+ target_path = derive_output_path(source_path, target_override);
+ if (!target_path)
+ {
+ lwerror("Out of memory while preparing output filename");
+ goto cleanup;
+ }
+
+ printf("generating %s\n", target_path);
+
+ stringbuffer_aprintf(&job.command, "%s -size %s xc:none ", job.converter_cli, job.options.image_size);
+
+ if (append_layers(&job, source) < 0)
+ goto cleanup;
+
+ stringbuffer_append(&job.command, "-flip -background white -flatten -depth 8 ");
+ stringbuffer_append(&job.command, target_path);
+
+ if (job.options.verbose)
+ puts(stringbuffer_getstring(&job.command));
+
+ checked_system(stringbuffer_getstring(&job.command));
+
+ rc = 0;
+
+cleanup:
+ if (source)
+ fclose(source);
+ if (styles_path)
+ lwfree(styles_path);
+ if (target_path)
+ lwfree(target_path);
+ generator_job_reset(&job);
+ return rc;
}
/**
* Main Application.
*/
-int main( int argc, const char* argv[] )
+int
+main(int argc, const char *argv[])
{
- FILE *pfile;
- LWGEOM *lwgeom;
- char line [65536];
- char *filename;
- int layerCount;
- LAYERSTYLE *styles;
- char *stylefile_path;
+ generator_options options;
const char *image_src;
- char *ptr;
- const char *stylefilename = "styles.conf";
+ const char *target_override = NULL;
+ bool show_help;
- int filePos = parseOptions(argc, argv);
- if ( filePos >= argc || strlen(argv[filePos]) < 3)
+ int filePos = parse_options(argc, argv, &options, &show_help);
+ if (show_help)
+ {
+ print_usage(stdout, argv[0]);
+ return 0;
+ }
+
+ if (filePos < 0 || filePos >= argc || strlen(argv[filePos]) < 3)
{
lwerror("Usage: %s [-v] [-s <width>x<height>] <source_wktfile> [<output_pngfile>]", argv[0]);
return -1;
@@ -504,106 +830,11 @@ int main( int argc, const char* argv[] )
image_src = argv[filePos];
- if ( (pfile = fopen(image_src, "r")) == NULL)
- {
- perror ( image_src );
+ if (argc - filePos >= 2)
+ target_override = argv[filePos + 1];
+
+ if (generator_render(&options, image_src, target_override) != 0)
return -1;
- }
-
- /* Get style */
- ptr = rindex( image_src, '/' );
- if ( ptr ) /* source image file has a slash */
- {
- size_t dirname_len = (ptr - image_src);
- stylefile_path = malloc( strlen(stylefilename) + dirname_len + 2);
- /* copy the directory name */
- memcpy(stylefile_path, image_src, dirname_len);
- sprintf(stylefile_path + dirname_len, "/%s", stylefilename);
- }
- else /* source image file has no slash, use CWD */
- {
- stylefile_path = strdup(stylefilename);
- }
- printf("reading styles from %s\n", stylefile_path);
- getStyles(stylefile_path, &styles);
- free(stylefile_path);
-
- if ( argc - filePos >= 2 )
- {
- filename = strdup(argv[filePos + 1]);
- }
- else
- {
- filename = strdup(image_src);
- sprintf(filename + strlen(image_src) - 3, "png" );
- }
-
- tmpdir = mkdtemp(tempdir_template);
- if ( NULL == tmpdir ) {
- perror ( image_src );
- exit(EXIT_FAILURE);
- }
-
- printf( "generating %s\n", filename );
-
- layerCount = 0;
- while ( fgets ( line, sizeof line, pfile ) != NULL && !isspace(*line) )
- {
- GEOMETRY_DRAW_CONTEXT ctx;
- char output[32768];
- char *ptr = output;
- char *styleName;
- int useDefaultStyle;
-
- initializeGeometryDrawContext(&ctx);
- ctx.tmpdir = tmpdir;
-
- ptr += sprintf( ptr, "convert -size %s xc:none ", imageSize );
-
- useDefaultStyle = getStyleName(&styleName, line);
- LWDEBUGF( 4, "%s", styleName );
-
- if (useDefaultStyle)
- {
- printf(" Warning: using Default style for layer %d\n", layerCount);
- lwgeom = lwgeom_from_wkt( line, LW_PARSER_CHECK_NONE );
- }
- else
- lwgeom = lwgeom_from_wkt( line+strlen(styleName)+1, LW_PARSER_CHECK_NONE );
-
- LWDEBUGF( 4, "geom = %s", lwgeom_to_ewkt((LWGEOM*)lwgeom) );
-
- ctx.style = getStyle(styles, styleName);
- if ( ! ctx.style ) {
- lwerror("Could not find style named %s", styleName);
- free(styleName);
- return -1;
- }
- free(styleName);
-
- ptr += drawGeometry( ptr, lwgeom, &ctx );
-
- ptr += sprintf( ptr, "-flip %s/tmp%d.png", tmpdir, layerCount );
-
- lwgeom_free( lwgeom );
-
- LWDEBUGF( 4, "%s", output );
- if (optionVerbose) {
- puts(output);
- }
- checked_system(output);
-
- layerCount++;
- }
-
- flattenLayers(filename);
- optimizeImage(filename);
-
- fclose(pfile);
- free(filename);
- freeStyles(&styles);
-
- cleanupTempDir(tmpdir);
return 0;
}
diff --git a/doc/installation.xml b/doc/installation.xml
index 84cd1aef4..13305910b 100644
--- a/doc/installation.xml
+++ b/doc/installation.xml
@@ -298,8 +298,15 @@ sh autogen.sh
<listitem>
<para>
- ImageMagick (<filename>convert</filename>) is required to generate the
- images used in the documentation. ImageMagick is available from
+ GraphicsMagick (<filename>gm convert</filename>) is preferred to
+ generate the images used in the documentation. If GraphicsMagick is
+ unavailable, ImageMagick (<filename>convert</filename> or
+ <filename>magick convert</filename>) provides the same command-line
+ syntax. GraphicsMagick is available from
+ <link xlink:href="http://www.graphicsmagick.org/">
+ http://www.graphicsmagick.org/
+ </link>
+ and ImageMagick from
<link xlink:href="http://www.imagemagick.org/">
http://www.imagemagick.org/
</link>
-----------------------------------------------------------------------
Summary of changes:
NEWS | 2 +
configure.ac | 17 +-
doc/README | 4 +-
doc/html/images/generator.c | 933 +++++++++++++++++++++++++++-----------------
doc/installation.xml | 11 +-
5 files changed, 609 insertions(+), 358 deletions(-)
hooks/post-receive
--
PostGIS
More information about the postgis-tickets
mailing list