yilanboy/preview 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

yilanboy/preview

Composer 安装命令:

composer require yilanboy/preview

包简介

Generate the preview image

关键字:

README 文档

README

A simple package to generate a preview image.

Installation

Install the package with composer.

composer require yilanboy/preview

Then create an image generator.

use Yilanboy\Preview\Canvas\Solid;
use Yilanboy\Preview\Canvas\Enums\Margin;
use Yilanboy\Preview\Canvas\Enums\Format;
use Yilanboy\Preview\Canvas\Enums\Size;
use Yilanboy\Preview\Generator;
use Yilanboy\Preview\Text\Enums\Font;
use Yilanboy\Preview\Text\Enums\FontSize;
use Yilanboy\Preview\Text\TextBlock;

new Generator()
    ->size(Size::OpenGraph)
    ->margin(Margin::Medium)
    ->format(Format::PNG)
    ->background(new Solid('#777bb3'))
    ->title(new TextBlock(
        text: 'Preview',
        color: 'white',
        fontSize: FontSize::Large,
        font: Font::Inter,
    ))
    ->description(new TextBlock(
        text: 'A simple PHP package to create preview image',
        color: 'white',
        fontSize: FontSize::Small,
        font: Font::Inter,
    ))
    ->output();

This code will display the following image on the web page.

preview

Examples

The most common use for this package is generating an Open Graph image — the preview card shown when a link is shared on social media. Instead of designing one by hand for every blog post, generate it from the post's own title and description. (Generator defaults to Size::OpenGraph, so the dimensions are already right.)

Laravel

Serve it on the fly. Point a route at each post and render the image straight to the response:

use App\Models\Post;
use Yilanboy\Preview\Canvas\Enums\Format;
use Yilanboy\Preview\Canvas\Gradient;
use Yilanboy\Preview\Generator;
use Yilanboy\Preview\Text\Enums\FontSize;
use Yilanboy\Preview\Text\TextBlock;

Route::get('/posts/{post}/og.png', function (Post $post) {
    $format = Format::PNG;

    $image = new Generator()
        ->format($format)
        ->background(new Gradient(from: '#1e3a8a', to: '#9333ea'))
        ->title(new TextBlock(text: $post->title, color: 'white', fontSize: FontSize::Medium))
        ->bytes();

    return response($image, 200, [
        'Content-Type' => $format->mimeType(),
    ]);
})->name('posts.og');

Then reference the route in your page's <head> so social platforms pick it up:

<meta property="og:image" content="{{ route('posts.og', $post) }}">

Or generate it once, ahead of time. If you'd rather not render on every request, build the image when a post is published and save it to disk, then serve the static file:

$image = new Generator()
    ->background(new Gradient(from: '#1e3a8a', to: '#9333ea'))
    ->title(new TextBlock(text: $post->title, color: 'white', fontSize: FontSize::Medium))
    ->bytes();

Storage::put("images/posts/{$post->id}/og.png", $image);

Canvas

Size

Pick a preset that matches where the image will be embedded. Generator defaults to Size::OpenGraph.

use Yilanboy\Preview\Canvas\Enums\Size;

$generator->size(Size::Square);
Preset Dimensions Where it's used
OpenGraph 1200 × 630 Facebook, generic Open Graph previews
Square 1080 × 1080 Instagram, LinkedIn square posts
Landscape 1920 × 1080 16:9 landscape, slide / hero images
Portrait 1080 × 1920 9:16 vertical, stories and reels
YouTube 1280 × 720 YouTube video thumbnails

If no preset fits, set the width and height yourself. Both must be at least 1, otherwise an InvalidInput exception is thrown. size() and dimensions() set the same canvas size, so the last call wins.

$generator->dimensions(width: 800, height: 418);

Margin

Text is inset from the canvas edges by a fixed pixel margin. Generator defaults to Margin::Medium (60px).

use Yilanboy\Preview\Canvas\Enums\Margin;

$generator->margin(Margin::Large);
Preset Pixels
None 0
Small 30
Medium 60
Large 90
ExtraLarge 120

Backgrounds

Generator::background() accepts anything implementing the Background interface. Three implementations ship with the package.

