diff --git a/README.md b/README.md index 9857524..049a450 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ HTML to image rendering for PHP, powered by Rust and [Takumi](https://github.com/kane50613/takumi). Generate Open Graph images and more from HTML/CSS without headless browser overhead. + + + ## Features - **No headless browser.** Rendering runs in-process through a native Rust library, so there is no Chromium to install, launch, or keep alive. @@ -45,12 +48,18 @@ vendor/bin/htmlshot update ``` > [!NOTE] -> A natives.lock file is added to the root of the project. Commit this file to your repo to ensure that each time you'll get the same native version. +> A `natives.lock` file is added to the root of the project. Commit this file to your repo to ensure that each time you'll get the same native version. + +## Documentation +- [Getting started](docs/getting-started.md) — install and render your first image +- [Fonts & styles](docs/fonts-and-styles.md) — custom fonts, inline/` +
Hello
+``` + +### 3. External stylesheets (`stylesheets` option) + +Keep styling out of the markup entirely by passing CSS through the +`stylesheets` option. This works well when your CSS lives in its own file: the +HTML carries only semantic class names. + +```php +$css = file_get_contents(__DIR__.'/styles.css'); + +$png = HtmlShot::render('
Hello
', [ + 'width' => 1200, + 'height' => 630, + 'stylesheets' => [$css], // pass any number of sheets +]); +``` + +The array can hold several sheets; they are applied on top of the document just +like linked stylesheets in a browser. See the +[`event-card`](../examples/event-card/) example for a complete walkthrough. + +### 4. `tw` Tailwind utilities + +Takumi's engine understands a `tw` attribute carrying Tailwind utility classes, +so you can lay out a card without writing any CSS: + +```html +
+

HtmlShot.

+

html in, image out

+
+``` + +Arbitrary values (`bg-[#16130f]`, `text-[12em]`) are supported. The +[`tailwind`](../examples/tailwind/) and [`stars`](../examples/stars/) examples +use this approach. + +## Images and SVG + +Both `` and CSS `background-image: url(...)` accept local file +paths and `data:` URIs; `http(s)` URLs are passed through to the engine. Inline +`` is rasterized as an image, so vector graphics render without a separate +conversion step. The [main README's "Deep Dive"](../README.md#deep-dive) covers +the exact handling. + +## Next steps + +- [Advanced usage](advanced.md) — HiDPI output and reusing a `Context` + + `Renderer` for repeated rendering. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..916b4aa --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,107 @@ +# Getting started + +## Requirements + +- PHP 8.3 or higher +- The FFI extension enabled (`ext-ffi`) + +Check that FFI is active: + +```bash +php -m | grep FFI +``` + +If it is missing, enable it in your `php.ini` and restart PHP: + +```ini +extension=ffi +ffi.enable=true +``` + +## Install + +```bash +composer require avvertix/html-shot +``` + +The package renders through a compiled native library. Download the binary that +matches your platform and package version with the bundled console command: + +```bash +vendor/bin/htmlshot install +``` + +This fetches the correct binary from the matching GitHub release, verifies its +checksum, and stores it under the package `lib/` directory. It also writes a +`natives.lock` file to your project root. Commit it so every environment +resolves the same native version. After upgrading the package, refresh the +library: + +```bash +composer update avvertix/html-shot +vendor/bin/htmlshot update +``` + +## Your first image + +The quickest way in is the `HtmlShot` façade. Give it some HTML and a few +options; it returns the encoded image bytes, which you can write to disk or +stream to a response. + +```php +use HtmlShot\HtmlShot; + +$png = HtmlShot::render( + '
That was fast!
', + [ + 'width' => 1280, + 'height' => 630, + 'format' => 'png', + ], +); + +file_put_contents('hello.png', $png); +``` + +A few things to know up front: + +- The single wrapper `
` is sized to fill the canvas with `width:100%; + height:100%`. Layout is driven entirely by CSS, so reach for Flexbox or Grid + to position things. +- Text only renders if a font covering it is available. The snippet above relies + on a system fallback; to control typography, load your own font (see + [Fonts & styles](fonts-and-styles.md)). + +## Render options + +All options are passed in the array argument to `HtmlShot::render()`: + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| `width` | `int` | `1200` | Logical canvas width in pixels | +| `height` | `int` | `628` | Logical canvas height in pixels | +| `format` | `string` | `'png'` | `'png'`, `'webp'`, or `'jpeg'` | +| `quality` | `int` | `0` | 1–100 for the lossy formats; `0` uses the library default | +| `stylesheets` | `string[]` | `[]` | Extra CSS applied on top of the document | +| `fonts` | `Font[]` | `[]` | Fonts to load for this render | +| `devicePixelRatio` | `float` | `1.0` | Output scale factor (e.g. `2.0` for Retina) | + +## Serving the bytes + +`render()` returns raw image bytes, so you can stream them straight to a +response without touching the filesystem. For example, in plain PHP: + +```php +header('Content-Type: image/png'); +echo HtmlShot::render($html, ['width' => 1200, 'height' => 630]); +``` + +## Next steps + +- [Fonts & styles](fonts-and-styles.md) — custom typography and the different + ways to style your markup. +- [Advanced usage](advanced.md) — HiDPI output and reusing a `Context` + + `Renderer` for repeated rendering. +- [`examples/`](../examples/) — runnable Open Graph and social-card scripts. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..8a73ef4 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,52 @@ +# Examples + +Here a few examples on how to render images with `html-shot`. Each one is a small, +self-contained recipe: read the source to see the technique, run it to get the +PNG in that example's `output/` folder. + + +| | | | +| :---: | :---: | :---: | +|
`simple` |
`tailwind` |
`event` | +|
`changelog` |
`quote` |
`repository` | +|
`stars` |
`event-card` |
`line-chart` | +|
`banner` | | | + +## What each example teaches + +| Folder | What you'll learn | +| --- | --- | +| `simple/` | The **`HtmlShot::render()` façade**, inline CSS, loading a single font | +| `tailwind/` | Styling with the **`tw` Tailwind** utility attribute instead of CSS | +| `htmlshot/` | Generate HtmlShot Open Graph image | +| `stars/` | Absolute positioning and a full-bleed background image with `tw` | +| `changelog/` | Building repeated rows from a PHP array, uppercase tag labels | +| `event/` | A full-height flex column split into three regions, letter-spacing | +| `quote/` | Large decorative typography and an inline metadata row | +| `repository/` | Mixing **two font families** (Geist + Geist Mono) in one render | +| `event-card/` | Passing a **custom stylesheet** via the `stylesheets` option — markup carries only class names, all styling lives in `styles.css` | +| `line-chart/` | Generating an **inline ``** from a data array (computed points, area fill, grid) with HTML axis labels laid over the same coordinate space | +| `banner/` | Reusing a `Context` + `Renderer`, emitting multiple outputs (1× PNG and a 2× WebP via `devicePixelRatio`), an inline SVG watermark | + + +## Running them + +```bash +# from the project root +php examples/simple/simple.php +``` + +Every example writes to its own `output/` directory and prints the file it +saved. To rebuild them all at once: + +```bash +php examples/regenerate.php +``` + +**Prerequisites** + +- `composer install` +- `vendor/bin/htmlshot install` to download the native rendering library +- The bundled fonts under `tests/fonts/` (Geist, Geist Mono) — most examples + load one or both. + diff --git a/examples/assets/images/fresh-htmlshot-no-bg.png b/examples/assets/images/fresh-htmlshot-no-bg.png new file mode 100644 index 0000000..3051221 Binary files /dev/null and b/examples/assets/images/fresh-htmlshot-no-bg.png differ diff --git a/examples/assets/images/fresh-htmlshot.png b/examples/assets/images/fresh-htmlshot.png new file mode 100644 index 0000000..6ef12e2 Binary files /dev/null and b/examples/assets/images/fresh-htmlshot.png differ diff --git a/examples/banner/banner.php b/examples/banner/banner.php new file mode 100644 index 0000000..185d00f --- /dev/null +++ b/examples/banner/banner.php @@ -0,0 +1,90 @@ + 'v1', + 'width' => 1200, + 'height' => 675, + 'fonts' => [ + ['path' => __DIR__.'/../../tests/fonts/Geist/variable/Geist[wght].ttf', 'family' => 'Geist'], + ['path' => __DIR__.'/../../tests/fonts/GeistMono/variable/GeistMono[wght].ttf', 'family' => 'Geist Mono'], + ], + 'html' => static function () use ($assetsImages): string { + // Build the 18-row repeated pattern + $rows = ''; + for ($i = 0; $i < 18; $i++) { + $marginLeft = (($i * 137) % 600); + $items = ''; + for ($j = 0; $j < 15; $j++) { + $items .= <<<'HTML' +
+ Shake. + Render. + Refresh. +   +
+HTML; + } + $rows .= << + {$items} +
+HTML; + } + + return << + + +
+ {$rows} +
+ + +
+ +

