[SCM] PostGIS branch stable-3.5 updated. 3.5.7-70-g9bbeb5ea1
git at osgeo.org
git at osgeo.org
Sun Jul 19 18:45:22 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.5 has been updated
via 9bbeb5ea1f3ea72436d7a58b9cca0a13cbc90414 (commit)
via 82bb78c09aadc9de8fe2a11de2a1d2ced0d9d8d6 (commit)
via cc8165a5fbcd83820980d0f7ff6fb2de49b00c1c (commit)
via 0c1962be1f0d9e1e4978336bf1a0521bea41ce6f (commit)
from 2dec9daa4d5d4676f256a7d5c1fb085dca1f1758 (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 9bbeb5ea1f3ea72436d7a58b9cca0a13cbc90414
Merge: 2dec9daa4 82bb78c09
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date: Sun Jul 19 18:45:21 2026 -0700
Merge pull request 'Backport doc image generator policy fix to stable 3.5' (!454) from Komzpa/postgis:fix/stable-3.5-doc-image-policy into stable-3.5
Reviewed-on: https://gitea.osgeo.org/postgis/postgis/pulls/454
commit 82bb78c09aadc9de8fe2a11de2a1d2ced0d9d8d6
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Mon Jul 20 05:43:41 2026 +0400
NEWS: list doc image fix under PostGIS 3.5.8
diff --git a/NEWS b/NEWS
index 06124d278..1951d6be2 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.5.8
* Bug Fixes *
+ - #6046, Prefer GraphicsMagick for documentation image generation
+ (Darafei Praliaskouski)
- #6094, [upgrade] Avoid legacy string literal warnings in downgrade checks
with standard_conforming_strings off (Darafei Praliaskouski)
- GH-897, [topology] Harden topology helper functions and
@@ -25,8 +27,6 @@ PostGIS 3.5.7
- #5357, ST_LineFromEncodedPolyline dropping close points (Paul Ramsey)
- OSSFuzz 525554772, avoid signed integer overflow (Even Rouault)
- OSSFuzz 525548201, avoid overly nested inputs (Darafei Praliaskouski)
- - #6046, Prefer GraphicsMagick for documentation image generation
- (Darafei Praliaskouski)
- Avoid Inf loop in Geohash calculation (Paul Ramsey)
- [address_standardizer] Avoid unsafe table-name handling and out-of-bounds
rule parsing (Paul Ramsey)
commit cc8165a5fbcd83820980d0f7ff6fb2de49b00c1c
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Mon Jul 20 04:56:05 2026 +0400
ci: keep stable 3.5 docs prepare available for PRs
diff --git a/.woodpecker/docs.yml b/.woodpecker/docs.yml
index d453cd0e7..671bbf81a 100644
--- a/.woodpecker/docs.yml
+++ b/.woodpecker/docs.yml
@@ -34,9 +34,6 @@ steps:
- ../configure --without-pg --without-protobuf --without-raster
- make postgis_revision.h
- xmllint --version
- when:
- - event: push
- path: *when_path_full
check-xml:
image: *test_image
@@ -878,4 +875,3 @@ steps:
path:
include:
- "doc/po/uk/*.po"
-
commit 0c1962be1f0d9e1e4978336bf1a0521bea41ce6f
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 a8b584eb5..06124d278 100644
--- a/NEWS
+++ b/NEWS
@@ -25,6 +25,8 @@ PostGIS 3.5.7
- #5357, ST_LineFromEncodedPolyline dropping close points (Paul Ramsey)
- OSSFuzz 525554772, avoid signed integer overflow (Even Rouault)
- OSSFuzz 525548201, avoid overly nested inputs (Darafei Praliaskouski)
+ - #6046, Prefer GraphicsMagick for documentation image generation
+ (Darafei Praliaskouski)
- Avoid Inf loop in Geohash calculation (Paul Ramsey)
- [address_standardizer] Avoid unsafe table-name handling and out-of-bounds
rule parsing (Paul Ramsey)
diff --git a/configure.ac b/configure.ac
index 0411756d7..ba9418f2e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -253,10 +253,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. Documentation cannot be built])
+ CAN_BUILD_PDF=no
fi
AC_PATH_PROG([DBLATEX], [dblatex], [])
@@ -2028,7 +2037,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 31200; 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 a1f08b25b..ff5040323 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:
.woodpecker/docs.yml | 4 -
NEWS | 2 +
configure.ac | 17 +-
doc/README | 4 +-
doc/html/images/generator.c | 933 +++++++++++++++++++++++++++-----------------
doc/installation.xml | 11 +-
6 files changed, 609 insertions(+), 362 deletions(-)
hooks/post-receive
--
PostGIS
More information about the postgis-tickets
mailing list