# @leumas/adapter-meme

Meme / image-caption adapter — impact-text captions, meme templates (Drake, Distracted Boyfriend, Galaxy Brain, …), demotivational posters, free-form text, and a deep-fry filter. Text is drawn as SVG...


Meme / image-caption generator. Give it a source **image** (base64 or `data:` URL) and it hands
back a PNG (base64 + data-URL) with meme text baked on.

Text is drawn with **no `canvas` dependency**: every tool builds an **SVG** string (font, stroke,
word-wrap and auto-fit computed in pure JS) and hands it to **`sharp.composite()`**, which
rasterizes the SVG over the decoded image and re-encodes to PNG.

## Tools

| Tool | Input | Output |
|---|---|---|
| `caption` | `{ image, top?, bottom?, options? }` | Classic white impact top/bottom text with black stroke, auto-fit + wrapped. |
| `fromTemplate` | `{ template, texts, image?, options? }` | Fill a named template's zones. `texts` is an object keyed by zone key/alias, or an array in zone order. Without `image`, a flat template background is generated. |
| `demotivational` | `{ image, title?, subtitle?, options? }` | Black poster: photo inset with a thin white keyline, serif title + subtitle below. |
| `addText` | `{ image, text, x?, y?, size?, color?, options? }` | Free-form caption. `x`/`y` accept absolute px or a `0..1` fraction (default centered). |
| `addBottomBar` | `{ image, text, options? }` | Reddit/Tumblr-style solid caption bar added **above** (`position:'top'`, default) or below the image. |
| `deepFry` | `{ image, options: { level: 1..5 } }` | Crunchy over-saturated "deep-fried" filter (crush → resharpen → saturate → JPEG-crush). |
| `blankTemplate` | `{ template }` | Render a template's empty background canvas. |
| `imageInfo` | `{ image }` | Decode and report `{ width, height, format, bytes, hasAlpha }`. |
| `listTemplates` | *(none)* | **Pure.** Catalog of templates with their zones. |
| `describeTemplate` | `{ template }` | **Pure.** Full zone geometry for one template. |
| `layoutMeme` | `{ template, texts }` or `{ text, width, height }` | **Pure.** Preview where captions land (fontSize + wrapped lines per zone) without rasterizing. |

Templates: `drake`, `distracted-boyfriend`, `two-buttons`, `expanding-brain`,
`change-my-mind`, `one-does-not-simply` (see `data/templates.json`).

## Usage

```js
import meme from '@leumas/adapter-meme'; // or the registry: registry.run('meme', 'caption', args)

const src = fs.readFileSync('cat.png').toString('base64');

const out = await meme.adapters.caption({ image: src, top: 'one does not simply', bottom: 'make a meme' });
// -> { ok:true, format:'png', base64, dataUrl, bytes, width, height }

await meme.adapters.fromTemplate({ template: 'drake', texts: { top: 'writing my own text renderer', bottom: 'building an SVG and letting sharp do it' } });

meme.adapters.listTemplates();           // pure — no image, no sharp needed
```

Each POST body maps 1:1 onto a call (one `args` object per tool).

## Dependency & lazy guard

The only heavy dependency is **`sharp`** (installed at the workspace root, **shared with the `image`
pack** — not re-installed here). It is imported **lazily and guarded**: this module always *loads*
even if the native `sharp` binary is unavailable. The image tools then return
`{ ok:false, unavailable:true, error:'sharp not installed' }`, while the pure tools
(`listTemplates`, `describeTemplate`, `layoutMeme`) keep working.

## DRY boundary

- **This pack captions/decorates raster IMAGES.** It does **not** do ASCII art or emoji art — that
  is *text* and lives in the ascii / emoji text packs.
- It does **not** do general image resize / format-convert / color filters — that is the **`image`**
  pack. `meme` is the meme-specific layer (impact text, meme templates, demotivational frames,
  deep-fry) built on top of the same shared `sharp`.
- No `canvas`/`jimp` dependency is added: text rendering is SVG → `sharp.composite`.


---
Source: shared/engines/adapters/domain/meme/README.md
Canonical: https://docs.leumas.tech/p/adapters/adapter-meme