v0.3.0 Released

+
+ composer require avvertix/html-shot +
+
+ + +HTML; + }, +]; + +$ctx = new Context; +foreach ($COMPONENT['fonts'] as $f) { + $ctx->loadFontFile($f['path'], family: $f['family']); +} +$renderer = new Renderer($ctx); +$html = ($COMPONENT['html'])(); + +$png = $renderer->render($html, $COMPONENT['width'], $COMPONENT['height'], 'png'); + +save_to_output($png, 'banner.png', $outputDir); + +$webp = $renderer->render($html, $COMPONENT['width'], $COMPONENT['height'], 'webp', devicePixelRatio: 2.0); + +save_to_output($webp, 'banner@2x.webp', $outputDir); diff --git a/examples/banner/output/banner.png b/examples/banner/output/banner.png new file mode 100644 index 0000000..06d5e36 Binary files /dev/null and b/examples/banner/output/banner.png differ diff --git a/examples/banner/output/banner@2x.webp b/examples/banner/output/banner@2x.webp new file mode 100644 index 0000000..f9e392a Binary files /dev/null and b/examples/banner/output/banner@2x.webp differ diff --git a/examples/changelog/changelog.php b/examples/changelog/changelog.php new file mode 100644 index 0000000..6429d8f --- /dev/null +++ b/examples/changelog/changelog.php @@ -0,0 +1,80 @@ + 'New', 'text' => 'Installation script'], + ['tag' => 'Perf', 'text' => '30% smaller package footprint'], + ['tag' => 'Fixed', 'text' => 'Out of the box Tailwind CSS'], +]; + +$ink = '#16140F'; +$muted = '#6E6A60'; +$accent = '#1F9D55'; + +$bulletRows = ''; +foreach ($bullets as $b) { + $tag = $b['tag']; + $text = $b['text']; + $bulletRows .= << +
+ {$tag} +
+ + {$text} + + + HTML; +} + +$html = << + +
+ {$version} + · + {$date} +
+ +

