Skip to content

Graphics

The graphics namespace covers everything between “set fill colour” and “fill the path” — including the colour spaces, patterns, shadings, and functions that drive rendering.

ShadingType2 (axial) and ShadingType3 (radial) are the workhorses. Both are driven by a Func (usually a FunctionType2) that interpolates between two colour stops.

use Phpdftk\Pdf\Core\Font\StandardFont;
use Phpdftk\Pdf\Core\Font\Type1Font;
use Phpdftk\Pdf\Core\Graphics\ColorSpace\DeviceRGB;
use Phpdftk\Pdf\Core\Graphics\Function\FunctionType2;
use Phpdftk\Pdf\Core\Graphics\Pattern\ShadingPattern;
use Phpdftk\Pdf\Core\Graphics\Shading\ShadingType2;
use Phpdftk\Pdf\Core\Graphics\Shading\ShadingType3;
use Phpdftk\Pdf\Core\PdfArray;
use Phpdftk\Pdf\Core\PdfNumber;
use Phpdftk\Pdf\Writer\PdfWriter;
$writer = new PdfWriter();
$page = $writer->addPage();
$body = $writer->addFont(new Type1Font(StandardFont::Helvetica));
$bold = $writer->addFont(new Type1Font(StandardFont::HelveticaBold));
// A Type 2 function defines the color ramp used by every shading on this page:
// magenta at t=0, cyan at t=1, linear interpolation in between.
$ramp = new FunctionType2(
domain: new PdfArray([new PdfNumber(0), new PdfNumber(1)]),
c0: new PdfArray([new PdfNumber(0.9), new PdfNumber(0.2), new PdfNumber(0.5)]),
c1: new PdfArray([new PdfNumber(0.2), new PdfNumber(0.6), new PdfNumber(0.9)]),
n: 1.0,
);
$rampRef = $writer->register($ramp);
// Axial (linear) shading from x=72 to x=540.
$axial = new ShadingType2(
new DeviceRGB(),
new PdfArray([
new PdfNumber(72), new PdfNumber(0),
new PdfNumber(540), new PdfNumber(0),
]),
$rampRef,
);
$axialRef = $writer->register($axial);
$axialPattern = $writer->register(new ShadingPattern($axialRef));
// Radial shading centered at (306, 380), starting at radius 0 and ending at radius 180.
$radial = new ShadingType3(
new DeviceRGB(),
new PdfArray([
new PdfNumber(306), new PdfNumber(380), new PdfNumber(0),
new PdfNumber(306), new PdfNumber(380), new PdfNumber(180),
]),
$rampRef,
);
$radialRef = $writer->register($radial);
$radialPattern = $writer->register(new ShadingPattern($radialRef));
// Expose both patterns under page resources.
$page->corePage()->resources->pattern = [
'P1' => $axialPattern,
'P2' => $radialPattern,
];
$cs = $writer->addContentStream($page);
$cs->beginText()->setFont($bold, 22)->moveTextPosition(72, 740)
->showText('Shadings')->endText();
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 690)
->showText('Axial shading — Type 2 function ramped along a linear axis:')
->endText();
$cs->raw('/Pattern cs');
$cs->raw('/P1 scn');
$cs->rectangle(72, 620, 468, 50)->fill();
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 580)
->showText('Radial shading — same ramp from center outward:')
->endText();
$cs->raw('/Pattern cs');
$cs->raw('/P2 scn');
$cs->rectangle(126, 200, 360, 360)->fill();
$writer->save('shadings.pdf');

TilingPattern defines a repeating motif by embedding a small content stream that’s painted across the fill area. Cell size is set by xStep and yStep.

