Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ The color schemes is based on the [Prism library](https://prismjs.com).

![](https://i.imgur.com/uFfviX7.png)

The [Prism repository](https://github.com/PrismJS/prism-themes/tree/master?tab=readme-ov-file#available-themes) offers a wide range of themes to choose from as well as a [CDN option](https://unpkg.com/browse/prismjs@1.29.0/themes).
The [automad-prism-themes repository](https://github.com/automadcms/automad-prism-themes) offers a wide range of themes to choose from as well as a [CDN option](https://unpkg.com/browse/automad-prism-themes@latest/dist/).

##### element

Expand Down
162 changes: 134 additions & 28 deletions packages/screenshot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,17 @@ const buffer = await browserless.screenshot('https://example.com', {

### Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `type` | `string` | `'png'` | Image format: `'png'`, `'jpeg'`, `'webp'` |
| `quality` | `number` | — | Quality (0-100) for JPEG/WebP |
| `fullPage` | `boolean` | `false` | Capture full scrollable page |
| `element` | `string` | — | CSS selector for element screenshot |
| `codeScheme` | `string` | `'atom-dark'` | Prism.js theme for code highlighting |
| `waitUntil` | `string` | `'auto'` | When to consider navigation done |
| `waitForDom` | `number` | `0` | DOM stability window in ms (idle is `waitForDom / 10`, `0` disables DOM wait) |
| `isPageReady` | `function` | `({ isWhite }) => !isWhite` | Custom readiness predicate for retry loop |
| `overlay` | `object` | `{}` | Browser overlay options |
| Option | Type | Default | Description |
| ------------- | ---------- | --------------------------- | ----------------------------------------------------------------------------- |
| `type` | `string` | `'png'` | Image format: `'png'`, `'jpeg'`, `'webp'` |
| `quality` | `number` | — | Quality (0-100) for JPEG/WebP |
| `fullPage` | `boolean` | `false` | Capture full scrollable page |
| `element` | `string` | — | CSS selector for element screenshot |
| `codeScheme` | `string` | `'atom-dark'` | Prism.js theme for code highlighting |
| `waitUntil` | `string` | `'auto'` | When to consider navigation done |
| `waitForDom` | `number` | `0` | DOM stability window in ms (idle is `waitForDom / 10`, `0` disables DOM wait) |
| `isPageReady` | `function` | `({ isWhite }) => !isWhite` | Custom readiness predicate for retry loop |
| `overlay` | `object` | `{}` | Browser overlay options |

All [Puppeteer page.screenshot() options](https://pptr.dev/api/puppeteer.screenshotoptions) are supported.

Expand All @@ -84,10 +84,10 @@ const buffer = await browserless.screenshot(url, {
overlay: {
// Browser frame theme: 'dark' or 'light'
browser: 'dark',

// Background: color, gradient, or image URL
background: 'linear-gradient(225deg, #FF057C 0%, #8D0B93 50%, #321575 100%)',

// Margin around the screenshot (default: 0.2 = 20%)
margin: 0.2
}
Expand Down Expand Up @@ -149,8 +149,114 @@ const buffer = await browserless.screenshot('https://api.example.com/data', {
})
```

Available themes from [prism-themes](https://github.com/PrismJS/prism-themes):
- `atom-dark`, `ghcolors`, `dracula`, `duotone-dark`, `duotone-light`, `material-dark`, `material-light`, `nord`, `synthwave84`, and more
Available themes from [automad-prism-themes](https://github.com/automadcms/automad-prism-themes) and [prism-themes](https://github.com/PrismJS/prism-themes). When a theme exists in both, the automad version is used.

| Theme | Source |
| --------------------------------- | ------------ |
| `a11y-dark` | prism-themes |
| `atom-dark` | automad |
| `atom-one-dark` | automad |
| `atom-one-light` | automad |
| `aura-dark` | automad |
| `automad-dark` | automad |
| `automad-light` | automad |
| `ayu-dark` | automad |
| `ayu-light` | automad |
| `ayu-mirage` | automad |
| `base` | automad |
| `base16-ateliersulphurpool.light` | prism-themes |
| `bearded-arc-blueberry` | automad |
| `bearded-vivid-light` | automad |
| `boola-dark` | automad |
| `boola-light` | automad |
| `catppuccin-frappe` | automad |
| `catppuccin-latte` | automad |
| `catppuccin-macchiato` | automad |
| `catppuccin-mocha` | automad |
| `cb` | prism-themes |
| `coldark-cold` | automad |
| `coldark-dark` | automad |
| `coy-without-shadows` | prism-themes |
| `darcula` | prism-themes |
| `dark-frost` | automad |
| `dark-space` | automad |
| `dracula` | automad |
| `duotone-dark` | automad |
| `duotone-earth` | automad |
| `duotone-forest` | automad |
| `duotone-light` | automad |
| `duotone-sea` | automad |
| `duotone-space` | automad |
| `ghcolors` | prism-themes |
| `github-dark` | automad |
| `github-light` | automad |
| `gruvbox-dark` | automad |
| `gruvbox-light` | automad |
| `holi-theme` | prism-themes |
| `hopscotch` | prism-themes |
| `laserwave` | automad |
| `lucario` | prism-themes |
| `material-dark` | prism-themes |
| `material-light` | prism-themes |
| `material-oceanic` | prism-themes |
| `night-owl` | automad |
| `night-owl-light` | automad |
| `nightfall` | automad |
| `nord` | automad |
| `one-dark` | prism-themes |
| `one-light` | prism-themes |
| `panda` | automad |
| `poimandres` | automad |
| `pojoaque` | prism-themes |
| `rose-pine` | automad |
| `rose-pine-dawn` | automad |
| `sakura-sun` | automad |
| `sea-shells-dark` | automad |
| `serendipity-midnight` | automad |
| `serendipity-morning` | automad |
| `serendipity-sunset` | automad |
| `shades-of-purple` | automad |
| `solarized-dark-atom` | automad |
| `synthwave84` | automad |
| `tailwind-ice` | automad |
| `tailwind-moon-blue` | automad |
| `tokyo-night` | automad |
| `tokyo-night-light` | automad |
| `tokyo-night-storm` | automad |
| `verdandi` | automad |
| `verdandi-alter` | automad |
| `violet-dream` | automad |
| `vs` | prism-themes |
| `vsc-dark-plus` | automad |
| `xonokai` | prism-themes |
| `z-touch` | prism-themes |

Some themes also offer light/dark combo variants that bundle both modes in a single CSS file, switching automatically based on the page's dark mode class. Since screenshots render without a dark mode context, the light variant will always be used — prefer explicit themes like `github-light` or `github-dark` instead.

Available combo themes:

| Combo theme | Light | Dark |
| ---------------------------------- | --------------------- | ----------------------- |
| `atom-one.light-dark` | `atom-one-light` | `atom-one-dark` |
| `automad.light-dark` | `automad-light` | `automad-dark` |
| `ayu.light-dark` | `ayu-light` | `ayu-dark` |
| `ayu-mirage.light-dark` | `ayu-light` | `ayu-mirage` |
| `bearded-arc-blueberry.light-dark` | `bearded-vivid-light` | `bearded-arc-blueberry` |
| `boola.light-dark` | `boola-light` | `boola-dark` |
| `catppuccin-frappe.light-dark` | `catppuccin-latte` | `catppuccin-frappe` |
| `catppuccin-macchiato.light-dark` | `catppuccin-latte` | `catppuccin-macchiato` |
| `catppuccin-mocha.light-dark` | `catppuccin-latte` | `catppuccin-mocha` |
| `coldark.light-dark` | `coldark-cold` | `coldark-dark` |
| `github.light-dark` | `github-light` | `github-dark` |
| `gruvbox.light-dark` | `gruvbox-light` | `gruvbox-dark` |
| `night-owl.light-dark` | `night-owl-light` | `night-owl` |
| `rose-pine.light-dark` | `rose-pine-dawn` | `rose-pine` |
| `serendipity-midnight.light-dark` | `serendipity-morning` | `serendipity-midnight` |
| `serendipity-sunset.light-dark` | `serendipity-morning` | `serendipity-sunset` |
| `tailwind-moon-blue.light-dark` | `tailwind-ice` | `tailwind-moon-blue` |
| `tokyo-night-storm.light-dark` | `tokyo-night-light` | `tokyo-night-storm` |
| `tokyo-night.light-dark` | `tokyo-night-light` | `tokyo-night` |
| `verdandi.light-dark` | `verdandi` | `verdandi-alter` |

### Smart Content Detection

Expand Down Expand Up @@ -222,22 +328,22 @@ const buffer = await browserless.screenshot('https://example.com', {

This is a **core functionality package** for screenshot capture:

| Consumer | Purpose |
|----------|---------|
| `browserless` (core) | Provides the `.screenshot()` method |
| `@browserless/cli` | Powers the `browserless screenshot` command |
| `@browserless/pdf` | Uses `isWhiteScreenshot` for content detection |
| Consumer | Purpose |
| -------------------- | ---------------------------------------------- |
| `browserless` (core) | Provides the `.screenshot()` method |
| `@browserless/cli` | Powers the `browserless screenshot` command |
| `@browserless/pdf` | Uses `isWhiteScreenshot` for content detection |

### Dependencies

| Package | Purpose |
|---------|---------|
| `@browserless/goto` | Page navigation with ad blocking |
| `sharp` | Image composition for overlays; White/blank screenshot detection |
| `prism-themes` | Syntax highlighting themes |
| `svg-gradient` | Generate gradient backgrounds |
| `got` | Fetch remote background images |
| `is-html-content` | Detect if content is HTML vs code |
| Package | Purpose |
| ---------------------- | ---------------------------------------------------------------- |
| `@browserless/goto` | Page navigation with ad blocking |
| `sharp` | Image composition for overlays; White/blank screenshot detection |
| `automad-prism-themes` | Syntax highlighting themes |
| `svg-gradient` | Generate gradient backgrounds |
| `got` | Fetch remote background images |
| `is-html-content` | Detect if content is HTML vs code |

## License

Expand Down
1 change: 1 addition & 0 deletions packages/screenshot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@browserless/goto": "^10.11.4",
"@kikobeats/content-type": "~1.0.3",
"@kikobeats/time-span": "~1.0.11",
"automad-prism-themes": "~0.3.7",
"debug-logfmt": "~1.4.8",
"got": "~11.8.6",
"is-html-content": "~1.0.0",
Expand Down
44 changes: 36 additions & 8 deletions packages/screenshot/src/pretty/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,46 @@ const { readFile } = require('fs/promises')
const isHttpUrl = require('is-url-http')
const path = require('path')

const { existsSync, readdirSync } = require('fs')

const CACHE = new NullProtoObj()
let themeIndex

const GET_THEME_PATH = () => require('prism-themes').themesDirectory
const GET_THEME_INDEX = () => {
const automadRoot = path.dirname(require.resolve('automad-prism-themes/package.json'))
const { themesDirectory } = require('automad-prism-themes')
const prismRoot = path.dirname(require.resolve('prism-themes/package.json'))
const { themesDirectory: prismThemesDirectory } = require('prism-themes')

const THEME_PATH = () => CACHE.root || (CACHE.root = GET_THEME_PATH())
const dirs = [
themesDirectory,
path.resolve(automadRoot, 'dist'),
prismThemesDirectory,
path.resolve(prismRoot, 'themes')
].filter(dir => dir && existsSync(dir))

module.exports = async themeId => {
if (isHttpUrl(themeId)) return `<link rel="stylesheet" type="text/css" href="${themeId}">`
const index = new NullProtoObj()
for (const dir of dirs) {
for (const file of readdirSync(dir)) {
const match = file.match(/^prism-(.+)\.css$/)
if (match && !match[1].endsWith('.min') && !(match[1] in index)) {
index[match[1]] = path.resolve(dir, file)
}
}
}
return index
}

const stylesheet =
CACHE[themeId] ||
(CACHE[themeId] = await readFile(path.resolve(THEME_PATH(), `prism-${themeId}.css`)))
const THEME_INDEX = () => themeIndex || (themeIndex = GET_THEME_INDEX())

return `<style>${stylesheet}</style>`
const readTheme = async themeId => {
const filePath = THEME_INDEX()[themeId]
if (!filePath) throw new Error(`Unable to resolve Prism theme: ${themeId}`)
return readFile(filePath)
}

module.exports = async themeId => {
if (isHttpUrl(themeId)) return `<link rel="stylesheet" type="text/css" href="${themeId}">`
CACHE[themeId] = CACHE[themeId] || (await readTheme(themeId))
return `<style>${CACHE[themeId]}</style>`
}