+ {$headline} +

+ +
+ {$bulletRows} +
+ +HTML; + +$png = HtmlShot::render($html, [ + 'width' => 1200, + 'height' => 630, + 'format' => 'png', + 'fonts' => [ + Font::fromFile("{$fontsPath}/Geist/variable/Geist[wght].ttf", 'Geist'), + ], +]); + +save_to_output($png, 'changelog.png', $outputDir); diff --git a/examples/changelog/output/changelog.png b/examples/changelog/output/changelog.png new file mode 100644 index 0000000..523dc4f Binary files /dev/null and b/examples/changelog/output/changelog.png differ diff --git a/examples/common.php b/examples/common.php new file mode 100644 index 0000000..79fc3ec --- /dev/null +++ b/examples/common.php @@ -0,0 +1,21 @@ + +
+ +
+

Bloom & Breathe: A Mindful Floral Session

+
RSVP
+
+
+
+ Event cover +
+ +HTML; + +$png = HtmlShot::render($html, [ + 'width' => 1200, + 'height' => 630, + 'format' => 'png', + // Pass any number of custom stylesheets here; they are applied on top of + // the document just like a linked CSS file would be in a browser. + 'stylesheets' => [$stylesheet], + 'fonts' => [ + Font::fromFile("{$fontsPath}/Geist/variable/Geist[wght].ttf", 'Geist'), + ], +]); + +save_to_output($png, 'event-card.png', $outputDir); diff --git a/examples/event-card/output/event-card.png b/examples/event-card/output/event-card.png new file mode 100644 index 0000000..f582cbd Binary files /dev/null and b/examples/event-card/output/event-card.png differ diff --git a/examples/event-card/styles.css b/examples/event-card/styles.css new file mode 100644 index 0000000..c4daa97 --- /dev/null +++ b/examples/event-card/styles.css @@ -0,0 +1,68 @@ +/* + * Custom stylesheet for the event card. + * + * Everything that styles the card lives here, not inline on the elements. + * The example loads this file and hands it to HtmlShot through the + * `stylesheets` option, so the HTML only carries semantic class names. + */ + +.card { + display: flex; + width: 100%; + height: 100%; + font-family: Geist, sans-serif; + color: #ffffff; + background-image: linear-gradient(115deg, #5460c8 0%, #7d79bb 52%, #a39ba2 100%); +} + +.left { + display: flex; + flex: 1; + flex-direction: column; + padding: 60px; +} + +.logo { + width: 96px; + height: 36px; +} + +.body { + display: flex; + flex-direction: column; + margin-top: auto; + gap: 32px; +} + +.title { + margin: 0; + max-width: 540px; + font-size: 46px; + font-weight: 600; + line-height: 1.18; + letter-spacing: -0.02em; +} + +.rsvp { + display: flex; + align-self: flex-start; + padding: 14px 36px; + border-radius: 9999px; + background-color: #ffffff; + color: #4b3f8f; + font-size: 22px; + font-weight: 600; +} + +.right { + display: flex; + width: 470px; + padding: 34px 34px 34px 0; +} + +.photo { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 28px; +} diff --git a/examples/event/event.php b/examples/event/event.php new file mode 100644 index 0000000..3ba728c --- /dev/null +++ b/examples/event/event.php @@ -0,0 +1,70 @@ + + + + {$track} + + +
+ + {$name} + +
+ +
+
+ {$datetime} + {$location} +
+
+ {$hostName} + · + {$hostTitle} +
+
+ +HTML; + +$png = HtmlShot::render($html, [ + 'width' => 1200, + 'height' => 630, + 'format' => 'png', + 'fonts' => [ + Font::fromFile("{$fontsPath}/Geist/variable/Geist[wght].ttf", 'Geist'), + ], +]); + +save_to_output($png, 'event.png', $outputDir); diff --git a/examples/event/output/event.png b/examples/event/output/event.png new file mode 100644 index 0000000..d0bc77a Binary files /dev/null and b/examples/event/output/event.png differ diff --git a/examples/htmlshot/htmlshot.php b/examples/htmlshot/htmlshot.php new file mode 100644 index 0000000..11fa5c7 --- /dev/null +++ b/examples/htmlshot/htmlshot.php @@ -0,0 +1,115 @@ +Generate OG image with native PHP.'; +$stars = '0'; +$forks = '0'; + +$ink = '#16140F'; +$muted = '#6E6A60'; +$mono = "'Geist Mono', monospace"; + +$rows = ''; +for ($i = 0; $i < 18; $i++) { + $marginLeft = (($i * 137) % 600); + $items = ''; + for ($j = 0; $j < 15; $j++) { + $items .= <<<'HTML' +
+ Shake. + Render. + Refresh. +   +
+HTML; + } + $rows .= << + {$items} + +HTML; +} + +$html = << + + +
+ {$rows} +
+ + +
+ +
+ + + {$owner}/ + + + {$name} + + + {$description} + + +
+ +
+
+ PHP + + +
+ Rust +
+
+ +HTML; + +$png = HtmlShot::render($html, [ + 'width' => 1280, + 'height' => 640, + 'format' => 'png', + 'fonts' => [ + Font::fromFile("{$fontsPath}/Geist/variable/Geist[wght].ttf", 'Geist'), + Font::fromFile("{$fontsPath}/GeistMono/variable/GeistMono[wght].ttf", 'Geist Mono'), + ], +]); + +save_to_output($png, 'htmlshot.png', $outputDir); diff --git a/examples/htmlshot/output/htmlshot.png b/examples/htmlshot/output/htmlshot.png new file mode 100644 index 0000000..1c632ee Binary files /dev/null and b/examples/htmlshot/output/htmlshot.png differ diff --git a/examples/line-chart/line-chart.php b/examples/line-chart/line-chart.php new file mode 100644 index 0000000..a0ca65a --- /dev/null +++ b/examples/line-chart/line-chart.php @@ -0,0 +1,120 @@ + $plotL + $plotW * $i / ($n - 1); +$y = static fn (float $v): float => $plotB - $plotH * ($v / $maxScale); + +// Point list for the line + area path. +$points = []; +for ($i = 0; $i < $n; $i++) { + $points[] = round($x($i), 1).','.round($y($data[$i]), 1); +} +$polyline = implode(' ', $points); +$areaPath = "M {$plotL},{$plotB} L ".implode(' L ', $points)." L {$plotR},{$plotB} Z"; + +// Horizontal grid lines. +$grid = ''; +foreach ($gridValues as $v) { + $gy = round($y($v), 1); + $grid .= ""; +} + +// Point markers. +$markers = ''; +for ($i = 0; $i < $n; $i++) { + $cx = round($x($i), 1); + $cy = round($y($data[$i]), 1); + $markers .= ""; +} + +$svg = << + + + + + + + {$grid} + + + {$markers} + +SVG; + +// ── Axis labels positioned over the same coordinate space ──────────────────── +$yLabels = ''; +foreach ($gridValues as $v) { + $top = round($y($v) - 13, 1); + $yLabels .= "{$v}k"; +} +$xLabels = ''; +for ($i = 0; $i < $n; $i++) { + $left = round($x($i) - 40, 1); + $xLabels .= "{$months[$i]}"; +} + +$html = << + + Monthly active renders + + 2026 · in thousands + + + {$svg} + {$yLabels} + {$xLabels} + +HTML; + +$png = HtmlShot::render($html, [ + 'width' => $W, + 'height' => $H, + 'format' => 'png', + 'fonts' => [ + Font::fromFile("{$fontsPath}/Geist/variable/Geist[wght].ttf", 'Geist'), + ], +]); + +save_to_output($png, 'line-chart.png', $outputDir); diff --git a/examples/line-chart/output/line-chart.png b/examples/line-chart/output/line-chart.png new file mode 100644 index 0000000..464b495 Binary files /dev/null and b/examples/line-chart/output/line-chart.png differ diff --git a/examples/quote/output/quote.png b/examples/quote/output/quote.png new file mode 100644 index 0000000..4eeeebd Binary files /dev/null and b/examples/quote/output/quote.png differ diff --git a/examples/quote/quote.php b/examples/quote/quote.php new file mode 100644 index 0000000..5caa51e --- /dev/null +++ b/examples/quote/quote.php @@ -0,0 +1,64 @@ + + + + “ + + +

