Skip to content

Releases: iliaal/fastchart

fastchart 1.1.0

16 May 13:16
1.1.0
051d418

Choose a tag to compare

Added

  • Funnel::setStyle(STYLE_CONE) — pyramid layout with front-facing ellipse-arc edges at each band's top and bottom, suggesting a 3D cone seen from the side. Layout is identical to STYLE_PYRAMID (apex at top, base at bottom, band heights proportional to value); only the silhouette changes. See docs/examples/52_funnel_cone.php.

  • AreaChart::setBandMode(bool) — with exactly two series the chart fills the envelope between them instead of filling each series down to the baseline. series[0] is the upper bound, series[1] is the lower. Useful for confidence intervals, forecast ranges, min/max envelopes. Silently falls back to the per-series fill when n_series != 2 or setStacked(true) is also active. See docs/examples/53_area_band.php.

  • PolarChart::setInterpolation(int $mode)INTERP_LINEAR (default) connects points with straight segments; INTERP_SMOOTH runs Catmull-Rom subdivision through each segment for a curved fit. Markers anchor to the original data points. Ignored in STYLE_ROSE.

  • PolarChart::addVectors(array $vectors) — overlay arrow vectors anchored in the same (angle, radius) data space as the series. Each entry is ['angle' => float, 'radius' => float, 'angle_to' => float, 'radius_to' => float, 'color' => int?]. Useful for wind / flow / phase plots. Atomic commit: a malformed entry mid-list aborts the entire call without partial state. See docs/examples/54_polar_smooth_vectors.php.

  • BubbleChart::setYAxisScale(SCALE_LOG) — log-10 Y axis for exponential-shaped data. Matches the existing Line / Bar / Area / Scatter / Stock log-axis API. Y values must be strictly positive. See docs/examples/55_bubble_log_axis.php.

  • Chart::setImageMap(array $entries) + Chart::getImageMap(string $name = 'fastchart') — per-data-point HTML image-map hot-spots on the Chart base class. Entries are index-aligned with setSeries() / setSlices() / setPoints(). The renderer captures hot-spot geometry during draw; getImageMap() emits the matching <map> markup. Currently wired up for BarChart (rect per category) and PieChart (poly per slice); ScatterChart's existing per-point setPoints 'href' / 'tooltip' keys are preserved. AreaChart, BubbleChart, LineChart, and StockChart use the same infrastructure but their renderers don't push hot-spots yet (planned for v1.2). URL scheme is allowlisted (http / https / mailto / relative / or # only). Tooltip text is HTML-escaped. Embedded NUL bytes in href / tooltip are rejected. See docs/examples/56_image_map_bar_pie.php.

Fixed

  • stb_image PNG palette OOB-read that leaked uninitialized stack bytes into the rendered raster on paletted PNGs whose pixel data references indices >= pal_len. Vendored vendor/plutovg/source/plutovg-stb-image.h patched to zero-init the 1024-byte stack palette[] at function entry. Local patch marked with a fastchart-local comment so the next vendor refresh re-applies.

  • PNG IDAT chunk-length amplifier that allowed a 30-byte malicious PNG to drive stb_image into a ~1 GB realloc before reading any payload, OOM-killing the worker. Added fc_validate_png_chunks in fastchart_target.c that walks the chunk list before handoff and rejects declared lengths that would overrun the buffer.

  • Chart::setImageMap() use-after-free when the caller did setImageMap → render → setImageMap → getImageMap without an intervening render. The cached image_map_areas array held borrowed href / tooltip pointers into image_map_entries[]; the second setImageMap freed those entries while areas still pointed at them. setImageMap now resets areas before freeing entries.

  • PolarChart::addVectors() partial commit on mid-loop throw. A malformed entry in the middle of the input array would advance n_vectors for the prefix that already wrote, leaving the object in a half-populated state that a retry would duplicate on top of. The setter now stages entries into a temporary buffer and only commits to self->vectors after every entry validates.

  • Userland-subclass instantiation no longer corrupts heap. Both FastChart\Chart and FastChart\Symbol (and FastChart\Barcode) abstract bases now install a sentinel create_object handler. class MyChart extends FastChart\Chart {} (with or without a userland __construct) was previously triggering either a debug assertion or a Z_FASTCHART_OBJ_P heap scribble; it now throws a clean Error with a clear message at instantiation time.

Changed

  • Vendored stb_image attack surface narrowed. STBI_ONLY_PNG + STBI_ONLY_JPEG defined before STB_IMAGE_IMPLEMENTATION in vendor/plutovg/source/plutovg-surface.c, eliminating the BMP / GIF / PSD / TGA / HDR / PIC / PNM decoders from the linked binary. fastchart's MIME sniff already gated these formats at the caller; this closes the defense-in-depth gap.

  • FASTCHART_IMAGE_MAP_MAX_COORDS raised from 16 to 32. PieChart poly hot-spots use 14 ints (center + 6 arc samples); the cap bump leaves headroom for denser sampling without bumping the per-area fixed-size coord buffer at the call sites.

  • Test suite: 118 → 127 phpts. Six v1.1 feature tests (153 – 158), three review-fix regression tests (159 – 161).

fastchart 1.0.2

15 May 17:56
1.0.2
80dc523

Choose a tag to compare

A build/packaging patch: the 1.0.1 Linux prebuilt binaries linked against the build host's libjpeg.so.8 (Ubuntu's libjpeg-turbo soname) and failed to load on Debian-based PHP, including every official php:X.Y-cli Docker image where Debian's libjpeg-turbo ships as libjpeg.so.62 instead. Debian provides no package that satisfies libjpeg.so.8, so pie install iliaal/fastchart on a Debian PHP fell back to source build.

