Skip to content
Open
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
40 changes: 15 additions & 25 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
name: Test
on:
push:
paths-ignore:
- ".github/workflows/prebuild.yaml"
pull_request:
paths-ignore:
- ".github/workflows/prebuild.yaml"
- push
- pull_request

jobs:
Linux:
Expand All @@ -19,12 +15,11 @@ jobs:
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v4
- name: Install Dependencies
run: |
sudo apt update
sudo apt install -y libcairo2-dev libjpeg-dev libpango1.0-dev libgif-dev librsvg2-dev
- uses: mlugg/setup-zig@v2
- name: Install
run: npm ci --build-from-source
run: npm ci --force # https://github.com/npm/cli/issues/6138
- name: Build
run: zig build
- name: Test
run: npm test

Expand All @@ -39,15 +34,11 @@ jobs:
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v4
- name: Install Dependencies
run: |
Invoke-WebRequest "https://ftp.gnome.org/pub/GNOME/binaries/win64/gtk+/2.22/gtk+-bundle_2.22.1-20101229_win64.zip" -OutFile "gtk.zip"
Expand-Archive gtk.zip -DestinationPath "C:\GTK"
Invoke-WebRequest "https://downloads.sourceforge.net/project/libjpeg-turbo/2.0.4/libjpeg-turbo-2.0.4-vc64.exe" -OutFile "libjpeg.exe" -UserAgent NativeHost
.\libjpeg.exe /S
choco install vcredist2010 -y
- uses: mlugg/setup-zig@v2
- name: Install
run: npm ci --build-from-source
run: npm ci --force
- name: Build
run: zig build
- name: Test
run: npm test

Expand All @@ -62,12 +53,11 @@ jobs:
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v4
- name: Install Dependencies
run: |
brew update
brew install python-setuptools pkg-config cairo pango libpng jpeg giflib librsvg
- uses: mlugg/setup-zig@v2
- name: Install
run: npm ci --build-from-source
run: npm ci --force
- name: Build
run: zig build
- name: Test
run: npm test

Expand All @@ -80,7 +70,7 @@ jobs:
node-version: 20.9.0
- uses: actions/checkout@v4
- name: Install
run: npm ci --ignore-scripts
run: npm ci --force
- name: Lint
run: npm run lint
- name: Lint Types
Expand Down
13 changes: 0 additions & 13 deletions .github/workflows/prebuild.yaml

This file was deleted.

51 changes: 44 additions & 7 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,50 @@
# This is a dummy file so that this workflow shows up in the Actions tab.
# Publishing packages via CI is a 4x feature (next branch) not yet released

name: Publish
on: workflow_dispatch