+ {$quote} +

+ +
+ {$author} + · + {$role} + · + {$company} +
+ +HTML; + +$png = HtmlShot::render($html, [ + 'width' => 1200, + 'height' => 630, + 'format' => 'png', + 'fonts' => [ + Font::fromFile("{$fontsPath}/Geist/variable/Geist[wght].ttf", 'Geist'), + ], +]); + +save_to_output($png, 'quote.png', $outputDir); diff --git a/examples/regenerate.php b/examples/regenerate.php new file mode 100644 index 0000000..3e16a78 --- /dev/null +++ b/examples/regenerate.php @@ -0,0 +1,70 @@ +/.php) and runs it in its + * own PHP process with shell_exec, so a single failing example can't abort the + * rest of the batch. The helper (common.php) and the diagnostic (debug.php) at + * the top level are skipped. + * + * Usage: + * php examples/regenerate.php + */ +$examplesDir = __DIR__; + +// Collect runnable example scripts: one level deep, examples//*.php. +$scripts = []; +foreach (glob($examplesDir.'/*', GLOB_ONLYDIR) as $dir) { + foreach (glob($dir.'/*.php') as $script) { + $scripts[] = $script; + } +} +sort($scripts); + +if ($scripts === []) { + fwrite(STDERR, "No example scripts found under {$examplesDir}.\n"); + exit(1); +} + +$php = escapeshellarg(PHP_BINARY); +$failures = []; +$saved = 0; +$start = microtime(true); + +echo 'Regenerating '.count($scripts)." examples...\n\n"; + +foreach ($scripts as $script) { + $rel = str_replace('\\', '/', substr($script, strlen($examplesDir) + 1)); + echo "==> {$rel}\n"; + + $t0 = microtime(true); + // Run the example and capture stdout + stderr together. + $output = (string) shell_exec($php.' '.escapeshellarg($script).' 2>&1'); + $ms = (int) round((microtime(true) - $t0) * 1000); + + foreach (explode("\n", rtrim($output)) as $line) { + if (trim($line) !== '') { + echo " {$line}\n"; + } + } + + $saved += substr_count($output, 'Saved:'); + + // Treat PHP-level errors as failures; warnings/notices are reported but pass. + if (preg_match('/(Fatal error|Uncaught|Parse error)/i', $output)) { + $failures[] = $rel; + echo " [FAIL] in {$ms}ms\n\n"; + } else { + echo " [ok] {$ms}ms\n\n"; + } +} + +$elapsed = number_format(microtime(true) - $start, 1); +echo str_repeat('-', 48)."\n"; +echo count($scripts)." scripts, {$saved} images, ".count($failures)." failed in {$elapsed}s\n"; + +if ($failures !== []) { + echo 'Failed: '.implode(', ', $failures)."\n"; + exit(1); +} diff --git a/examples/repository/output/repository.png b/examples/repository/output/repository.png new file mode 100644 index 0000000..04836d6 Binary files /dev/null and b/examples/repository/output/repository.png differ diff --git a/examples/repository/repository.php b/examples/repository/repository.php new file mode 100644 index 0000000..b18b9a4 --- /dev/null +++ b/examples/repository/repository.php @@ -0,0 +1,81 @@ + + + + {$owner}/ + + + {$name} + + + {$description} + + +
+
+ {$stars} + stars +
+ · +
+ {$forks} + forks +
+ · +
+
+ PHP + + +
+ Rust +
+
+ +HTML; + +$png = HtmlShot::render($html, [ + 'width' => 1200, + 'height' => 630, + 'format' => 'png', + 'fonts' => [ + Font::fromFile("{$fontsPath}/Geist/variable/Geist[wght].ttf", 'Geist'), + Font::fromFile("{$fontsPath}/GeistMono/variable/GeistMono[wght].ttf", 'Geist Mono'), + ], +]); + +save_to_output($png, 'repository.png', $outputDir); diff --git a/examples/simple/output/simple.png b/examples/simple/output/simple.png new file mode 100644 index 0000000..029a9b7 Binary files /dev/null and b/examples/simple/output/simple.png differ diff --git a/examples/simple/simple.php b/examples/simple/simple.php new file mode 100644 index 0000000..66de863 --- /dev/null +++ b/examples/simple/simple.php @@ -0,0 +1,28 @@ +That was fast!'; + +$png = HtmlShot::render($html, [ + 'width' => 1280, + 'height' => 630, + 'format' => 'png', + 'fonts' => [ + Font::fromFile("{$fontsPath}/Geist/variable/Geist[wght].ttf", 'Geist'), + ], +]); + +save_to_output($png, 'simple.png', $outputDir); diff --git a/examples/stars/output/stars.png b/examples/stars/output/stars.png new file mode 100644 index 0000000..6134b0c Binary files /dev/null and b/examples/stars/output/stars.png differ diff --git a/examples/stars/stars.php b/examples/stars/stars.php new file mode 100644 index 0000000..f313a2a --- /dev/null +++ b/examples/stars/stars.php @@ -0,0 +1,39 @@ + +
+ +
+
+

zero stars

+
+ +HTML; + +$png = HtmlShot::render($html, [ + 'width' => 1280, + 'height' => 630, + 'format' => 'png', + 'fonts' => [ + Font::fromFile("{$fontsPath}/Geist/variable/Geist[wght].ttf", 'Geist'), + ], +]); + +save_to_output($png, 'stars.png', $outputDir); diff --git a/examples/tailwind/output/tailwind.png b/examples/tailwind/output/tailwind.png new file mode 100644 index 0000000..5caf5fc Binary files /dev/null and b/examples/tailwind/output/tailwind.png differ diff --git a/examples/tailwind/tailwind.php b/examples/tailwind/tailwind.php new file mode 100644 index 0000000..925b2cb --- /dev/null +++ b/examples/tailwind/tailwind.php @@ -0,0 +1,36 @@ + +
+

HtmlShot.

+

html in, image out

+

HTML to image rendering for PHP, powered by Rust.

+
+ +HTML; + +$png = HtmlShot::render($html, [ + 'width' => 1280, + 'height' => 630, + 'format' => 'png', + 'fonts' => [ + Font::fromFile("{$fontsPath}/Geist/variable/Geist[wght].ttf", 'Geist'), + ], +]); + +save_to_output($png, 'tailwind.png', $outputDir); diff --git a/tests/fonts/README.md b/tests/fonts/README.md new file mode 100644 index 0000000..fd6791a --- /dev/null +++ b/tests/fonts/README.md @@ -0,0 +1,5 @@ +# Geist Font + +The [Geist font](https://vercel.com/font) is included so that testa and [examples](../../examples/) can be tried without external dependencies. + +If you want to use Geist in your project, you should not rely on this font file being present when the package is installed via Composer. \ No newline at end of file