Fixed

  • Linux prebuilt binaries now portable across distros. Each .so produced by .github/workflows/release-linux.yml statically links freetype + libpng + libjpeg-turbo + libwebp from ./configure-time source builds (pinned versions in workflow env). --with-fastchart-static-codecs=<prefix> is the new config.m4 flag that drives it: prepends the prefix to PKG_CONFIG_PATH, switches every pkg-config call to --static, and appends -Wl,--exclude-libs=ALL so the codec-archive symbols stay local in fastchart.so. Without --exclude-libs, loading ext/gd (dynamically linked against the system libjpeg) in the same process collides on jpeg_CreateCompress with two different JPEG_LIB_VERSION values and produces "encoder produced no output" on the first renderJpeg().
  • readelf -d fastchart.so NEEDED entries trimmed from libjpeg.so.8, libfreetype.so.6, libpng16.so.16, libwebp.so.7, libz.so.1, libm, libc → just libz, libm, libc, ld-linux. Works on Ubuntu, Debian, RHEL, anywhere glibc + zlib are present.
  • macOS prebuilts unaffected. Mach-O resolves by install-name (absolute path baked at link time), not soname major, so the Ubuntu↔Debian issue doesn't manifest. The macOS path keeps dynamic linking against Homebrew kegs.

Changed

  • release-linux.yml gains a workflow_dispatch trigger so maintainers can validate the recipe end-to-end without cutting a release tag. The release-asset upload step is gated on github.event_name == 'release'; manual runs build + verify but do not upload.
  • release-linux.yml adds a Verify codec libs are static step that fails the job if libjpeg, libpng, libwebp, or libfreetype appears in the produced .so's NEEDED entries — catches link-flag regressions immediately.
  • Static-built codec libs cached via actions/cache@v4 keyed on the pinned lib versions (env: FT_VERSION, PNG_VERSION, JPEG_VERSION, WEBP_VERSION). First build ~4min; subsequent cache-hit builds skip the source build.

Trade-off: .so size grows from ~4MB → ~6.8MB on Linux. Acceptable in exchange for cross-distro portability.

1.0.1

15 May 16:26
1.0.1
6340e14

Choose a tag to compare

This is the first release with prebuilt binaries on GitHub Releases. No API changes — every change in this release is build / packaging / ZTS-correctness.

