kartsims/easysvg
EasySVG is a lightweight PHP library for creating SVG files programmatically. It offers simple methods for adding text, shapes, lines, and paths, making it useful for generating dynamic badges, icons, charts, and other vector graphics in PHP applications.
The entire package is one class in src/EasySVG.php. Read that file before anything else — there is no service provider, no config, no facade, no interfaces. Once you've skimmed the ~300 lines you know the whole API surface.
composer require kartsims/easysvg
Minimal first use — render a text badge directly to the browser:
use EasySVG\EasySVG;
$svg = new EasySVG();
$svg->setWidth(320);
$svg->setHeight(100);
$svg->setViewBox(0, 0, 320, 100);
$svg->setFont(base_path('resources/fonts/Inter-Bold.ttf'));
$svg->setFontSize(40);
$svg->setFontColor('#0f172a');
$svg->addText('Hello SVG', ['text-anchor' => 'middle']);
return response($svg->svg(), 200, ['Content-Type' => 'image/svg+xml']);
First real use case: server-side generated badges, labels, and simple charts where you already have the layout data and just need to emit an <svg> string. It is not an SVG manipulation library — you build from scratch, every time.
Key methods to learn on day one: setWidth/setHeight/setViewBox, setFont/setFontSize/setFontColor, addText, addRect, addCircle, addLine, addPath, and svg().
Return SVG from a controller or route. svg() returns a string, not a response object, so wrap it:
Route::get('/badge/{slug}.svg', function ($slug) {
$svg = new EasySVG();
$svg->setWidth(200); $svg->setHeight(56);
$svg->setFont(resource_path('fonts/Inter-Regular.ttf'));
$svg->setFontSize(20);
$svg->addRect(0, 0, 200, 56, ['fill' => '#111827', 'rx' => 8]);
$svg->addText($slug, ['x' => 12, 'y' => 34, 'fill' => '#ffffff']);
return response($svg->svg(), 200, ['Content-Type' => 'image/svg+xml']);
});
Wrap it in a builder class. Because EasySVG is stateful (font, size, color are sticky properties), you will fight it inside controllers. Create a BadgeBuilder or SvgRenderer that takes data and returns a fresh EasySVG each time. This also gives you a place to centralize font paths and colors instead of scattering them.
Use addPath() as the escape hatch. There is no <g>, <defs>, transform, gradient, or clip-path API. When you need anything beyond primitives, compute the d attribute yourself and call addPath():
$svg->addPath('M0,0 L100,0 L50,80 Z', ['fill' => '#3b82f6']);
This is also how you do icons: store SVG path data alongside your data model and loop.
Layer with call order. Z-order is exactly the order you call add*(). Build backgrounds first, foreground last. There is no z-index.
Measure text before you lay it out. If the class exposes textLength() (it uses imagettfbbox() internally), use it to right-align values or size containers dynamically rather than hard-coding widths per label. Note it requires the same font file and size you intend to render with.
Persist SVGs as files, not live renders. For anything reused — icons, generated badges, chart exports — generate once and store via Storage::put('svg/badge-42.svg', $svg->svg()). Serve the static file through your CDN or Storage::response(). Rebuilding on every request wastes CPU on imagettfbbox() calls.
Embed in Blade or PDFs. Since the output is a string, you can inline it into Blade ({!! $svg !!}) for icon sprites, or feed it to Dompdf/Snappy as the source HTML. Confirm the SVG has the xmlns attribute set (the class writes it) or some PDF engines will drop it.
Testing. Assert against substrings (assertStringContainsString('<circle', $svg->svg())) rather than comparing whole documents byte-for-byte — floating-point attribute formatting and font metrics can shift between environments.
setFont() needs an absolute, readable path to a .ttf. imagettfbbox() also requires PHP's GD extension compiled with FreeType. On Alpine or minimal Docker images this is frequently missing — add freetype and freetype-dev to your image build.setFont() doesn't take a size. Set the font file and size separately (setFont() then setFontSize()), and set both before addText(). Changing them afterward affects only subsequent text elements, not retroactively.y is the baseline, not the top. If your text is clipped at the top of the canvas, you gave it the top-left coordinate, not the baseline. Add roughly 0.8 × fontSize to y.text-anchor: middle centers on x, not on the canvas. You still need to pass x (typically width / 2). Without x, the anchor behaves unexpectedly.setViewBox if you want the SVG to scale responsively — the class does not derive it for you.fill, x, or path d, sanitize it yourself. Never pass raw request input into addPath().<defs>, <use>, <g>, gradients, or filters. If you need a gradient or a reusable symbol, hand-craft the fragment and concatenate it into the string returned by svg(), or subclass EasySVG and add a method that appends raw markup.final and the markup buffer appears to be a protected property — write class BrandedSVG extends EasySVG { public function addGradient(...) { ... } } rather than string-patching the output downstream.composer.json, and don't expect upstream fixes. For anything mission-critical, consider mevdschee/php-svg or building the markup yourself — the API is thin enough that replacement is measured in hours, not days.$svg->svg() to a .svg file and open it in a browser instead of eyeballing the PHP. Inline rendering in a data URI hides malformed attributes that a real file reveals.How can I help you explore Laravel packages today?