Solid — a flat color.

use Yilanboy\Preview\Canvas\Solid;

$generator->background(new Solid('#777bb3'));

Gradient — two colors interpolated across the canvas.

use Yilanboy\Preview\Canvas\Gradient;
use Yilanboy\Preview\Canvas\Enums\GradientDirection;

$generator->background(new Gradient(
    from: '#1e3a8a',
    to: '#9333ea',
    direction: GradientDirection::Diagonal,
));

GradientDirection cases: Vertical (default) · Horizontal · Diagonal.

Image — render a bitmap behind your text.

use Yilanboy\Preview\Canvas\Image;
use Yilanboy\Preview\Canvas\Enums\ImageFit;

$generator->background(new Image(
    path: __DIR__.'/cover.jpg',
    fit: ImageFit::Cover,
    opacity: 0.6,
    tint: '#000000',
));

ImageFit cases: Cover (default) · Contain · Stretch · Tile.

opacity is a float between 0.0 and 1.0 (default 1.0). When opacity < 1.0, the canvas is filled with tint first so the tint color shows through the partially transparent image — use it to darken or wash the background. tint defaults to #000000.

See all three modes interactively in the playground (next section).

Text

TextBlock

TextBlock is a final readonly class, so it's immutable. Every constructor argument is named and defaulted — to vary a field, construct a new instance:

$base = new TextBlock(text: 'Hello');
$red  = new TextBlock(text: 'Hello', color: 'red');
$big  = new TextBlock(text: 'Hello', fontSize: FontSize::Huge);

If no FontSize preset fits, pass a custom size in pixels instead. It must be at least 1, otherwise an InvalidInput exception is thrown.

$custom = new TextBlock(text: 'Hello', fontSize: 42);

Available customization enums live under Yilanboy\Preview\Text\Enums:

Enum Cases
Font Inter · InterMedium · Roboto · RobotoMedium · JetBrainsMono · JetBrainsMonoMedium · NotoSans · NotoSansMedium · NotoSansSC · NotoSansSCMedium · NotoSansTC · NotoSansTCMedium · NotoSansJP · NotoSansJPMedium
FontSize ExtraSmall (24) · Small (32) · Medium (50) · Large (64) · ExtraLarge (80) · Huge (100)
Alignment Left · Center · Right
LineHeight Snug (1.15) · Normal (1.3) · Relaxed (1.5) · Loose (1.75)

All 14 bundled fonts are static TrueType instances shipped under SIL OFL, with separate files per weight because GD cannot select a weight from a variable font: each family ships a Regular (400) and a Medium (500) variant (the *Medium cases). Inter is the 24pt optical cut. NotoSansTC covers Latin + Traditional Chinese, NotoSansSC covers Latin + Simplified Chinese, and NotoSansJP covers Latin + Japanese; NotoSans, Inter, Roboto, and JetBrainsMono (a monospaced family) are Latin-only.

Currently, the text supports English, Chinese (Traditional and Simplified), and Japanese.

Alignment controls how each line is positioned horizontally within the margins. TextBlock defaults to Alignment::Left.

use Yilanboy\Preview\Text\Enums\Alignment;
use Yilanboy\Preview\Text\TextBlock;

$generator->title(new TextBlock(
    text: 'Preview',
    alignment: Alignment::Center,
));

Line Height

When text wraps to multiple lines, LineHeight controls the spacing between them. The value is a unit-less multiplier of the text's font size (CSS line-height semantics). TextBlock defaults to LineHeight::Normal (1.3×).

use Yilanboy\Preview\Text\Enums\LineHeight;
use Yilanboy\Preview\Text\TextBlock;

$generator->description(new TextBlock(
    text: 'A longer description that wraps to multiple lines for demonstration purposes.',
    lineHeight: LineHeight::Loose,
));
Preset Multiplier
Snug 1.15×
Normal 1.3×
Relaxed 1.5×
Loose 1.75×

Custom Fonts

The font argument also accepts a filesystem path to your own font file, instead of a bundled Font case.

use Yilanboy\Preview\Text\TextBlock;

new TextBlock(
    text: 'Hello',
    font: __DIR__.'/fonts/MyFont.ttf',
);