Added

  • Prebuilt binaries on GitHub Releases. composer.json now declares download-url-method: ["pre-packaged-binary", "composer-default"], so PIE prefers a prebuilt asset and falls back to source build when no asset matches the install target. Two new workflows attach binaries on release publish:

    • .github/workflows/windows.yml — Windows DLLs for PHP-8.3/8.4/8.5 (NTS + TS, x64 + x86) via php/php-windows-builder. System deps (freetype, libpng, libjpeg-turbo, libwebp) come through the action's libs: input and resolve via the PHP-on-Windows SDK deps server.
    • .github/workflows/release-linux.yml — Linux x86_64 (ubuntu-24.04), Linux arm64 (ubuntu-24.04-arm), and macOS arm64 (macos-14) .so binaries for PHP-8.4 and 8.5 (NTS) via php/pie-ext-binary-builder. apt-get / brew install the four deps before the build.

    PHP-8.3 on Linux/macOS, macOS Intel, and Alpine musl users continue to source-build via PIE's composer-default fallback.

  • config.w32 — Windows build manifest mirroring config.m4. All 29 wrapper sources + 12 vendor sources (qrcodegen + plutovg + plutosvg), FreeType mandatory via CHECK_LIB("freetype_a.lib;...") + CHECK_HEADER_ADD_INCLUDE("ft2build.h", ..., ..\deps\include\ freetype2), libpng / libjpeg-turbo / libwebp optional with the same HAVE_LIBxxx defines the Unix side uses.

Fixed

  • ZTS module globals not zero-initialised. Under NTS the globals struct lives in BSS so the linker zero-fills it; under ZTS it is heap-allocated per thread by TSRM and the contents are whatever the heap had. On Linux ZTS that often happened to be zero (fresh page from anonymous mmap) and the bug stayed latent; on Windows x64 ZTS the random content looked like a live FT_Library handle, so FT_Done_Face / FT_New_Face dereferenced garbage and every renderXxx() call segfaulted at first use. Explicit memset(fastchart_globals, 0, sizeof(*fastchart_globals)) in the new PHP_GINIT_FUNCTION closes both. Valgrind on Linux ZTS now reports zero errors from zero contexts (was 14 from 7).
  • TSRMLS cache uninitialised in dynamically-loaded ZTS modules. Added ZEND_TSRMLS_CACHE_DEFINE() at the COMPILE_DL_FASTCHART level and ZEND_TSRMLS_CACHE_UPDATE() in PHP_GINIT_FUNCTION. Without these, every FASTCHART_G(...) dereference from a loaded DSO under ZTS reads an undefined __declspec(thread) slot — silent on Linux ZTS (GCC __thread has weaker linkage), segfault on Windows ZTS at first access. Mirrors what ext/intl, ext/mbstring, ext/curl have always done.
  • Default font path no longer a shared interned zend_string. The interned-permanent shared string was a v1.0 round-2 fix for a refcount race under ZTS. After surfacing the deeper ZTS bug above it adds nothing, so it is gone. Each chart now zend_string_inits its own font_path from a const char * pointing into a static string-literal table. ~32 extra bytes per chart, no cross-thread refcount math.
  • Windows font candidates for the default-font probe. The probe previously listed only /usr/share, /Library, and /System/ Library paths; on Windows the probe returned NULL, every chart started with font_path == NULL, and every text-rendering call silently no-op'd. Added C:\Windows\Fonts\arial.ttf and C:\Windows\Fonts\segoeui.ttf.
  • MSVC C2057 in fastchart_stock.c. const int baseT = 10 followed by int dq[baseT + 1] is portable C99 (a VLA on GCC / Clang but with a compile-time-constant bound) — MSVC's C front-end rejects it as "expected constant expression". Converted baseT to a #define (scoped #undef at function end). All 17 other uses in arithmetic / bounds checks work unchanged with macro substitution.

Changed

  • tests/089_font_cache_open_basedir.phpt — moved the /usr/share-font probe into --SKIPIF--. The test was an early echo "skip: ..." + exit; inside --FILE--, which run-tests.php treated as a failed assertion (no --SKIPIF-- block means "treat output literally"). Windows runners now skip cleanly instead of producing a confusing FAIL.
  • 30 SVG-validating phpts declare simplexml in --EXTENSIONS--. The tests parse SVG output with simplexml_load_string() to verify well-formedness and element counts. SimpleXML is normally compiled-in but on ZTS PHP builds where libxml2 / simplexml were dropped (custom --disable-all setups, ASAN-PHP builds) the tests fatal at the simplexml_load_string call. Adding it to --EXTENSIONS-- makes run-tests.php skip with a clear "Required extension missing: simplexml" reason.