jobs:
Linux:
name: Nothing
binaries:
name: ${{ matrix.package }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- { runner: macos-15, target: aarch64-macos, package: canvas-darwin-arm64 }
- { runner: macos-15, target: x86_64-macos, package: canvas-darwin-x64 }
- { runner: ubuntu-latest, target: x86-windows, package: canvas-win32-x86 }
- { runner: ubuntu-latest, target: x86_64-windows, package: canvas-win32-x64 }
- { runner: ubuntu-latest, target: aarch64-windows, package: canvas-win32-arm64 }
- { runner: ubuntu-latest, target: arm-linux-gnueabihf, package: canvas-linux-arm-gnueabihf }
- { runner: ubuntu-latest, target: x86-linux-gnu.2.28, package: canvas-linux-x86-gnu }
- { runner: ubuntu-latest, target: x86-linux-musl, package: canvas-linux-x86-musl }
- { runner: ubuntu-latest, target: x86_64-linux-gnu.2.28, package: canvas-linux-x64-gnu }
- { runner: ubuntu-latest, target: x86_64-linux-musl, package: canvas-linux-x64-musl }
- { runner: ubuntu-latest, target: aarch64-linux-gnu.2.28, package: canvas-linux-arm64-gnu }
- { runner: ubuntu-latest, target: aarch64-linux-musl, package: canvas-linux-arm64-musl }
- { runner: ubuntu-latest, target: riscv64-linux-gnu, package: canvas-linux-riscv64-gnu }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 26
- uses: mlugg/setup-zig@v2
- name: Install
run: npm ci --force # https://github.com/npm/cli/issues/6138
- name: Build
run: zig build -Dtarget=${{ matrix.target }} -Doptimize=ReleaseSafe
- name: Publish
run: npm publish --dry-run --workspace ${{ matrix.package }}

root:
name: canvas
needs: binaries
runs-on: ubuntu-latest
steps:
- name: Nothing
run: echo "Nothing to do here"
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 26
- name: Install
run: npm ci --force # https://github.com/npm/cli/issues/6138
- name: Publish
run: npm publish --dry-run
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
build
bin
canvas.node
.DS_Store
.lock-wscript
test/images/*.png
Expand All @@ -18,3 +19,6 @@ node_modules
npm-debug.log

.idea

.zig-cache/
zig-pkg/
134 changes: 73 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,16 @@ node-canvas is a [Cairo](http://cairographics.org/)-backed Canvas implementation
$ npm install canvas
```

By default, pre-built binaries will be downloaded if you're on one of the following platforms:
- macOS x86/64
- macOS aarch64 (aka Apple silicon)
- Linux x86/64 (glibc only)
- Windows x86/64

If you want to build from source, use `npm install --build-from-source` and see the **Compiling** section below.

The minimum version of Node.js required is **18.12.0**.

### Compiling

If you don't have a supported OS or processor architecture, or you use `--build-from-source`, the module will be compiled on your system. This requires several dependencies, including Cairo and Pango.

For detailed installation information, see the [wiki](https://github.com/Automattic/node-canvas/wiki/_pages). One-line installation instructions for common OSes are below. Note that libgif/giflib, librsvg and libjpeg are optional and only required if you need GIF, SVG and JPEG support, respectively. Cairo v1.10.0 or later is required.

OS | Command
----- | -----
macOS | Using [Homebrew](https://brew.sh/):<br/>`brew install pkg-config cairo pango libpng jpeg giflib librsvg pixman python-setuptools`
Ubuntu | `sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev`
Fedora | `sudo yum install gcc-c++ cairo-devel pango-devel libjpeg-turbo-devel giflib-devel`
Solaris | `pkgin install cairo pango pkg-config xproto renderproto kbproto xextproto`
OpenBSD | `doas pkg_add cairo pango png jpeg giflib`
Windows | See the [wiki](https://github.com/Automattic/node-canvas/wiki/Installation:-Windows)
Others | See the [wiki](https://github.com/Automattic/node-canvas/wiki)
Binaries will be installed for the following platforms:
- macOS x64/arm64
- Linux x64/x86/arm64 (glibc or musl)
- Linux arm/riscv64 (glibc)
- Windows x64/x86/arm64

**Mac OS X v10.11+:** If you have recently updated to Mac OS X v10.11+ and are experiencing trouble when compiling, run the following command: `xcode-select --install`. Read more about the problem [on Stack Overflow](http://stackoverflow.com/a/32929012/148072).
If you have xcode 10.0 or higher installed, in order to build from source you need NPM 6.4.1 or higher.
If you want to install for a more exotic system, CD into the package and run
`zig build`. You may need to tweak build.zig. PRs are welcome.

## Quick Example

Expand Down Expand Up @@ -77,9 +59,11 @@ This project is an implementation of the Web Canvas API and implements that API
* [createCanvas()](#createcanvas)
* [createImageData()](#createimagedata)
* [loadImage()](#loadimage)
* [registerFont()](#registerfont)
* [deregisterAllFonts()](#deregisterAllFonts)

### Font API

* [FontFace](#fontface)
* [fonts](#fonts)

### Non-standard APIs

Expand Down Expand Up @@ -151,17 +135,35 @@ const myimg = await loadImage('http://server.com/image.png')
// do something with image
```

### registerFont()
### FontFace

> ```ts
> registerFont(path: string, { family: string, weight?: string, style?: string }) => void
>
> interface FontFaceDescriptors {
> weight?: string | number;
> style?: string;
> }
>
> class FontFace {
> constructor(
> family: string,
> source: string | ArrayBuffer | Uint8Array,
> descriptors?: FontFaceDescriptors
> )
>
> family: string;
> style: string;
> weight: string;
> status: 'loaded' | 'unloaded' | 'error';
> }
>
> ```

To use a font file that is not installed as a system font, use `registerFont()` to register the font with Canvas.
To use your own font file, create a `FontFace` with the file path and add it to [`fonts`](#fonts). `fonts` is just like the browser's `document.fonts`, but with a slightly more limited API.

```js
const { registerFont, createCanvas } = require('canvas')
registerFont('comicsans.ttf', { family: 'Comic Sans' })
const { createCanvas, FontFace, fonts } = require('canvas')
fonts.add(new FontFace('Comic Sans', '/home/you/comicsans.ttf'))

const canvas = createCanvas(500, 500)
const ctx = canvas.getContext('2d')
Expand All @@ -170,34 +172,44 @@ ctx.font = '12px "Comic Sans"'
ctx.fillText('Everyone hates this font :(', 250, 10)
```

The second argument is an object with properties that resemble the CSS properties that are specified in `@font-face` rules. You must specify at least `family`. `weight`, and `style` are optional and default to `'normal'`.
The second argument can either be a path to the font file or a buffer in TrueType format. The third argument is an object with properties that resemble the CSS properties that are specified in `@font-face` rules. You must specify at least `family`. `weight` and `style` are optional and default to `'normal'`.

### deregisterAllFonts()
### fonts

> ```ts
> deregisterAllFonts() => void
>
> class FontFaceSet {
> add(face: FontFace): void;
> has(face: FontFace): boolean;
> clear(): void;
> delete(face: FontFace): boolean;
> [Symbol.iterator](): Iterator<FontFace>;
> size: number;
> }
>
> fonts: FontFaceSet
> ```

Use `deregisterAllFonts` to unregister all fonts that have been previously registered. This method is useful when you want to remove all registered fonts, such as when using the canvas in tests
`fonts` implements a subset of the browser's `document.fonts`. Use `fonts.delete()` to unregister an individual font, or `fonts.clear()` to remove all registered fonts. This is useful when you want to remove all registered fonts, such as when using the canvas in tests.

```ts
const { registerFont, createCanvas, deregisterAllFonts } = require('canvas')
const { registerFont, createCanvas, fonts, FontFace } = require('canvas')

describe('text rendering', () => {
afterEach(() => {
deregisterAllFonts();
})
it('should render text with Comic Sans', () => {
registerFont('comicsans.ttf', { family: 'Comic Sans' })

const canvas = createCanvas(500, 500)
const ctx = canvas.getContext('2d')
ctx.font = '12px "Comic Sans"'
ctx.fillText('Everyone loves this font :)', 250, 10)
// assertScreenshot()
})
afterEach(() => {
fonts.clear()
})
it('should render text with Comic Sans', () => {
fonts.add(new FontFace('Comic Sans', '/home/you/comicsans.ttf'))

const canvas = createCanvas(500, 500)
const ctx = canvas.getContext('2d')

ctx.font = '12px "Comic Sans"'
ctx.fillText('Everyone loves this font :)', 250, 10)

// assertScreenshot()
})
})
```

Expand Down Expand Up @@ -271,15 +283,15 @@ Enabling mime data tracking has no benefits (only a slow down) unless you are ge
Creates a [`Buffer`](https://nodejs.org/api/buffer.html) object representing the image contained in the canvas.

* **callback** If provided, the buffer will be provided in the callback instead of being returned by the function. Invoked with an error as the first argument if encoding failed, or the resulting buffer as the second argument if it succeeded. Not supported for mimeType `raw` or for PDF or SVG canvases.
* **mimeType** A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support), `raw` (unencoded data in BGRA order on little-endian (most) systems, ARGB on big-endian systems; top-to-bottom), `application/pdf` (for PDF canvases) and `image/svg+xml` (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
* **mimeType** A string indicating the image format. Valid options are `image/png`, `image/jpeg`, `raw` (unencoded data in BGRA order on little-endian (most) systems, ARGB on big-endian systems; top-to-bottom), `application/pdf` (for PDF canvases) and `image/svg+xml` (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
* **config**
* For `image/jpeg`, an object specifying the quality (0 to 1), if progressive compression should be used and/or if chroma subsampling should be used: `{quality: 0.75, progressive: false, chromaSubsampling: true}`. All properties are optional.

* For `image/png`, an object specifying the ZLIB compression level (between 0 and 9), the compression filter(s), the palette (indexed PNGs only), the the background palette index (indexed PNGs only) and/or the resolution (ppi): `{compressionLevel: 6, filters: canvas.PNG_ALL_FILTERS, palette: undefined, backgroundIndex: 0, resolution: undefined}`. All properties are optional.

Note that the PNG format encodes the resolution in pixels per meter, so if you specify `96`, the file will encode 3780 ppm (~96.01 ppi). The resolution is undefined by default to match common browser behavior.

* For `application/pdf`, an object specifying optional document metadata: `{title: string, author: string, subject: string, keywords: string, creator: string, creationDate: Date, modDate: Date}`. All properties are optional and default to `undefined`, except for `creationDate`, which defaults to the current date. *Adding metadata requires Cairo 1.16.0 or later.*
* For `application/pdf`, an object specifying optional document metadata: `{title: string, author: string, subject: string, keywords: string, creator: string, creationDate: Date, modDate: Date}`. All properties are optional and default to `undefined`, except for `creationDate`, which defaults to the current date.

For a description of these properties, see page 550 of [PDF 32000-1:2008](https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf).

Expand Down Expand Up @@ -403,7 +415,7 @@ const stream = canvas.createJPEGStream({
> canvas.createPDFStream(config?: any) => ReadableStream
> ```

* `config` an object specifying optional document metadata: `{title: string, author: string, subject: string, keywords: string, creator: string, creationDate: Date, modDate: Date}`. See `toBuffer()` for more information. *Adding metadata requires Cairo 1.16.0 or later.*
* `config` an object specifying optional document metadata: `{title: string, author: string, subject: string, keywords: string, creator: string, creationDate: Date, modDate: Date}`. See `toBuffer()` for more information.

Applies to PDF canvases only. Creates a [`ReadableStream`](https://nodejs.org/api/stream.html#stream_class_stream_readable) that emits the encoded PDF. `canvas.toBuffer()` also produces an encoded PDF, but `createPDFStream()` can be used to reduce memory usage.

Expand Down Expand Up @@ -491,7 +503,7 @@ ctx.fillText('Hello World 2', 50, 80)

canvas.toBuffer() // returns a PDF file
canvas.createPDFStream() // returns a ReadableStream that emits a PDF
// With optional document metadata (requires Cairo 1.16.0):
// With optional document metadata:
canvas.toBuffer('application/pdf', {
title: 'my picture',
keywords: 'node.js demo cairo',
Expand Down Expand Up @@ -575,7 +587,7 @@ These additional pixel formats have experimental support:
* `RGB16_565` Each pixel is 16 bits, with red in the upper 5 bits, green in the middle 6 bits, and blue in the lower 5 bits, in native platform endianness. Some hardware devices and frame buffers use this format. Note that PNG does not support this format; when creating a PNG, the image will be converted to 24-bit RGB. This format is thus suboptimal for generating PNGs. `ImageData` instances for this mode use a `Uint16Array` instead of a `Uint8ClampedArray`.
* `A1` Each pixel is 1 bit, and pixels are packed together into 32-bit quantities. The ordering of the bits matches the endianness of the
platform: on a little-endian machine, the first pixel is the least-significant bit. This format can be used for creating single-color images. *Support for this format is incomplete, see note below.*
* `RGB30` Each pixel is 30 bits, with red in the upper 10, green in the middle 10, and blue in the lower 10. (Requires Cairo 1.12 or later.) *Support for this format is incomplete, see note below.*
* `RGB30` Each pixel is 30 bits, with red in the upper 10, green in the middle 10, and blue in the lower 10. *Support for this format is incomplete, see note below.*

Notes and caveats:

Expand All @@ -592,23 +604,23 @@ Notes and caveats:

* The `ImageData(width, height)` and `ImageData(Uint8ClampedArray, width)` constructors assume 4 bytes per pixel. To create an `ImageData` instance with a different number of bytes per pixel, use `new ImageData(new Uint8ClampedArray(size), width, height)` or `new ImageData(new Uint16ClampedArray(size), width, height)`.

## Testing
## Local Development and Testing

First make sure you've built the latest version. Get all the deps you need (see [compiling](#compiling) above), and run:
To set up node-canvas for local develoment, use the `--force` argument (until [npm/cli#6138](https://github.com/npm/cli/issues/6138) is fixed):

```
npm install --build-from-source
npm install --force
```

For visual tests: `npm run test-server` and point your browser to http://localhost:4000.

For unit tests: `npm run test`.

## Benchmarks
### Benchmarks

Benchmarks live in the `benchmarks` directory.

## Examples
### Examples

Examples line in the `examples` directory. Most produce a png image of the same name, and others such as *live-clock.js* launch an HTTP server to be viewed in the browser.

Expand Down
Loading
Loading