Only TrueType (.ttf) files are supported. OpenType (.otf) is rejected. A path is accepted only when all of the following hold:

  • the file exists and is readable;
  • the extension is .ttf (case-insensitive);
  • the file's first 4 bytes are the TrueType sfnt header (0x00010000) — this is what rejects an .otf renamed to .ttf, whose header is OTTO.

If the path is not a valid TrueType font, the constructor throws an InvalidInput exception with the message The font path is not a valid TrueType font file. Validation runs in the constructor, so an invalid TextBlock can never exist — construction fails fast.

Security: the font path is read straight off disk and is treated as trusted input. It must come from you, the developer — never from unsanitised end-user input, which would enable arbitrary file reads and file-existence probing.

Output

There are three ways to produce the final image:

  • output() renders it, sets the HTTP Content-Type header from the format's MIME type, and writes the bytes straight to the response. This is convenient for plain PHP scripts; in frameworks like Laravel or Symfony, prefer bytes() and return a framework response instead.
  • save($path) renders it and writes the bytes to a file.
  • bytes() renders it and returns the encoded bytes as a string, which is useful for framework responses or object storage.
$generator->output();            // serve in the HTTP response
$generator->save('preview.png'); // write to a file
$image = $generator->bytes();    // return encoded image bytes

format() selects the encoding. Format has three cases — PNG (default), JPEG, and WEBP.

use Yilanboy\Preview\Canvas\Enums\Format;

$generator->format(Format::JPEG);

quality() changes the output quality for lossy formats (JPEG and WEBP). It accepts values from 0 to 100.

$generator
    ->format(Format::WEBP)
    ->quality(85);

Note: quality() does not apply to PNG. PNG compression is lossless, so changing compression affects encoding speed and file size, not visual image quality.

Note: the format set via format() always wins — save() does not look at the file extension. The configured format is the single source of truth, so format(Format::JPEG)->save('preview.png') writes JPEG bytes into a file named preview.png. Keep the extension and format in sync yourself.

Exceptions

Everything the library throws lives under Yilanboy\Preview\Exceptions. Invalid input (bad colors, font paths, dimensions, etc.) throws InvalidInput, and render-time GD failures throw RenderFailure. Both implement the PreviewException marker interface and still extend their SPL parents (InvalidArgumentException / RuntimeException), so existing catch blocks keep working.

use Yilanboy\Preview\Exceptions\PreviewException;

try {
    $generator->save($path);
} catch (PreviewException $e) {
    // anything this library threw
}

Start a Local Server to Show the Image

There is a playground.php file in examples folder. Start a local server to open an interactive form where you can edit the title, description, font, and font size, switch between Solid / Gradient / Image backgrounds, preview gradients live via CSS, and tweak image opacity and tint with sliders.

php -S localhost:8000 examples/playground.php

Then open your browser and visit localhost:8000.

Development

Run tests:

composer tests

Format code with Pint (only dirty files):

composer fmt

Run all checks (Pint + PHPStan):

composer check

Snapshot Testing

The image tests use snapshot testing: each test generates a PNG and compares it against a stored reference image in tests/Fixtures/. The comparison is tolerant rather than pixel-exact — it ignores anti-aliasing noise that differs between macOS and Linux FreeType builds, while still catching real rendering changes (different text, color, or layout).

When you make an intentional change to rendering output, regenerate the fixtures by setting the UPDATE_SNAPSHOTS environment variable (any non-empty value other than 0 works):

UPDATE_SNAPSHOTS=1 composer tests

Each snapshot test detects the variable and overwrites its fixture in tests/Fixtures/ with the freshly generated image. Always open the regenerated PNGs and confirm they look correct before committing — once a fixture is updated, it becomes the new source of truth.

yilanboy/preview 适用场景与选型建议

yilanboy/preview 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 88 次下载、GitHub Stars 达 3, 最近一次更新时间为 2024 年 11 月 28 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「image」 「preview」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 yilanboy/preview 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 yilanboy/preview 我们能提供哪些服务?
定制开发 / 二次开发

基于 yilanboy/preview 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 88
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 4
  • 点击次数: 4
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 3
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2024-11-28