1.0.0

15 May 14:58
1.0.0
b38abe6

Choose a tag to compare

This release rebuilds the rendering pipeline around an SVG-canonical architecture. SVG is now the source of truth; every raster output (PNG / JPG / WebP) is produced by flattening text to glyph paths, rasterizing through plutovg, and encoding with libpng / libjpeg-turbo / libwebp. libgd is no longer required at runtime.

Breaking

  • draw(\GdImage $canvas) removed from every chart class. v1.0 owns its pixel buffer end-to-end. Callers that previously composited multiple charts onto a shared canvas should now stitch via drawSvgFragment() into one SVG document, or call renderPng() and composite the decoded bytes in userland.
  • renderGif() and renderAvif() removed from Chart and Symbol. Both raise \Error if called. Use renderPng(), renderJpeg(), renderWebp(), or renderSvg() instead. The renderToFile('out.gif' | 'out.avif') paths reject with the same error.
  • ext/gd is no longer a runtime requirement. Loading fastchart no longer triggers \GdImage class lookup at MINIT. ext/gd can be absent and fastchart still functions — but if you still call imagecreatefromstring() to consume renderPng() output, you obviously still want it loaded.
  • Default JPEG quality is now 88 (was 90). Matches the evaluation-validated sweet spot for the new plutovg + libjpeg-turbo encoder. Tunable via setJpegQuality() or per-call renderJpeg(int).
  • Pixel output is no longer byte-identical to 0.x. plutovg's rasterizer produces smoother anti-aliasing than libgd; byte-compare baselines against 0.x output will fail. Visual quality is improved, especially on diagonal lines and glyph edges.