use Phpdftk\Pdf\Core\Content\Resources;
use Phpdftk\Pdf\Core\Font\StandardFont;
use Phpdftk\Pdf\Core\Font\Type1Font;
use Phpdftk\Pdf\Core\Graphics\Pattern\TilingPattern;
use Phpdftk\Pdf\Core\PdfArray;
use Phpdftk\Pdf\Core\PdfNumber;
use Phpdftk\Pdf\Writer\PdfWriter;
$writer = new PdfWriter();
$page = $writer->addPage();
$body = $writer->addFont(new Type1Font(StandardFont::Helvetica));
$bold = $writer->addFont(new Type1Font(StandardFont::HelveticaBold));
$bbox = static fn () => new PdfArray([
new PdfNumber(0), new PdfNumber(0), new PdfNumber(20), new PdfNumber(20),
]);
// Checkerboard tile: two filled squares in a 20×20 cell.
$checker = new TilingPattern(
paintType: 1,
tilingType: 1,
bbox: $bbox(),
xStep: 20,
yStep: 20,
resources: new Resources(),
contentStream:
"0.92 0.96 1 rg\n0 0 20 20 re\nf\n" .
"0.20 0.36 0.85 rg\n0 0 10 10 re\nf\n" .
"0.20 0.36 0.85 rg\n10 10 10 10 re\nf\n",
);
// Dot grid tile: a single small filled circle (approximated by curves).
$dots = new TilingPattern(
paintType: 1,
tilingType: 1,
bbox: $bbox(),
xStep: 20,
yStep: 20,
resources: new Resources(),
contentStream:
"1 1 1 rg\n0 0 20 20 re\nf\n" .
"0.85 0.20 0.36 rg\n" .
"10 6 m " .
"12.21 6 14 7.79 14 10 c " .
"14 12.21 12.21 14 10 14 c " .
"7.79 14 6 12.21 6 10 c " .
"6 7.79 7.79 6 10 6 c f\n",
);
$checkerRef = $writer->register($checker);
$dotsRef = $writer->register($dots);
$page->corePage()->resources->pattern = [
'P1' => $checkerRef,
'P2' => $dotsRef,
];
$cs = $writer->addContentStream($page);
$cs->beginText()->setFont($bold, 22)->moveTextPosition(72, 740)
->showText('Tiling Patterns')->endText();
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 700)
->showText('Checkerboard pattern (paintType 1, 20×20 cell):')
->endText();
$cs->raw('/Pattern cs');
$cs->raw('/P1 scn');
$cs->rectangle(72, 480, 468, 200)->fill();
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 440)
->showText('Dot-grid pattern (curves approximating circles):')
->endText();
$cs->raw('/Pattern cs');
$cs->raw('/P2 scn');
$cs->rectangle(72, 220, 468, 200)->fill();
$writer->save('patterns.pdf');

ExtGState configures the graphics state — including fill/stroke alpha and blend modes. The Multiply blend mode below mixes three primaries the way subtractive inks would.

use Phpdftk\Pdf\Core\Font\StandardFont;
use Phpdftk\Pdf\Core\Font\Type1Font;
use Phpdftk\Pdf\Core\Graphics\ExtGState;
use Phpdftk\Pdf\Writer\PdfWriter;
$writer = new PdfWriter();
$page = $writer->addPage();
$body = $writer->addFont(new Type1Font(StandardFont::Helvetica))->getResourceName();
$bold = $writer->addFont(new Type1Font(StandardFont::HelveticaBold))->getResourceName();
// Define a graphics state for each alpha level we want to demonstrate.
$alphaStates = ['GS25' => 0.25, 'GS50' => 0.50, 'GS75' => 0.75, 'GS100' => 1.0];
foreach ($alphaStates as $name => $alpha) {
$gs = new ExtGState();
$gs->caLower = $alpha; // /ca — fill alpha
$gs->ca = $alpha; // /CA — stroke alpha
$ref = $writer->register($gs);
$page->corePage()->resources->addExtGState($name, $ref);
}
// Define a Multiply blend mode so overlapping circles mix.
$blend = new ExtGState();
$blend->bm = new \Phpdftk\Pdf\Core\PdfName('Multiply');
$page->corePage()->resources->addExtGState('GSMul', $writer->register($blend));
$cs = $writer->addContentStream($page);
$cs->beginText()->setFont($bold, 22)->moveTextPosition(72, 740)
->showText('Transparency & Blend Modes')->endText();
// Row 1 — four squares at increasing alpha over a striped background.
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 700)
->showText('Fill alpha 0.25 / 0.50 / 0.75 / 1.00 over a striped backdrop:')
->endText();
// Vertical stripes background
for ($x = 72; $x < 540; $x += 24) {
$cs->setFillColorRGB(0.85, 0.85, 0.92);
$cs->rectangle($x, 580, 12, 100)->fill();
}
$x = 90;
foreach ($alphaStates as $name => $_alpha) {
$cs->saveGraphicsState();
$cs->setGraphicsState($name);
$cs->setFillColorRGB(0.85, 0.15, 0.35);
$cs->rectangle($x, 590, 90, 80)->fill();
$cs->restoreGraphicsState();
$x += 115;
}
// Row 2 — three overlapping discs using a Multiply blend mode.
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 550)
->showText('Multiply blend mode mixing three CMYK-like primaries:')
->endText();
$disc = static function ($cs, float $cx, float $cy, float $r): void {
// Approximate a circle with cubic Béziers (Kappa ≈ 0.5523).
$k = 0.5523 * $r;
$cs->moveTo($cx - $r, $cy);
$cs->curveTo($cx - $r, $cy + $k, $cx - $k, $cy + $r, $cx, $cy + $r);
$cs->curveTo($cx + $k, $cy + $r, $cx + $r, $cy + $k, $cx + $r, $cy);
$cs->curveTo($cx + $r, $cy - $k, $cx + $k, $cy - $r, $cx, $cy - $r);
$cs->curveTo($cx - $k, $cy - $r, $cx - $r, $cy - $k, $cx - $r, $cy);
$cs->closePath();
$cs->fill();
};
$cs->saveGraphicsState();
$cs->setGraphicsState('GSMul');
$cs->setFillColorRGB(0.95, 0.20, 0.20);
$disc($cs, 230, 380, 80);
$cs->setFillColorRGB(0.20, 0.75, 0.30);
$disc($cs, 300, 380, 80);
$cs->setFillColorRGB(0.20, 0.30, 0.95);
$disc($cs, 265, 320, 80);
$cs->restoreGraphicsState();
$writer->save('transparency.pdf');

