tecnickcom/tc-lib-pdf-graph
Low-level geometric drawing and transformation primitives for PDF content streams. Compose paths, lines, curves, clipping, stroke/fill styles, gradients and shading, plus matrix transforms and coordinate helpers, with PDF/A-aware behavior.
What you're actually adopting. This is not a PDF writer. It is the geometry layer of the TCPDF ecosystem: a class (\Com\Tecnick\Pdf\Graph\Draw) that emits raw PDF graphics operators (m, l, c, re, W n, cm, sh, f, S, …) as strings. Everything day-to-day about this package follows from that one fact — you feed it numbers, you get content-stream fragments back, and something else (usually tecnickcom/tc-lib-pdf) has to place those fragments on a page.
Install and verify the environment.
composer require tecnickcom/tc-lib-pdf-graph
php -r 'var_dump(extension_loaded("zlib"), PHP_VERSION_ID >= 80200);'
PHP 8.2+ and zlib are hard requirements. If you're on PHP 8.1 or earlier, stop here — the release line has moved past you.
Minimal bootstrap. The constructor takes the working unit scale, the page dimensions, a color factory, an encrypt service, and a PDF/A flag. All of the "PDF-ness" lives in those collaborators, not in Draw itself:
require_once __DIR__ . '/vendor/autoload.php';
$draw = new \Com\Tecnick\Pdf\Graph\Draw(
1.0, // scale factor (unit -> point conversion)
210, 297, // page width/height, used for origin flipping
new \Com\Tecnick\Color\Pdf(), // color/colour-space resolution
new \Com\Tecnick\Pdf\Encrypt\Encrypt(),// encryption context (may be inert)
false // PDF/A mode
);
echo $draw->getClippingRect(10, 10, 50, 20);
If that emits a q … re W n block, you understand the package.
Where to look first, in order.
src/Draw.php — it is the entire public surface. Read the method list top to bottom once; it is the de facto API reference.tecnickcom/tc-lib-pdf, for how Draw is instantiated and where its output is concatenated into page content. This is where the real usage patterns live.First real use case. Choose one of these, not "generate a report":
If your need is "put text and images on a page," use tecnickcom/tc-lib-pdf or TCPDF directly and never touch this package.
Treat Draw as an operator generator, not a renderer. The idiomatic flow is: build a buffer of strings, wrap it in the correct graphics state, and hand it to a page/content object.
$content = '';
$content .= $draw->getClippingRect(10, 10, 50, 20); // q ... re W n
$content .= $draw->getLine(10, 30, 60, 30); // path operators
$content .= $draw->getStyle(...); // stroke/fill selection
$content .= "Q\n"; // pop state
Always balance the q/Q pair yourself if you injected one — clipping and transformations set inside a q block will leak into the rest of the stream otherwise. This is the single most common source of "why is my whole page clipped" bugs.
Wrap it before you spread it. Draw's API is broad and operator-flavored. Do not let getStyle() string arguments leak into controllers or Blade templates. Introduce a thin domain layer:
final class PdfCanvas
{
public function __construct(private \Com\Tecnick\Pdf\Graph\Draw $draw) {}
public function rect(float $x, float $y, float $w, float $h, Stroke $s): string
{
return $this->draw->getRect($x, $y, $w, $h)
. $this->draw->getStyle($s->toPdfArgs());
}
}
This pays off immediately when the package bumps minor versions (see Gotchas) or when you want to swap in a different geometry backend for tests.
Coordinate and unit discipline. Draw receives a scale and the page height. Input coordinates are interpreted in your working unit with a top-left origin, and are converted to the PDF's bottom-left origin using the page height. Concretely:
scale to match — e.g. 72/25.4 for millimetres.Draw boundary.Compose primitives rather than reaching for one method per shape. Most real drawings are paths: move, line/curve repeatedly, then close and paint. Build a small internal builder that accumulates path operators and applies a single style at the end; it minimizes redundant q/Q and keeps output compact (content streams are not free — zlib compresses them, but large charts still bloat).
State management for multi-element drawings. For anything with more than a handful of elements, emit q at the start of each element, set colour/line-width/transform for that element, paint, and Q. This is verbose but robust and matches how Draw's transform helpers behave. Only hand-optimize state changes once you've profiled.
Transformations. Matrix helpers are the right tool for repeated elements (tick marks, legend symbols, repeated glyph paths): define the geometry once at the origin, then re-emit under different cm matrices. Do the multiply yourself or use the provided helpers, and remember PDF matrices are applied to the coordinate system, not the object — composition order matters.
Gradients and shading need the surrounding document. Draw can emit shading operators, but the actual gradient definition lives in a PDF resource dictionary, which is the document layer's job. In practice, use gradients through tc-lib-pdf rather than hand-assembling them here; otherwise you will produce operators referencing a resource that does not exist and get a silently blank area.
Integration with tc-lib-pdf. The realistic production pattern is: let tc-lib-pdf own the page, and use Draw only when you need a primitive whose output you want to control precisely. Grab the Draw instance from the page/document object rather than constructing your own — a second instance with different scale/page-size settings will produce misaligned output that is very hard to diagnose.
Testing pattern. Snapshot the operator strings.
it('emits a balanced clipping rect', function () {
$out = $this->draw->getClippingRect(10, 10, 50, 20);
expect($out)->toContain(' re ')->toContain('W n');
});
Then, for integration, render to a real PDF and diff rasterized pages (pdftoppm or mutool draw) against a golden image. Operator-level assertions catch API regressions; raster diffs catch geometry regressions.
The constructor is not optional. You must supply a Com\Tecnick\Color\Pdf instance and something that satisfies the encrypt dependency. The encrypt object is frequently unused in unencrypted documents but still required by the signature — instantiate it and move on. Do not try to "simplify" by passing null; check Draw.php for the exact nullability of each parameter in your pinned version.
Scale and page height are a silent trap. getClippingRect(10, 10, 50, 20) looks unambiguous, but the numbers mean nothing without the constructor's scale and height. Two developers with different constructor arguments will produce different PDFs from identical code. Put the constructor parameters in config, not in scattered call sites.
This is a leaf of a monorepo, with a matching release cadence. Versions ship frequently and track tc-lib-pdf. Pin an exact minor ("tecnickcom/tc-lib-pdf-graph": "2.18.*") and upgrade deliberately. Because the public surface is broad and largely undocumented, minor bumps can change method signatures without a prose changelog entry — the release notes are compare links, nothing more.
Read the licence, not the badge. The repository metadata reports NOASSERTION, but the README and the upstream project state GNU LGPL v3. That has practical consequences: dynamic linking from proprietary code is generally fine, but you must ship the licence notice and provide the library (or a written offer) — and any modifications to the library itself stay LGPL. Get this reviewed if the PDF feature is part of a closed-source product you distribute.
Star count is misleading. 5 stars and 0 listed dependents understate reality: this package is pulled in transitively by the TCPDF stack (100M+ installs claimed by the ecosystem). Do not read the low metrics as abandonment — but do read them as "there is no community to ask." Support comes from the maintainer's sponsorship channel and the source itself.
No prose documentation exists. The API docs are generated from source. Budget time for reading Draw.php. If you find yourself guessing what a method returns, write a two-line script that echoes it and paste the result into a PDF by hand — faster than searching.
Debugging recipe. When output renders wrong:
qpdf --qdf --object-streams=disable in.pdf out.pdf and inspect the page content.mutool draw -o page-%d.png in.pdf to rasterize.q/Q, a missing W n, or a stale cm will be obvious in the QDF output.Transparency and PDF/A are conditional. The PDF/A flag toggles behaviour and, depending on the primitive, alpha and soft masks may be dropped or handled differently. If your design depends on transparency, test both true and false for the flag early — discovering the divergence after you've built a visual layer is expensive.
Performance is content-stream size, not CPU. The library is fast; the output can be large. Ten thousand individually-styled elements will produce a fat uncompressed stream. Batch path construction, reuse cm transforms for repeated elements, and let the document layer compress.
Extension points are limited by design. The class is a concrete primitive emitter; there are no interfaces to implement or events to hook. Extend by composition (your own builder around Draw), not by subclassing — that keeps you insulated from the churn described above and lets you write pure-function unit tests for your geometry logic. If you genuinely need new primitives, fork or contribute upstream: the make deps && make qa workflow plus make rpm/make deb packaging targets are documented and functional.
For system-packaged installs, the bootstrap differs (require_once '/usr/share/php/Com/Tecnick/Pdf/Graph/autoload.php'). Don't mix Composer and distro packages in the same process — you will get two Draw classes and a confusing instanceof failure.
How can I help you explore Laravel packages today?