Added

  • Seven new chart families lifting the family count from 19 to 26:
    • BulletChart — Stephen Few bullet: performance bar against qualitative bands with a target tick.
    • ParetoChart — descending bars + cumulative-percentage line overlay (the 80/20 visualization).
    • CalendarHeatmap — GitHub-style day-grid keyed by YYYY-MM-DD with a low/high color ramp.
    • SunburstChart — radial hierarchical donut; recursive children arrays with optional value per node (interior nodes auto-sum).
    • SankeyChart — bipartite / multi-layer flow with bezier ribbons; setNodes() + setLinks() with from / to indices.
    • MarimekkoChart — variable-width stacked columns where column width is proportional to category total.
    • VectorChart — arrow-on-grid vector field with magnitude scaling and optional ramp coloring.
  • Funnel::STYLE_PYRAMID: opt into a triangle-with-bands layout instead of the default descending-trapezoid look. Value still drives shape — band heights are value-proportional, widths follow the triangle's natural taper.
  • Chart::svgToPng() / svgToJpeg() / svgToWebp(): three static methods that hand caller-supplied SVG bytes to the same plutovg + encoder pipeline used by renderPng() / renderJpeg() / renderWebp(). Lets callers stitch chart fragments (via drawSvgFragment()) into one SVG document and round-trip the whole thing back to raster without ImageMagick / rsvg-convert / pure-PHP rasterizers. svgToJpeg($svg, int $q = 88, int $bgRgb = 0xFFFFFF) composites transparent regions under $bgRgb before JPEG encode (JPEG has no alpha); svgToWebp($svg, int $q = 90, int $mode = WEBP_DRAWING) honours the same mode as the instance-side encoder. Hard caps: 16 MB input cap; output dims capped at 4096 px / 16 Mpx; embedded data:image/ URIs and <use> elements rejected (plutosvg's data-URI loader bypasses the output-dim cap, and its <use> renderer's cycle detector doesn't count fan-out, so a nested <g><use/>×10 tree can hit billion-laughs expansion). Text in caller-supplied SVG renders blank — plutovg has no text renderer; flatten <text> to <path> upstream.
  • Chart::setWebpMode(int) with class constants WEBP_DRAWING (default; encoder preset tuned for vector-like content), WEBP_PHOTO (preset for photo input), WEBP_LOSSLESS (lossless encode), and WEBP_FAST (lower-effort encode for batch jobs). Same setter on Symbol. Default WEBP_DRAWING matches what chart content actually is and produces visibly tighter file sizes than the libwebp simple-API default this release also drops.
  • plutovg + plutosvg rasterizer, vendored under vendor/plutovg/ and vendor/plutosvg/. Builds with PLUTOVG_BUILD_STATIC + PLUTOSVG_BUILD_STATIC + -fvisibility=hidden so the library symbols stay internal to fastchart.so. The rasterizer has no text support of its own; fastchart flattens text at SVG-build time.
  • Chart::setSvgTextMode(int) with class constants SVG_TEXT_PATHS (default; every <text> becomes a <g><path d="…"/></g> via FreeType outline decomposition — self-contained SVG, renders in any rasterizer) and SVG_TEXT_NATIVE (raw <text> elements, smaller files, requires consumer text support). Symbol gains the same setter.
  • Chart::setJpegQuality(int) (1..100, default 88). Affects renderJpeg() and renderToFile('*.jpg'). Symbol mirrors.
  • Raster encoders wired directly: PNG via libpng with pHYs DPI metadata; JPEG via libjpeg-turbo with optimize_coding=TRUE, 4:2:0 subsampling, and density_unit metadata; WebP via libwebp's WebPEncodeRGBA. No libgd encoder path remains on the raster output path.
  • SVG output. Chart::renderSvg() returns the full document; Chart::drawSvgFragment() returns a <g class="fastchart">…</g> group with no outer envelope for stitching multiple charts into one caller-managed SVG. renderToFile() routes .svg through the vector path. Same surface on Symbol.
  • Internal render-target abstraction (fastchart_target_t) with two backends: GD-wrapping for the unchanged raster path and SVG-emitting via smart_str. The axis, text, and palette helpers operate on the abstraction; chart families thread the target down to the primitive layer. SVG primitives use native <text> (no path-embedded glyphs) with the font family resolved via FreeType. SVG output is DPI-invariant — vector strokes scale infinitely, so setDpi() no longer inflates the SVG viewport while the raster path retains DPI scaling.

Fixed

  • Layout reservation for 45° rotated X-axis labels accounted for only the right-end anchor's projection (width * 0.707), not the up-left extent of the rest of the rotated text. Long labels like "Jan 2025" ran off the bottom of the canvas in raster output too. Reservation now uses the full width * 1.414 projection.
  • Layout reservation for the rightmost X-axis tick label measured a fixed "999999" numeric probe, even when the caller set setCategoryLabels() with longer strings. The X-label measurement path now walks the supplied category labels and uses the widest, mirroring the existing Y-axis logic.
  • Right margin without a secondary Y axis was bare MARGIN_RIGHT_PAD next to a full Y-axis label reservation on the left, which read as visibly off-center. Right margin now anchors at left / 2 when both Y and X axes are present.
  • StockChart: first and last candles straddled the Y-axis line and right edge because the time domain mapped t_min to exactly plot.x0. The time domain is now padded by half a bar-step on each end, matching the half-cell offset categorical X axes already use.
  • BubbleChart: range computation didn't reserve headroom for the bubble radius, so the largest bubble could clip past plot.y0 / plot.x1 on data sets where the data extremum sat near a niced tick boundary. xmin/xmax/ymin/ymax now pad by 10% of the data span before nice-tick rounding.

Changed

  • Build dependencies. Drop libgd-dev. Add libpng-dev, libjpeg-turbo-dev (or libjpeg-dev), libwebp-dev, and libfreetype-dev. config.m4 probes all four via pkg-config.

Followups landed (post-Phase 6)

  • libgd link removed. Dead gdImage* branches stripped from every chart family body; fastchart_text.c swapped libgd's gdImageStringFTEx(NULL, …) measurement path for direct FT_Set_Char_Size + FT_Load_Glyph advance summation; config.m4 drops the libgd probe entirely. ldd modules/fastchart.so shows only libfreetype, libpng, libjpeg, libwebp.
  • Effects on the SVG path. fastchart_effects.c gradient_filled_* now emits <linearGradient> defs with stops at the chart's gradient_from/_to; shadow_filled_* emits an offset-duplicate shape with the shadow color before the main draw. (Hard-edged shadow rather than Gaussian-blurred — plutosvg doesn't parse <filter>.) Wired into Bar and Pie families.
  • Background image / icon SVG emission. fastchart_target_image() loads the source file, base64-encodes the bytes, and emits <image href="data:image/png;base64,…" preserveAspectRatio="none">. Source-image byte and dimension caps (8 MiB / 4096 px / 16 Mpx) enforced at render time; open_basedir re-checked. PNG and JPEG source files only — WebP/GIF/AVIF source files are silently skipped because plutosvg's data-URI loader handles only those two.
  • Pixel-tolerance test sweep. 7 tests that scanned for exact RGB matches now use fc_color_near() which accepts any AA-blended version of the target color against white (alpha 0.3..1.0, ±4 RGB per channel). Plutovg's 1px stroke centerline lands at ~50% coverage; libgd produced exact-color centerlines.
  • Test EXTENSIONS. 33 phpt files that round-trip raster output via imagecreatefromstring() + imagecolorat() for pixel inspection now declare gd in their --EXTENSIONS-- block, so they SKIP cleanly when ext/gd isn't loaded rather than fatal.