DeviceRGB, DeviceCMYK, and DeviceGray are the three device-dependent spaces. The content stream API exposes setFillColorRGB, setFillColorCMYK, and setFillColorGray directly.

use Phpdftk\Pdf\Core\Font\StandardFont;
use Phpdftk\Pdf\Core\Font\Type1Font;
use Phpdftk\Pdf\Writer\PdfWriter;
$writer = new PdfWriter();
$page = $writer->addPage();
$body = $writer->addFont(new Type1Font(StandardFont::Helvetica));
$bold = $writer->addFont(new Type1Font(StandardFont::HelveticaBold));
$cs = $writer->addContentStream($page);
$cs->beginText()->setFont($bold, 22)->moveTextPosition(72, 740)
->showText('Device Color Spaces')->endText();
// ---- DeviceRGB ----------------------------------------------------------
$cs->beginText()->setFont($bold, 13)->moveTextPosition(72, 700)
->showText('DeviceRGB (additive — used for screens):')->endText();
$rgb = [
[1.0, 0.0, 0.0, 'Red'],
[0.0, 1.0, 0.0, 'Green'],
[0.0, 0.0, 1.0, 'Blue'],
[1.0, 1.0, 0.0, 'Yellow'],
[1.0, 0.0, 1.0, 'Magenta'],
[0.0, 1.0, 1.0, 'Cyan'],
];
$x = 72;
foreach ($rgb as [$r, $g, $b, $label]) {
$cs->setFillColorRGB($r, $g, $b);
$cs->rectangle($x, 620, 70, 50)->fill();
$cs->setFillColorRGB(0, 0, 0);
$cs->beginText()->setFont($body, 9)->moveTextPosition($x, 605)
->showText($label)->endText();
$x += 78;
}
// ---- DeviceCMYK ---------------------------------------------------------
$cs->beginText()->setFont($bold, 13)->moveTextPosition(72, 560)
->showText('DeviceCMYK (subtractive — used for print):')->endText();
$cmyk = [
[0.0, 0.0, 0.0, 0.0, 'White (0,0,0,0)'],
[1.0, 0.0, 0.0, 0.0, 'Cyan'],
[0.0, 1.0, 0.0, 0.0, 'Magenta'],
[0.0, 0.0, 1.0, 0.0, 'Yellow'],
[0.0, 0.0, 0.0, 1.0, 'Black'],
[0.6, 0.4, 0.0, 0.0, 'Mid blue'],
];
$x = 72;
foreach ($cmyk as [$c, $m, $y, $k, $label]) {
$cs->setFillColorCMYK($c, $m, $y, $k);
$cs->rectangle($x, 480, 70, 50)->fill();
$cs->setFillColorRGB(0, 0, 0);
$cs->beginText()->setFont($body, 9)->moveTextPosition($x, 465)
->showText($label)->endText();
$x += 78;
}
// ---- DeviceGray --------------------------------------------------------
$cs->beginText()->setFont($bold, 13)->moveTextPosition(72, 420)
->showText('DeviceGray ramp:')->endText();
for ($i = 0; $i <= 10; $i++) {
$gray = $i / 10;
$cs->setFillColorGray($gray);
$cs->rectangle(72 + $i * 42, 340, 40, 60)->fill();
}
$writer->save('color-spaces.pdf');

A PDF function maps an input domain to an output range. FunctionType2 (exponential interpolation) and FunctionType3 (stitching) are the most common ways to define colour ramps.

use Phpdftk\Pdf\Core\Font\StandardFont;
use Phpdftk\Pdf\Core\Font\Type1Font;
use Phpdftk\Pdf\Core\Graphics\ColorSpace\DeviceRGB;
use Phpdftk\Pdf\Core\Graphics\Function\FunctionType2;
use Phpdftk\Pdf\Core\Graphics\Function\FunctionType3;
use Phpdftk\Pdf\Core\Graphics\Pattern\ShadingPattern;
use Phpdftk\Pdf\Core\Graphics\Shading\ShadingType2;
use Phpdftk\Pdf\Core\PdfArray;
use Phpdftk\Pdf\Core\PdfNumber;
use Phpdftk\Pdf\Writer\PdfWriter;
$writer = new PdfWriter();
$page = $writer->addPage();
$body = $writer->addFont(new Type1Font(StandardFont::Helvetica))->getResourceName();
$bold = $writer->addFont(new Type1Font(StandardFont::HelveticaBold))->getResourceName();
// Helper: build an exponential function ramp between two RGB triplets.
$ramp = static function (array $c0, array $c1, float $n = 1.0) {
return new FunctionType2(
domain: new PdfArray([new PdfNumber(0), new PdfNumber(1)]),
c0: new PdfArray(array_map(fn ($v) => new PdfNumber($v), $c0)),
c1: new PdfArray(array_map(fn ($v) => new PdfNumber($v), $c1)),
n: $n,
);
};
// 1) Linear Type 2 function: blue → orange.
$linear = $writer->register($ramp([0.10, 0.30, 0.85], [0.95, 0.55, 0.10], 1.0));
// 2) Non-linear Type 2 function with N=4 (a steep falloff).
$steep = $writer->register($ramp([0.05, 0.05, 0.05], [1.00, 0.85, 0.15], 4.0));
// 3) Stitching (Type 3) function: red → white → green, joined at t=0.5.
$redWhite = $ramp([0.85, 0.20, 0.20], [1.0, 1.0, 1.0]);
$whiteGreen = $ramp([1.0, 1.0, 1.0], [0.10, 0.55, 0.20]);
$redWhiteRef = $writer->register($redWhite);
$whiteGreenRef = $writer->register($whiteGreen);
$stitching = new FunctionType3(
domain: new PdfArray([new PdfNumber(0), new PdfNumber(1)]),
functions: new PdfArray([$redWhiteRef, $whiteGreenRef]),
bounds: new PdfArray([new PdfNumber(0.5)]),
encode: new PdfArray([
new PdfNumber(0), new PdfNumber(1),
new PdfNumber(0), new PdfNumber(1),
]),
);
$stitchingRef = $writer->register($stitching);
// Wire each function to an axial shading and a pattern for paint.
$axisCoords = new PdfArray([
new PdfNumber(72), new PdfNumber(0),
new PdfNumber(540), new PdfNumber(0),
]);
$bands = [
['P1', $linear, 640, 'Type 2 — linear (N=1):'],
['P2', $steep, 540, 'Type 2 — exponential (N=4):'],
['P3', $stitchingRef, 440, 'Type 3 — stitching of two ramps:'],
];
foreach ($bands as [$name, $funcRef, $y, $label]) {
$shading = new ShadingType2(new DeviceRGB(), $axisCoords, $funcRef);
$shadingRef = $writer->register($shading);
$page->corePage()->resources->pattern[$name] = $writer->register(new ShadingPattern($shadingRef));
}
$cs = $writer->addContentStream($page);
$cs->beginText()->setFont($bold, 22)->moveTextPosition(72, 740)
->showText('Functions')->endText();
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 706)
->showText('Functions drive the color computation behind every shading.')
->endText();
foreach ($bands as [$name, , $y, $label]) {
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, $y + 50)
->showText($label)->endText();
$cs->raw('/Pattern cs');
$cs->raw('/' . $name . ' scn');
$cs->rectangle(72, $y, 468, 40)->fill();
}
$writer->save('functions.pdf');