Followups landed (security, leak-clean, optional codecs)

  • Security: open_basedir TOCTOU eliminated in source-image loading. setBackgroundImage / addIconAt previously sniffed dimensions via direct fopen(), then stat()-ed, then opened the path through the PHP stream layer with STREAM_DISABLE_OPEN_BASEDIR. A basedir-inside symlink whose target was swapped to outside basedir would resolve at the bypass-flagged final open. Collapsed to one `php_stream_ope...
Read more

0.2.0

09 May 23:42
0.2.0
377c4fc

Choose a tag to compare

Added

  • Symbol family. Two-class symbology hierarchy parallel to Chart: FastChart\Code128 (1D barcode, ISO/IEC 15417) and FastChart\QrCode (2D matrix code, ISO/IEC 18004). Render-only API (renderPng(), renderJpeg(), renderWebp(), renderGif(), renderAvif(), renderToFile()) — Symbol classes do not accept a caller-supplied \GdImage. Code128 auto-switches between subsets A/B/C with an odd-tail-to-C optimisation; mod-103 checksum is appended automatically; setShowText(true) renders the human-readable payload below the bars. QrCode ships with all four ECC levels (ECC_L/M/Q/H) and versions 1..40 via the vendored nayuki QR encoder under vendor/qrcodegen/. Both classes honour setSize(), setQuietZone(), setForeground(), setBackground(), setTransparentBackground(), and setDpi() (with PNG/JPEG metadata written via gdImageSetResolution).

Fixed

  • StockChart::setOhlcv() now clears overlay (Bollinger / SAR) and indicator-pane buffers when the new candle count is shorter than the previous one. Previously the overlay's n field kept the old length and the renderer walked off the end of the new candle array — a use-after-realloc OOB read on the candle pointer.
  • Setters that accept paths (setFont, setBackgroundImage, addIconAt) now throw an explicit Error before RETURN_THROWS when php_check_open_basedir blocks the path. php_check_open_basedir only emits E_WARNING and does not set EG(exception), so RETURN_THROWS asserted under a debug PHP build.
  • QrCode::setQuietZone() rejects quiet zones above 256 modules with a ValueError instead of silently clamping.

Changed

  • composer.json license expression is now (BSD-3-Clause AND MIT). fastchart's own code is unchanged BSD-3-Clause; the composite expression declares the MIT-licensed nayuki QR encoder vendored under vendor/qrcodegen/.

0.1.1

06 May 14:33
0.1.1
6db150c

Choose a tag to compare

Fixed

  • Module load order: declare ZEND_MOD_REQUIRED("gd") on the module entry so the engine reorders MINIT regardless of php.ini / conf.d / -d extension= order. Previously, configurations that loaded fastchart before gd (notably docker-php-ext-enable's alphabetical conf.d/*.ini) failed startup with "GdImage class not found." PHP_ADD_EXTENSION_DEP in config.m4 is build-system-only and does not affect runtime ordering.

Added

  • scripts/pie-smoke.sh: PIE install + functional smoke test that runs against php:8.x-cli with libgd-dev and ext/gd provisioned in the container. Wired into .release-config as smoke_test.

0.1.0

06 May 13:47
0.1.0
bf47017

Choose a tag to compare

Added

  • Initial public release of fastchart.