Initial commit
This commit is contained in:
commit
e49726988c
5445 files changed
+1089914
No files matched your search
+657
@@ -0,0 +1,657 @@
|
||||
# node-canvas
|
||||
|
||||

|
||||
[](http://badge.fury.io/js/canvas)
|
||||
|
||||
node-canvas is a [Cairo](http://cairographics.org/)-backed Canvas implementation for [Node.js](http://nodejs.org).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ 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)
|
||||
|
||||
**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.
|
||||
|
||||
## Quick Example
|
||||
|
||||
```javascript
|
||||
const { createCanvas, loadImage } = require('canvas')
|
||||
const canvas = createCanvas(200, 200)
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
||||
// Write "Awesome!"
|
||||
ctx.font = '30px Impact'
|
||||
ctx.rotate(0.1)
|
||||
ctx.fillText('Awesome!', 50, 100)
|
||||
|
||||
// Draw line under text
|
||||
var text = ctx.measureText('Awesome!')
|
||||
ctx.strokeStyle = 'rgba(0,0,0,0.5)'
|
||||
ctx.beginPath()
|
||||
ctx.lineTo(50, 102)
|
||||
ctx.lineTo(50 + text.width, 102)
|
||||
ctx.stroke()
|
||||
|
||||
// Draw cat with lime helmet
|
||||
loadImage('examples/images/lime-cat.jpg').then((image) => {
|
||||
ctx.drawImage(image, 50, 0, 70, 70)
|
||||
|
||||
console.log('<img src="' + canvas.toDataURL() + '" />')
|
||||
})
|
||||
```
|
||||
|
||||
## Upgrading from 1.x to 2.x
|
||||
|
||||
See the [changelog](https://github.com/Automattic/node-canvas/blob/master/CHANGELOG.md) for a guide to upgrading from 1.x to 2.x.
|
||||
|
||||
For version 1.x documentation, see [the v1.x branch](https://github.com/Automattic/node-canvas/tree/v1.x).
|
||||
|
||||
## Documentation
|
||||
|
||||
This project is an implementation of the Web Canvas API and implements that API as closely as possible. For API documentation, please visit [Mozilla Web Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API). (See [Compatibility Status](https://github.com/Automattic/node-canvas/wiki/Compatibility-Status) for the current API compliance.) All utility methods and non-standard APIs are documented below.
|
||||
|
||||
### Utility methods
|
||||
|
||||
* [createCanvas()](#createcanvas)
|
||||
* [createImageData()](#createimagedata)
|
||||
* [loadImage()](#loadimage)
|
||||
* [registerFont()](#registerfont)
|
||||
* [deregisterAllFonts()](#deregisterAllFonts)
|
||||
|
||||
|
||||
### Non-standard APIs
|
||||
|
||||
* [Image#src](#imagesrc)
|
||||
* [Image#dataMode](#imagedatamode)
|
||||
* [Canvas#toBuffer()](#canvastobuffer)
|
||||
* [Canvas#createPNGStream()](#canvascreatepngstream)
|
||||
* [Canvas#createJPEGStream()](#canvascreatejpegstream)
|
||||
* [Canvas#createPDFStream()](#canvascreatepdfstream)
|
||||
* [Canvas#toDataURL()](#canvastodataurl)
|
||||
* [CanvasRenderingContext2D#patternQuality](#canvasrenderingcontext2dpatternquality)
|
||||
* [CanvasRenderingContext2D#quality](#canvasrenderingcontext2dquality)
|
||||
* [CanvasRenderingContext2D#textDrawingMode](#canvasrenderingcontext2dtextdrawingmode)
|
||||
* [CanvasRenderingContext2D#globalCompositeOperation = 'saturate'](#canvasrenderingcontext2dglobalcompositeoperation--saturate)
|
||||
* [CanvasRenderingContext2D#antialias](#canvasrenderingcontext2dantialias)
|
||||
|
||||
### createCanvas()
|
||||
|
||||
> ```ts
|
||||
> createCanvas(width: number, height: number, type?: 'PDF'|'SVG') => Canvas
|
||||
> ```
|
||||
|
||||
Creates a Canvas instance. This method works in both Node.js and Web browsers, where there is no Canvas constructor. (See `browser.js` for the implementation that runs in browsers.)
|
||||
|
||||
```js
|
||||
const { createCanvas } = require('canvas')
|
||||
const mycanvas = createCanvas(200, 200)
|
||||
const myPDFcanvas = createCanvas(600, 800, 'pdf') // see "PDF Support" section
|
||||
```
|
||||
|
||||
### createImageData()
|
||||
|
||||
> ```ts
|
||||
> createImageData(width: number, height: number) => ImageData
|
||||
> createImageData(data: Uint8ClampedArray, width: number, height?: number) => ImageData
|
||||
> // for alternative pixel formats:
|
||||
> createImageData(data: Uint16Array, width: number, height?: number) => ImageData
|
||||
> ```
|
||||
|
||||
Creates an ImageData instance. This method works in both Node.js and Web browsers.
|
||||
|
||||
```js
|
||||
const { createImageData } = require('canvas')
|
||||
const width = 20, height = 20
|
||||
const arraySize = width * height * 4
|
||||
const mydata = createImageData(new Uint8ClampedArray(arraySize), width)
|
||||
```
|
||||
|
||||
### loadImage()
|
||||
|
||||
> ```ts
|
||||
> loadImage() => Promise<Image>
|
||||
> ```
|
||||
|
||||
Convenience method for loading images. This method works in both Node.js and Web browsers.
|
||||
|
||||
```js
|
||||
const { loadImage } = require('canvas')
|
||||
const myimg = loadImage('http://server.com/image.png')
|
||||
|
||||
myimg.then(() => {
|
||||
// do something with image
|
||||
}).catch(err => {
|
||||
console.log('oh no!', err)
|
||||
})
|
||||
|
||||
// or with async/await:
|
||||
const myimg = await loadImage('http://server.com/image.png')
|
||||
// do something with image
|
||||
```
|
||||
|
||||
### registerFont()
|
||||
|
||||
> ```ts
|
||||
> registerFont(path: string, { family: string, weight?: string, style?: string }) => void
|
||||
> ```
|
||||
|
||||
To use a font file that is not installed as a system font, use `registerFont()` to register the font with Canvas.
|
||||
|
||||
```js
|
||||
const { registerFont, createCanvas } = require('canvas')
|
||||
registerFont('comicsans.ttf', { family: 'Comic Sans' })
|
||||
|
||||
const canvas = createCanvas(500, 500)
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
||||
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'`.
|
||||
|
||||
### deregisterAllFonts()
|
||||
|
||||
> ```ts
|
||||
> deregisterAllFonts() => void
|
||||
> ```
|
||||
|
||||
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
|
||||
|
||||
```ts
|
||||
const { registerFont, createCanvas, deregisterAllFonts } = 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()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Image#src
|
||||
|
||||
> ```ts
|
||||
> img.src: string|Buffer
|
||||
> ```
|
||||
|
||||
As in browsers, `img.src` can be set to a `data:` URI or a remote URL. In addition, node-canvas allows setting `src` to a local file path or `Buffer` instance.
|
||||
|
||||
```javascript
|
||||
const { Image } = require('canvas')
|
||||
|
||||
// From a buffer:
|
||||
fs.readFile('images/squid.png', (err, squid) => {
|
||||
if (err) throw err
|
||||
const img = new Image()
|
||||
img.onload = () => ctx.drawImage(img, 0, 0)
|
||||
img.onerror = err => { throw err }
|
||||
img.src = squid
|
||||
})
|
||||
|
||||
// From a local file path:
|
||||
const img = new Image()
|
||||
img.onload = () => ctx.drawImage(img, 0, 0)
|
||||
img.onerror = err => { throw err }
|
||||
img.src = 'images/squid.png'
|
||||
|
||||
// From a remote URL:
|
||||
img.src = 'http://picsum.photos/200/300'
|
||||
// ... as above
|
||||
|
||||
// From a `data:` URI:
|
||||
img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
|
||||
// ... as above
|
||||
```
|
||||
|
||||
*Note: In some cases, `img.src=` is currently synchronous. However, you should always use `img.onload` and `img.onerror`, as we intend to make `img.src=` always asynchronous as it is in browsers. See https://github.com/Automattic/node-canvas/issues/1007.*
|
||||
|
||||
### Image#dataMode
|
||||
|
||||
> ```ts
|
||||
> img.dataMode: number
|
||||
> ```
|
||||
|
||||
Applies to JPEG images drawn to PDF canvases only.
|
||||
|
||||
Setting `img.dataMode = Image.MODE_MIME` or `Image.MODE_MIME|Image.MODE_IMAGE` enables MIME data tracking of images. When MIME data is tracked, PDF canvases can embed JPEGs directly into the output, rather than re-encoding into PNG. This can drastically reduce filesize and speed up rendering.
|
||||
|
||||
```javascript
|
||||
const { Image, createCanvas } = require('canvas')
|
||||
const canvas = createCanvas(w, h, 'pdf')
|
||||
const img = new Image()
|
||||
img.dataMode = Image.MODE_IMAGE // Only image data tracked
|
||||
img.dataMode = Image.MODE_MIME // Only mime data tracked
|
||||
img.dataMode = Image.MODE_MIME | Image.MODE_IMAGE // Both are tracked
|
||||
```
|
||||
|
||||
If working with a non-PDF canvas, image data *must* be tracked; otherwise the output will be junk.
|
||||
|
||||
Enabling mime data tracking has no benefits (only a slow down) unless you are generating a PDF.
|
||||
|
||||
### Canvas#toBuffer()
|
||||
|
||||
> ```ts
|
||||
> canvas.toBuffer((err: Error|null, result: Buffer) => void, mimeType?: string, config?: any) => void
|
||||
> canvas.toBuffer(mimeType?: string, config?: any) => Buffer
|
||||
> ```
|
||||
|
||||
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.
|
||||
* **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 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).
|
||||
|
||||
Note that there is no standard separator for `keywords`. A space is recommended because it is in common use by other applications, and Cairo will enclose the list of keywords in quotes if a comma or semicolon is used.
|
||||
|
||||
**Return value**
|
||||
|
||||
If no callback is provided, a [`Buffer`](https://nodejs.org/api/buffer.html). If a callback is provided, none.
|
||||
|
||||
#### Examples
|
||||
|
||||
```js
|
||||
// Default: buf contains a PNG-encoded image
|
||||
const buf = canvas.toBuffer()
|
||||
|
||||
// PNG-encoded, zlib compression level 3 for faster compression but bigger files, no filtering
|
||||
const buf2 = canvas.toBuffer('image/png', { compressionLevel: 3, filters: canvas.PNG_FILTER_NONE })
|
||||
|
||||
// JPEG-encoded, 50% quality
|
||||
const buf3 = canvas.toBuffer('image/jpeg', { quality: 0.5 })
|
||||
|
||||
// Asynchronous PNG
|
||||
canvas.toBuffer((err, buf) => {
|
||||
if (err) throw err // encoding failed
|
||||
// buf is PNG-encoded image
|
||||
})
|
||||
|
||||
canvas.toBuffer((err, buf) => {
|
||||
if (err) throw err // encoding failed
|
||||
// buf is JPEG-encoded image at 95% quality
|
||||
}, 'image/jpeg', { quality: 0.95 })
|
||||
|
||||
// BGRA pixel values, native-endian
|
||||
const buf4 = canvas.toBuffer('raw')
|
||||
const { stride, width } = canvas
|
||||
// In memory, this is `canvas.height * canvas.stride` bytes long.
|
||||
// The top row of pixels, in BGRA order on little-endian hardware,
|
||||
// left-to-right, is:
|
||||
const topPixelsBGRALeftToRight = buf4.slice(0, width * 4)
|
||||
// And the third row is:
|
||||
const row3 = buf4.slice(2 * stride, 2 * stride + width * 4)
|
||||
|
||||
// SVG and PDF canvases
|
||||
const myCanvas = createCanvas(w, h, 'pdf')
|
||||
myCanvas.toBuffer() // returns a buffer containing a PDF-encoded canvas
|
||||
// With optional metadata:
|
||||
myCanvas.toBuffer('application/pdf', {
|
||||
title: 'my picture',
|
||||
keywords: 'node.js demo cairo',
|
||||
creationDate: new Date()
|
||||
})
|
||||
```
|
||||
|
||||
### Canvas#createPNGStream()
|
||||
|
||||
> ```ts
|
||||
> canvas.createPNGStream(config?: any) => ReadableStream
|
||||
> ```
|
||||
|
||||
Creates a [`ReadableStream`](https://nodejs.org/api/stream.html#stream_class_stream_readable) that emits PNG-encoded data.
|
||||
|
||||
* `config` An object specifying the ZLIB compression level (between 0 and 9), the compression filter(s), the palette (indexed PNGs only) and/or the background palette index (indexed PNGs only): `{compressionLevel: 6, filters: canvas.PNG_ALL_FILTERS, palette: undefined, backgroundIndex: 0, resolution: undefined}`. All properties are optional.
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
const fs = require('fs')
|
||||
const out = fs.createWriteStream(__dirname + '/test.png')
|
||||
const stream = canvas.createPNGStream()
|
||||
stream.pipe(out)
|
||||
out.on('finish', () => console.log('The PNG file was created.'))
|
||||
```
|
||||
|
||||
To encode indexed PNGs from canvases with `pixelFormat: 'A8'` or `'A1'`, provide an options object:
|
||||
|
||||
```js
|
||||
const palette = new Uint8ClampedArray([
|
||||
//r g b a
|
||||
0, 50, 50, 255, // index 1
|
||||
10, 90, 90, 255, // index 2
|
||||
127, 127, 255, 255
|
||||
// ...
|
||||
])
|
||||
canvas.createPNGStream({
|
||||
palette: palette,
|
||||
backgroundIndex: 0 // optional, defaults to 0
|
||||
})
|
||||
```
|
||||
|
||||
### Canvas#createJPEGStream()
|
||||
|
||||
> ```ts
|
||||
> canvas.createJPEGStream(config?: any) => ReadableStream
|
||||
> ```
|
||||
|
||||
Creates a [`ReadableStream`](https://nodejs.org/api/stream.html#stream_class_stream_readable) that emits JPEG-encoded data.
|
||||
|
||||
*Note: At the moment, `createJPEGStream()` is synchronous under the hood. That is, it runs in the main thread, not in the libuv threadpool.*
|
||||
|
||||
* `config` 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.
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
const fs = require('fs')
|
||||
const out = fs.createWriteStream(__dirname + '/test.jpeg')
|
||||
const stream = canvas.createJPEGStream()
|
||||
stream.pipe(out)
|
||||
out.on('finish', () => console.log('The JPEG file was created.'))
|
||||
|
||||
// Disable 2x2 chromaSubsampling for deeper colors and use a higher quality
|
||||
const stream = canvas.createJPEGStream({
|
||||
quality: 0.95,
|
||||
chromaSubsampling: false
|
||||
})
|
||||
```
|
||||
|
||||
### Canvas#createPDFStream()
|
||||
|
||||
> ```ts
|
||||
> 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.*
|
||||
|
||||
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.
|
||||
|
||||
### Canvas#toDataURL()
|
||||
|
||||
This is a standard API, but several non-standard calls are supported. The full list of supported calls is:
|
||||
|
||||
```js
|
||||
dataUrl = canvas.toDataURL() // defaults to PNG
|
||||
dataUrl = canvas.toDataURL('image/png')
|
||||
dataUrl = canvas.toDataURL('image/jpeg')
|
||||
dataUrl = canvas.toDataURL('image/jpeg', quality) // quality from 0 to 1
|
||||
canvas.toDataURL((err, png) => { }) // defaults to PNG
|
||||
canvas.toDataURL('image/png', (err, png) => { })
|
||||
canvas.toDataURL('image/jpeg', (err, jpeg) => { }) // sync JPEG is not supported
|
||||
canvas.toDataURL('image/jpeg', {...opts}, (err, jpeg) => { }) // see Canvas#createJPEGStream for valid options
|
||||
canvas.toDataURL('image/jpeg', quality, (err, jpeg) => { }) // spec-following; quality from 0 to 1
|
||||
```
|
||||
|
||||
### CanvasRenderingContext2D#patternQuality
|
||||
|
||||
> ```ts
|
||||
> context.patternQuality: 'fast'|'good'|'best'|'nearest'|'bilinear'
|
||||
> ```
|
||||
|
||||
Defaults to `'good'`. Affects pattern (gradient, image, etc.) rendering quality.
|
||||
|
||||
### CanvasRenderingContext2D#quality
|
||||
|
||||
> ```ts
|
||||
> context.quality: 'fast'|'good'|'best'|'nearest'|'bilinear'
|
||||
> ```
|
||||
|
||||
Defaults to `'good'`. Like `patternQuality`, but applies to transformations affecting more than just patterns.
|
||||
|
||||
### CanvasRenderingContext2D#textDrawingMode
|
||||
|
||||
> ```ts
|
||||
> context.textDrawingMode: 'path'|'glyph'
|
||||
> ```
|
||||
|
||||
Defaults to `'path'`. The effect depends on the canvas type:
|
||||
|
||||
* **Standard (image)** `glyph` and `path` both result in rasterized text. Glyph mode is faster than `path`, but may result in lower-quality text, especially when rotated or translated.
|
||||
|
||||
* **PDF** `glyph` will embed text instead of paths into the PDF. This is faster to encode, faster to open with PDF viewers, yields a smaller file size and makes the text selectable. The subset of the font needed to render the glyphs will be embedded in the PDF. This is usually the mode you want to use with PDF canvases.
|
||||
|
||||
* **SVG** `glyph` does *not* cause `<text>` elements to be produced as one might expect ([cairo bug](https://gitlab.freedesktop.org/cairo/cairo/issues/253)). Rather, `glyph` will create a `<defs>` section with a `<symbol>` for each glyph, then those glyphs be reused via `<use>` elements. `path` mode creates a `<path>` element for each text string. `glyph` mode is faster and yields a smaller file size.
|
||||
|
||||
In `glyph` mode, `ctx.strokeText()` and `ctx.fillText()` behave the same (aside from using the stroke and fill style, respectively).
|
||||
|
||||
This property is tracked as part of the canvas state in save/restore.
|
||||
|
||||
### CanvasRenderingContext2D#globalCompositeOperation = 'saturate'
|
||||
|
||||
In addition to all of the standard global composite operations defined by the Canvas specification, the ['saturate'](https://www.cairographics.org/operators/#saturate) operation is also available.
|
||||
|
||||
### CanvasRenderingContext2D#antialias
|
||||
|
||||
> ```ts
|
||||
> context.antialias: 'default'|'none'|'gray'|'subpixel'
|
||||
> ```
|
||||
|
||||
Sets the anti-aliasing mode.
|
||||
|
||||
## PDF Output Support
|
||||
|
||||
node-canvas can create PDF documents instead of images. The canvas type must be set when creating the canvas as follows:
|
||||
|
||||
```js
|
||||
const canvas = createCanvas(200, 500, 'pdf')
|
||||
```
|
||||
|
||||
An additional method `.addPage()` is then available to create multiple page PDFs:
|
||||
|
||||
```js
|
||||
// On first page
|
||||
ctx.font = '22px Helvetica'
|
||||
ctx.fillText('Hello World', 50, 80)
|
||||
|
||||
ctx.addPage()
|
||||
// Now on second page
|
||||
ctx.font = '22px Helvetica'
|
||||
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):
|
||||
canvas.toBuffer('application/pdf', {
|
||||
title: 'my picture',
|
||||
keywords: 'node.js demo cairo',
|
||||
creationDate: new Date()
|
||||
})
|
||||
```
|
||||
|
||||
It is also possible to create pages with different sizes by passing `width` and `height` to the `.addPage()` method:
|
||||
|
||||
```js
|
||||
ctx.font = '22px Helvetica'
|
||||
ctx.fillText('Hello World', 50, 80)
|
||||
ctx.addPage(400, 800)
|
||||
|
||||
ctx.fillText('Hello World 2', 50, 80)
|
||||
```
|
||||
|
||||
It is possible to add hyperlinks using `.beginTag()` and `.endTag()`:
|
||||
|
||||
```js
|
||||
ctx.beginTag('Link', "uri='https://google.com'")
|
||||
ctx.font = '22px Helvetica'
|
||||
ctx.fillText('Hello World', 50, 80)
|
||||
ctx.endTag('Link')
|
||||
```
|
||||
|
||||
Or with a defined rectangle:
|
||||
|
||||
```js
|
||||
ctx.beginTag('Link', "uri='https://google.com' rect=[50 80 100 20]")
|
||||
ctx.endTag('Link')
|
||||
```
|
||||
|
||||
Note that the syntax for attributes is unique to Cairo. See [cairo_tag_begin](https://www.cairographics.org/manual/cairo-Tags-and-Links.html#cairo-tag-begin) for the full documentation.
|
||||
|
||||
You can create areas on the canvas using the "cairo.dest" tag, and then link to them using the "Link" tag with the `dest=` attribute. You can also define PDF structure for accessibility by using tag names like "P", "H1", and "TABLE". The standard tags are defined in §14.8.4 of the [PDF 1.7](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf) specification.
|
||||
|
||||
See also:
|
||||
|
||||
* [Image#dataMode](#imagedatamode) for embedding JPEGs in PDFs
|
||||
* [Canvas#createPDFStream()](#canvascreatepdfstream) for creating PDF streams
|
||||
* [CanvasRenderingContext2D#textDrawingMode](#canvasrenderingcontext2dtextdrawingmode)
|
||||
for embedding text instead of paths
|
||||
|
||||
## SVG Output Support
|
||||
|
||||
node-canvas can create SVG documents instead of images. The canvas type must be set when creating the canvas as follows:
|
||||
|
||||
```js
|
||||
const canvas = createCanvas(200, 500, 'svg')
|
||||
// Use the normal primitives.
|
||||
fs.writeFileSync('out.svg', canvas.toBuffer())
|
||||
```
|
||||
|
||||
## SVG Image Support
|
||||
|
||||
If librsvg is available when node-canvas is installed, node-canvas can render SVG images to your canvas context. This currently works by rasterizing the SVG image (i.e. drawing an SVG image to an SVG canvas will not preserve the SVG data).
|
||||
|
||||
```js
|
||||
const img = new Image()
|
||||
img.onload = () => ctx.drawImage(img, 0, 0)
|
||||
img.onerror = err => { throw err }
|
||||
img.src = './example.svg'
|
||||
```
|
||||
|
||||
## Image pixel formats (experimental)
|
||||
|
||||
node-canvas has experimental support for additional pixel formats, roughly following the [Canvas color space proposal](https://github.com/WICG/canvas-color-space/blob/master/CanvasColorSpaceProposal.md).
|
||||
|
||||
```js
|
||||
const canvas = createCanvas(200, 200)
|
||||
const ctx = canvas.getContext('2d', { pixelFormat: 'A8' })
|
||||
```
|
||||
|
||||
By default, canvases are created in the `RGBA32` format, which corresponds to the native HTML Canvas behavior. Each pixel is 32 bits. The JavaScript APIs that involve pixel data (`getImageData`, `putImageData`) store the colors in the order {red, green, blue, alpha} without alpha pre-multiplication. (The C++ API stores the colors in the order {alpha, red, green, blue} in native-[endian](https://en.wikipedia.org/wiki/Endianness) ordering, with alpha pre-multiplication.)
|
||||
|
||||
These additional pixel formats have experimental support:
|
||||
|
||||
* `RGB24` Like `RGBA32`, but the 8 alpha bits are always opaque. This format is always used if the `alpha` context attribute is set to false (i.e. `canvas.getContext('2d', {alpha: false})`). This format can be faster than `RGBA32` because transparency does not need to be calculated.
|
||||
* `A8` Each pixel is 8 bits. This format can either be used for creating grayscale images (treating each byte as an alpha value), or for creating indexed PNGs (treating each byte as a palette index) (see [the example using alpha values with `fillStyle`](examples/indexed-png-alpha.js) and [the example using `imageData`](examples/indexed-png-image-data.js)).
|
||||
* `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.*
|
||||
|
||||
Notes and caveats:
|
||||
|
||||
* Using a non-default format can affect the behavior of APIs that involve pixel data:
|
||||
|
||||
* `context2d.createImageData` The size of the array returned depends on the number of bit per pixel for the underlying image data format, per the above descriptions.
|
||||
* `context2d.getImageData` The format of the array returned depends on the underlying image mode, per the above descriptions. Be aware of platform endianness, which can be determined using node.js's [`os.endianness()`](https://nodejs.org/api/os.html#os_os_endianness)
|
||||
function.
|
||||
* `context2d.putImageData` As above.
|
||||
|
||||
* `A1` and `RGB30` do not yet support `getImageData` or `putImageData`. Have a use case and/or opinion on working with these formats? Open an issue and let us know! (See #935.)
|
||||
|
||||
* `A1`, `A8`, `RGB30` and `RGB16_565` with shadow blurs may crash or not render properly.
|
||||
|
||||
* 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
|
||||
|
||||
First make sure you've built the latest version. Get all the deps you need (see [compiling](#compiling) above), and run:
|
||||
|
||||
```
|
||||
npm install --build-from-source
|
||||
```
|
||||
|
||||
For visual tests: `npm run test-server` and point your browser to http://localhost:4000.
|
||||
|
||||
For unit tests: `npm run test`.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Benchmarks live in the `benchmarks` directory.
|
||||
|
||||
## 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.
|
||||
|
||||
## Original Authors
|
||||
|
||||
- TJ Holowaychuk ([tj](http://github.com/tj))
|
||||
- Nathan Rajlich ([TooTallNate](http://github.com/TooTallNate))
|
||||
- Rod Vagg ([rvagg](http://github.com/rvagg))
|
||||
- Juriy Zaytsev ([kangax](http://github.com/kangax))
|
||||
|
||||
## License
|
||||
|
||||
### node-canvas
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2010 LearnBoost, and contributors <dev@learnboost.com>
|
||||
|
||||
Copyright (c) 2014 Automattic, Inc and contributors <dev@automattic.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the 'Software'), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
### BMP parser
|
||||
|
||||
See [license](src/bmp/LICENSE.md)
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
{
|
||||
'conditions': [
|
||||
['OS=="win"', {
|
||||
'variables': {
|
||||
'GTK_Root%': 'C:/GTK', # Set the location of GTK all-in-one bundle
|
||||
'with_jpeg%': 'false',
|
||||
'with_gif%': 'false',
|
||||
'with_rsvg%': 'false',
|
||||
'variables': { # Nest jpeg_root to evaluate it before with_jpeg
|
||||
'jpeg_root%': '<!(node ./util/win_jpeg_lookup)'
|
||||
},
|
||||
'jpeg_root%': '<(jpeg_root)', # Take value of nested variable
|
||||
'conditions': [
|
||||
['jpeg_root==""', {
|
||||
'with_jpeg%': 'false'
|
||||
}, {
|
||||
'with_jpeg%': 'true'
|
||||
}]
|
||||
]
|
||||
}
|
||||
}, { # 'OS!="win"'
|
||||
'variables': {
|
||||
'with_jpeg%': '<!(node ./util/has_lib.js jpeg)',
|
||||
'with_gif%': '<!(node ./util/has_lib.js gif)',
|
||||
'with_rsvg%': '<!(node ./util/has_lib.js rsvg)'
|
||||
}
|
||||
}]
|
||||
],
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'canvas-postbuild',
|
||||
'dependencies': ['canvas'],
|
||||
'conditions': [
|
||||
['OS=="win"', {
|
||||
'copies': [{
|
||||
'destination': '<(PRODUCT_DIR)',
|
||||
'files': [
|
||||
'<(GTK_Root)/bin/zlib1.dll',
|
||||
'<(GTK_Root)/bin/libintl-8.dll',
|
||||
'<(GTK_Root)/bin/libpng14-14.dll',
|
||||
'<(GTK_Root)/bin/libpangocairo-1.0-0.dll',
|
||||
'<(GTK_Root)/bin/libpango-1.0-0.dll',
|
||||
'<(GTK_Root)/bin/libpangoft2-1.0-0.dll',
|
||||
'<(GTK_Root)/bin/libpangowin32-1.0-0.dll',
|
||||
'<(GTK_Root)/bin/libcairo-2.dll',
|
||||
'<(GTK_Root)/bin/libfontconfig-1.dll',
|
||||
'<(GTK_Root)/bin/libfreetype-6.dll',
|
||||
'<(GTK_Root)/bin/libglib-2.0-0.dll',
|
||||
'<(GTK_Root)/bin/libgobject-2.0-0.dll',
|
||||
'<(GTK_Root)/bin/libgmodule-2.0-0.dll',
|
||||
'<(GTK_Root)/bin/libgthread-2.0-0.dll',
|
||||
'<(GTK_Root)/bin/libexpat-1.dll'
|
||||
]
|
||||
}]
|
||||
}]
|
||||
]
|
||||
},
|
||||
{
|
||||
'target_name': 'canvas',
|
||||
'include_dirs': ["<!(node -p \"require('node-addon-api').include_dir\")"],
|
||||
'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS', 'NODE_ADDON_API_ENABLE_MAYBE' ],
|
||||
'sources': [
|
||||
'src/backend/Backend.cc',
|
||||
'src/backend/ImageBackend.cc',
|
||||
'src/backend/PdfBackend.cc',
|
||||
'src/backend/SvgBackend.cc',
|
||||
'src/bmp/BMPParser.cc',
|
||||
'src/Backends.cc',
|
||||
'src/Canvas.cc',
|
||||
'src/CanvasGradient.cc',
|
||||
'src/CanvasPattern.cc',
|
||||
'src/CanvasRenderingContext2d.cc',
|
||||
'src/closure.cc',
|
||||
'src/color.cc',
|
||||
'src/Image.cc',
|
||||
'src/ImageData.cc',
|
||||
'src/init.cc',
|
||||
'src/register_font.cc',
|
||||
'src/FontParser.cc'
|
||||
],
|
||||
'conditions': [
|
||||
['OS=="win"', {
|
||||
'libraries': [
|
||||
'-l<(GTK_Root)/lib/cairo.lib',
|
||||
'-l<(GTK_Root)/lib/libpng.lib',
|
||||
'-l<(GTK_Root)/lib/pangocairo-1.0.lib',
|
||||
'-l<(GTK_Root)/lib/pango-1.0.lib',
|
||||
'-l<(GTK_Root)/lib/freetype.lib',
|
||||
'-l<(GTK_Root)/lib/glib-2.0.lib',
|
||||
'-l<(GTK_Root)/lib/gobject-2.0.lib'
|
||||
],
|
||||
'include_dirs': [
|
||||
'<(GTK_Root)/include',
|
||||
'<(GTK_Root)/include/cairo',
|
||||
'<(GTK_Root)/include/pango-1.0',
|
||||
'<(GTK_Root)/include/glib-2.0',
|
||||
'<(GTK_Root)/include/freetype2',
|
||||
'<(GTK_Root)/lib/glib-2.0/include'
|
||||
],
|
||||
'defines': [
|
||||
'_USE_MATH_DEFINES', # for M_PI
|
||||
'NOMINMAX' # allow std::min/max to work
|
||||
],
|
||||
'configurations': {
|
||||
'Debug': {
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'WarningLevel': 4,
|
||||
'ExceptionHandling': 1,
|
||||
'DisableSpecificWarnings': [
|
||||
4100, 4611
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
'Release': {
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'WarningLevel': 4,
|
||||
'ExceptionHandling': 1,
|
||||
'DisableSpecificWarnings': [
|
||||
4100, 4611
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { # 'OS!="win"'
|
||||
'libraries': [
|
||||
'<!@(pkg-config pixman-1 --libs)',
|
||||
'<!@(pkg-config cairo --libs)',
|
||||
'<!@(pkg-config libpng --libs)',
|
||||
'<!@(pkg-config pangocairo --libs)',
|
||||
'<!@(pkg-config freetype2 --libs)'
|
||||
],
|
||||
'include_dirs': [
|
||||
'<!@(pkg-config cairo --cflags-only-I | sed s/-I//g)',
|
||||
'<!@(pkg-config libpng --cflags-only-I | sed s/-I//g)',
|
||||
'<!@(pkg-config pangocairo --cflags-only-I | sed s/-I//g)',
|
||||
'<!@(pkg-config freetype2 --cflags-only-I | sed s/-I//g)'
|
||||
],
|
||||
'cflags': ['-Wno-cast-function-type'],
|
||||
'cflags!': ['-fno-exceptions'],
|
||||
'cflags_cc!': ['-fno-exceptions']
|
||||
}],
|
||||
['OS=="mac"', {
|
||||
'cflags+': ['-fvisibility=hidden'],
|
||||
'xcode_settings': {
|
||||
'GCC_SYMBOLS_PRIVATE_EXTERN': 'YES', # -fvisibility=hidden
|
||||
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES'
|
||||
}
|
||||
}],
|
||||
['with_jpeg=="true"', {
|
||||
'defines': [
|
||||
'HAVE_JPEG'
|
||||
],
|
||||
'conditions': [
|
||||
['OS=="win"', {
|
||||
'copies': [{
|
||||
'destination': '<(PRODUCT_DIR)',
|
||||
'files': [
|
||||
'<(jpeg_root)/bin/jpeg62.dll',
|
||||
]
|
||||
}],
|
||||
'include_dirs': [
|
||||
'<(jpeg_root)/include'
|
||||
],
|
||||
'libraries': [
|
||||
'-l<(jpeg_root)/lib/jpeg.lib',
|
||||
]
|
||||
}, {
|
||||
'include_dirs': [
|
||||
'<!@(pkg-config libjpeg --cflags-only-I | sed s/-I//g)'
|
||||
],
|
||||
'libraries': [
|
||||
'<!@(pkg-config libjpeg --libs)'
|
||||
]
|
||||
}]
|
||||
]
|
||||
}],
|
||||
['with_gif=="true"', {
|
||||
'defines': [
|
||||
'HAVE_GIF'
|
||||
],
|
||||
'conditions': [
|
||||
['OS=="win"', {
|
||||
'libraries': [
|
||||
'-l<(GTK_Root)/lib/gif.lib'
|
||||
]
|
||||
}, {
|
||||
'include_dirs': [
|
||||
'/opt/homebrew/include'
|
||||
],
|
||||
'libraries': [
|
||||
'-L/opt/homebrew/lib',
|
||||
'-lgif'
|
||||
]
|
||||
}]
|
||||
]
|
||||
}],
|
||||
['with_rsvg=="true"', {
|
||||
'defines': [
|
||||
'HAVE_RSVG'
|
||||
],
|
||||
'conditions': [
|
||||
['OS=="win"', {
|
||||
'copies': [{
|
||||
'destination': '<(PRODUCT_DIR)',
|
||||
'files': [
|
||||
'<(GTK_Root)/bin/librsvg-2-2.dll',
|
||||
'<(GTK_Root)/bin/libgdk_pixbuf-2.0-0.dll',
|
||||
'<(GTK_Root)/bin/libgio-2.0-0.dll',
|
||||
'<(GTK_Root)/bin/libcroco-0.6-3.dll',
|
||||
'<(GTK_Root)/bin/libgsf-1-114.dll',
|
||||
'<(GTK_Root)/bin/libxml2-2.dll'
|
||||
]
|
||||
}],
|
||||
'libraries': [
|
||||
'-l<(GTK_Root)/lib/librsvg-2-2.lib'
|
||||
]
|
||||
}, {
|
||||
'include_dirs': [
|
||||
'<!@(pkg-config librsvg-2.0 --cflags-only-I | sed s/-I//g)'
|
||||
],
|
||||
'libraries': [
|
||||
'<!@(pkg-config librsvg-2.0 --libs)'
|
||||
]
|
||||
}]
|
||||
]
|
||||
}]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/* globals document, ImageData */
|
||||
|
||||
exports.createCanvas = function (width, height) {
|
||||
return Object.assign(document.createElement('canvas'), { width: width, height: height })
|
||||
}
|
||||
|
||||
exports.createImageData = function (array, width, height) {
|
||||
// Browser implementation of ImageData looks at the number of arguments passed
|
||||
switch (arguments.length) {
|
||||
case 0: return new ImageData()
|
||||
case 1: return new ImageData(array)
|
||||
case 2: return new ImageData(array, width)
|
||||
default: return new ImageData(array, width, height)
|
||||
}
|
||||
}
|
||||
|
||||
exports.loadImage = function (src, options) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const image = Object.assign(document.createElement('img'), options)
|
||||
|
||||
function cleanup () {
|
||||
image.onload = null
|
||||
image.onerror = null
|
||||
}
|
||||
|
||||
image.onload = function () { cleanup(); resolve(image) }
|
||||
image.onerror = function () { cleanup(); reject(new Error('Failed to load the image "' + src + '"')) }
|
||||
|
||||
image.src = src
|
||||
})
|
||||
}
|
||||
+354
@@ -0,0 +1,354 @@
|
||||
# We borrow heavily from the kernel build setup, though we are simpler since
|
||||
# we don't have Kconfig tweaking settings on us.
|
||||
|
||||
# The implicit make rules have it looking for RCS files, among other things.
|
||||
# We instead explicitly write all the rules we care about.
|
||||
# It's even quicker (saves ~200ms) to pass -r on the command line.
|
||||
MAKEFLAGS=-r
|
||||
|
||||
# The source directory tree.
|
||||
srcdir := ..
|
||||
abs_srcdir := $(abspath $(srcdir))
|
||||
|
||||
# The name of the builddir.
|
||||
builddir_name ?= .
|
||||
|
||||
# The V=1 flag on command line makes us verbosely print command lines.
|
||||
ifdef V
|
||||
quiet=
|
||||
else
|
||||
quiet=quiet_
|
||||
endif
|
||||
|
||||
# Specify BUILDTYPE=Release on the command line for a release build.
|
||||
BUILDTYPE ?= Release
|
||||
|
||||
# Directory all our build output goes into.
|
||||
# Note that this must be two directories beneath src/ for unit tests to pass,
|
||||
# as they reach into the src/ directory for data with relative paths.
|
||||
builddir ?= $(builddir_name)/$(BUILDTYPE)
|
||||
abs_builddir := $(abspath $(builddir))
|
||||
depsdir := $(builddir)/.deps
|
||||
|
||||
# Object output directory.
|
||||
obj := $(builddir)/obj
|
||||
abs_obj := $(abspath $(obj))
|
||||
|
||||
# We build up a list of every single one of the targets so we can slurp in the
|
||||
# generated dependency rule Makefiles in one pass.
|
||||
all_deps :=
|
||||
|
||||
|
||||
|
||||
CC.target ?= $(CC)
|
||||
CFLAGS.target ?= $(CPPFLAGS) $(CFLAGS)
|
||||
CXX.target ?= $(CXX)
|
||||
CXXFLAGS.target ?= $(CPPFLAGS) $(CXXFLAGS)
|
||||
LINK.target ?= $(LINK)
|
||||
LDFLAGS.target ?= $(LDFLAGS)
|
||||
AR.target ?= $(AR)
|
||||
PLI.target ?= pli
|
||||
|
||||
# C++ apps need to be linked with g++.
|
||||
LINK ?= $(CXX.target)
|
||||
|
||||
# TODO(evan): move all cross-compilation logic to gyp-time so we don't need
|
||||
# to replicate this environment fallback in make as well.
|
||||
CC.host ?= gcc
|
||||
CFLAGS.host ?= $(CPPFLAGS_host) $(CFLAGS_host)
|
||||
CXX.host ?= g++
|
||||
CXXFLAGS.host ?= $(CPPFLAGS_host) $(CXXFLAGS_host)
|
||||
LINK.host ?= $(CXX.host)
|
||||
LDFLAGS.host ?= $(LDFLAGS_host)
|
||||
AR.host ?= ar
|
||||
PLI.host ?= pli
|
||||
|
||||
# Define a dir function that can handle spaces.
|
||||
# http://www.gnu.org/software/make/manual/make.html#Syntax-of-Functions
|
||||
# "leading spaces cannot appear in the text of the first argument as written.
|
||||
# These characters can be put into the argument value by variable substitution."
|
||||
empty :=
|
||||
space := $(empty) $(empty)
|
||||
|
||||
# http://stackoverflow.com/questions/1189781/using-make-dir-or-notdir-on-a-path-with-spaces
|
||||
replace_spaces = $(subst $(space),?,$1)
|
||||
unreplace_spaces = $(subst ?,$(space),$1)
|
||||
dirx = $(call unreplace_spaces,$(dir $(call replace_spaces,$1)))
|
||||
|
||||
# Flags to make gcc output dependency info. Note that you need to be
|
||||
# careful here to use the flags that ccache and distcc can understand.
|
||||
# We write to a dep file on the side first and then rename at the end
|
||||
# so we can't end up with a broken dep file.
|
||||
depfile = $(depsdir)/$(call replace_spaces,$@).d
|
||||
DEPFLAGS = -MMD -MF $(depfile).raw
|
||||
|
||||
# We have to fixup the deps output in a few ways.
|
||||
# (1) the file output should mention the proper .o file.
|
||||
# ccache or distcc lose the path to the target, so we convert a rule of
|
||||
# the form:
|
||||
# foobar.o: DEP1 DEP2
|
||||
# into
|
||||
# path/to/foobar.o: DEP1 DEP2
|
||||
# (2) we want missing files not to cause us to fail to build.
|
||||
# We want to rewrite
|
||||
# foobar.o: DEP1 DEP2 \
|
||||
# DEP3
|
||||
# to
|
||||
# DEP1:
|
||||
# DEP2:
|
||||
# DEP3:
|
||||
# so if the files are missing, they're just considered phony rules.
|
||||
# We have to do some pretty insane escaping to get those backslashes
|
||||
# and dollar signs past make, the shell, and sed at the same time.
|
||||
# Doesn't work with spaces, but that's fine: .d files have spaces in
|
||||
# their names replaced with other characters.
|
||||
define fixup_dep
|
||||
# The depfile may not exist if the input file didn't have any #includes.
|
||||
touch $(depfile).raw
|
||||
# Fixup path as in (1).
|
||||
sed -e "s|^$(notdir $@)|$@|" $(depfile).raw >> $(depfile)
|
||||
# Add extra rules as in (2).
|
||||
# We remove slashes and replace spaces with new lines;
|
||||
# remove blank lines;
|
||||
# delete the first line and append a colon to the remaining lines.
|
||||
sed -e 's|\\||' -e 'y| |\n|' $(depfile).raw |\
|
||||
grep -v '^$$' |\
|
||||
sed -e 1d -e 's|$$|:|' \
|
||||
>> $(depfile)
|
||||
rm $(depfile).raw
|
||||
endef
|
||||
|
||||
# Command definitions:
|
||||
# - cmd_foo is the actual command to run;
|
||||
# - quiet_cmd_foo is the brief-output summary of the command.
|
||||
|
||||
quiet_cmd_cc = CC($(TOOLSET)) $@
|
||||
cmd_cc = $(CC.$(TOOLSET)) -o $@ $< $(GYP_CFLAGS) $(DEPFLAGS) $(CFLAGS.$(TOOLSET)) -c
|
||||
|
||||
quiet_cmd_cxx = CXX($(TOOLSET)) $@
|
||||
cmd_cxx = $(CXX.$(TOOLSET)) -o $@ $< $(GYP_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c
|
||||
|
||||
quiet_cmd_touch = TOUCH $@
|
||||
cmd_touch = touch $@
|
||||
|
||||
quiet_cmd_copy = COPY $@
|
||||
# send stderr to /dev/null to ignore messages when linking directories.
|
||||
cmd_copy = ln -f "$<" "$@" 2>/dev/null || (rm -rf "$@" && cp -af "$<" "$@")
|
||||
|
||||
quiet_cmd_symlink = SYMLINK $@
|
||||
cmd_symlink = ln -sf "$<" "$@"
|
||||
|
||||
quiet_cmd_alink = AR($(TOOLSET)) $@
|
||||
cmd_alink = rm -f $@ && $(AR.$(TOOLSET)) crs $@ $(filter %.o,$^)
|
||||
|
||||
quiet_cmd_alink_thin = AR($(TOOLSET)) $@
|
||||
cmd_alink_thin = rm -f $@ && $(AR.$(TOOLSET)) crsT $@ $(filter %.o,$^)
|
||||
|
||||
# Due to circular dependencies between libraries :(, we wrap the
|
||||
# special "figure out circular dependencies" flags around the entire
|
||||
# input list during linking.
|
||||
quiet_cmd_link = LINK($(TOOLSET)) $@
|
||||
cmd_link = $(LINK.$(TOOLSET)) -o $@ $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -Wl,--start-group $(LD_INPUTS) $(LIBS) -Wl,--end-group
|
||||
|
||||
# Note: this does not handle spaces in paths
|
||||
define xargs
|
||||
$(1) $(word 1,$(2))
|
||||
$(if $(word 2,$(2)),$(call xargs,$(1),$(wordlist 2,$(words $(2)),$(2))))
|
||||
endef
|
||||
|
||||
define write-to-file
|
||||
@: >$(1)
|
||||
$(call xargs,@printf "%s\n" >>$(1),$(2))
|
||||
endef
|
||||
|
||||
OBJ_FILE_LIST := ar-file-list
|
||||
|
||||
define create_archive
|
||||
rm -f $(1) $(1).$(OBJ_FILE_LIST); mkdir -p `dirname $(1)`
|
||||
$(call write-to-file,$(1).$(OBJ_FILE_LIST),$(filter %.o,$(2)))
|
||||
$(AR.$(TOOLSET)) crs $(1) @$(1).$(OBJ_FILE_LIST)
|
||||
endef
|
||||
|
||||
define create_thin_archive
|
||||
rm -f $(1) $(OBJ_FILE_LIST); mkdir -p `dirname $(1)`
|
||||
$(call write-to-file,$(1).$(OBJ_FILE_LIST),$(filter %.o,$(2)))
|
||||
$(AR.$(TOOLSET)) crsT $(1) @$(1).$(OBJ_FILE_LIST)
|
||||
endef
|
||||
|
||||
# We support two kinds of shared objects (.so):
|
||||
# 1) shared_library, which is just bundling together many dependent libraries
|
||||
# into a link line.
|
||||
# 2) loadable_module, which is generating a module intended for dlopen().
|
||||
#
|
||||
# They differ only slightly:
|
||||
# In the former case, we want to package all dependent code into the .so.
|
||||
# In the latter case, we want to package just the API exposed by the
|
||||
# outermost module.
|
||||
# This means shared_library uses --whole-archive, while loadable_module doesn't.
|
||||
# (Note that --whole-archive is incompatible with the --start-group used in
|
||||
# normal linking.)
|
||||
|
||||
# Other shared-object link notes:
|
||||
# - Set SONAME to the library filename so our binaries don't reference
|
||||
# the local, absolute paths used on the link command-line.
|
||||
quiet_cmd_solink = SOLINK($(TOOLSET)) $@
|
||||
cmd_solink = $(LINK.$(TOOLSET)) -o $@ -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -Wl,-soname=$(@F) -Wl,--whole-archive $(LD_INPUTS) -Wl,--no-whole-archive $(LIBS)
|
||||
|
||||
quiet_cmd_solink_module = SOLINK_MODULE($(TOOLSET)) $@
|
||||
cmd_solink_module = $(LINK.$(TOOLSET)) -o $@ -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -Wl,-soname=$(@F) -Wl,--start-group $(filter-out FORCE_DO_CMD, $^) -Wl,--end-group $(LIBS)
|
||||
|
||||
|
||||
# Define an escape_quotes function to escape single quotes.
|
||||
# This allows us to handle quotes properly as long as we always use
|
||||
# use single quotes and escape_quotes.
|
||||
escape_quotes = $(subst ','\'',$(1))
|
||||
# This comment is here just to include a ' to unconfuse syntax highlighting.
|
||||
# Define an escape_vars function to escape '$' variable syntax.
|
||||
# This allows us to read/write command lines with shell variables (e.g.
|
||||
# $LD_LIBRARY_PATH), without triggering make substitution.
|
||||
escape_vars = $(subst $$,$$$$,$(1))
|
||||
# Helper that expands to a shell command to echo a string exactly as it is in
|
||||
# make. This uses printf instead of echo because printf's behaviour with respect
|
||||
# to escape sequences is more portable than echo's across different shells
|
||||
# (e.g., dash, bash).
|
||||
exact_echo = printf '%s\n' '$(call escape_quotes,$(1))'
|
||||
|
||||
# Helper to compare the command we're about to run against the command
|
||||
# we logged the last time we ran the command. Produces an empty
|
||||
# string (false) when the commands match.
|
||||
# Tricky point: Make has no string-equality test function.
|
||||
# The kernel uses the following, but it seems like it would have false
|
||||
# positives, where one string reordered its arguments.
|
||||
# arg_check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) \
|
||||
# $(filter-out $(cmd_$@), $(cmd_$(1))))
|
||||
# We instead substitute each for the empty string into the other, and
|
||||
# say they're equal if both substitutions produce the empty string.
|
||||
# .d files contain ? instead of spaces, take that into account.
|
||||
command_changed = $(or $(subst $(cmd_$(1)),,$(cmd_$(call replace_spaces,$@))),\
|
||||
$(subst $(cmd_$(call replace_spaces,$@)),,$(cmd_$(1))))
|
||||
|
||||
# Helper that is non-empty when a prerequisite changes.
|
||||
# Normally make does this implicitly, but we force rules to always run
|
||||
# so we can check their command lines.
|
||||
# $? -- new prerequisites
|
||||
# $| -- order-only dependencies
|
||||
prereq_changed = $(filter-out FORCE_DO_CMD,$(filter-out $|,$?))
|
||||
|
||||
# Helper that executes all postbuilds until one fails.
|
||||
define do_postbuilds
|
||||
@E=0;\
|
||||
for p in $(POSTBUILDS); do\
|
||||
eval $$p;\
|
||||
E=$$?;\
|
||||
if [ $$E -ne 0 ]; then\
|
||||
break;\
|
||||
fi;\
|
||||
done;\
|
||||
if [ $$E -ne 0 ]; then\
|
||||
rm -rf "$@";\
|
||||
exit $$E;\
|
||||
fi
|
||||
endef
|
||||
|
||||
# do_cmd: run a command via the above cmd_foo names, if necessary.
|
||||
# Should always run for a given target to handle command-line changes.
|
||||
# Second argument, if non-zero, makes it do asm/C/C++ dependency munging.
|
||||
# Third argument, if non-zero, makes it do POSTBUILDS processing.
|
||||
# Note: We intentionally do NOT call dirx for depfile, since it contains ? for
|
||||
# spaces already and dirx strips the ? characters.
|
||||
define do_cmd
|
||||
$(if $(or $(command_changed),$(prereq_changed)),
|
||||
@$(call exact_echo, $($(quiet)cmd_$(1)))
|
||||
@mkdir -p "$(call dirx,$@)" "$(dir $(depfile))"
|
||||
$(if $(findstring flock,$(word 1,$(cmd_$1))),
|
||||
@$(cmd_$(1))
|
||||
@echo " $(quiet_cmd_$(1)): Finished",
|
||||
@$(cmd_$(1))
|
||||
)
|
||||
@$(call exact_echo,$(call escape_vars,cmd_$(call replace_spaces,$@) := $(cmd_$(1)))) > $(depfile)
|
||||
@$(if $(2),$(fixup_dep))
|
||||
$(if $(and $(3), $(POSTBUILDS)),
|
||||
$(call do_postbuilds)
|
||||
)
|
||||
)
|
||||
endef
|
||||
|
||||
# Declare the "all" target first so it is the default,
|
||||
# even though we don't have the deps yet.
|
||||
.PHONY: all
|
||||
all:
|
||||
|
||||
# make looks for ways to re-generate included makefiles, but in our case, we
|
||||
# don't have a direct way. Explicitly telling make that it has nothing to do
|
||||
# for them makes it go faster.
|
||||
%.d: ;
|
||||
|
||||
# Use FORCE_DO_CMD to force a target to run. Should be coupled with
|
||||
# do_cmd.
|
||||
.PHONY: FORCE_DO_CMD
|
||||
FORCE_DO_CMD:
|
||||
|
||||
TOOLSET := target
|
||||
# Suffix rules, putting all outputs into $(obj).
|
||||
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.c FORCE_DO_CMD
|
||||
@$(call do_cmd,cc,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
|
||||
@$(call do_cmd,cxx,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.cpp FORCE_DO_CMD
|
||||
@$(call do_cmd,cxx,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.cxx FORCE_DO_CMD
|
||||
@$(call do_cmd,cxx,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.s FORCE_DO_CMD
|
||||
@$(call do_cmd,cc,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.S FORCE_DO_CMD
|
||||
@$(call do_cmd,cc,1)
|
||||
|
||||
# Try building from generated source, too.
|
||||
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.c FORCE_DO_CMD
|
||||
@$(call do_cmd,cc,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
|
||||
@$(call do_cmd,cxx,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.cpp FORCE_DO_CMD
|
||||
@$(call do_cmd,cxx,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.cxx FORCE_DO_CMD
|
||||
@$(call do_cmd,cxx,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.s FORCE_DO_CMD
|
||||
@$(call do_cmd,cc,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.S FORCE_DO_CMD
|
||||
@$(call do_cmd,cc,1)
|
||||
|
||||
$(obj).$(TOOLSET)/%.o: $(obj)/%.c FORCE_DO_CMD
|
||||
@$(call do_cmd,cc,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(obj)/%.cc FORCE_DO_CMD
|
||||
@$(call do_cmd,cxx,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(obj)/%.cpp FORCE_DO_CMD
|
||||
@$(call do_cmd,cxx,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(obj)/%.cxx FORCE_DO_CMD
|
||||
@$(call do_cmd,cxx,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(obj)/%.s FORCE_DO_CMD
|
||||
@$(call do_cmd,cc,1)
|
||||
$(obj).$(TOOLSET)/%.o: $(obj)/%.S FORCE_DO_CMD
|
||||
@$(call do_cmd,cc,1)
|
||||
|
||||
|
||||
ifeq ($(strip $(foreach prefix,$(NO_LOAD),\
|
||||
$(findstring $(join ^,$(prefix)),\
|
||||
$(join ^,canvas.target.mk)))),)
|
||||
include canvas.target.mk
|
||||
endif
|
||||
|
||||
quiet_cmd_regen_makefile = ACTION Regenerating $@
|
||||
cmd_regen_makefile = cd $(srcdir); /__t/node/21.7.3/x64/lib/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/github/home/.cache/node-gyp/21.7.3" "-Dnode_gyp_dir=/__t/node/21.7.3/x64/lib/node_modules/node-gyp" "-Dnode_lib_file=/github/home/.cache/node-gyp/21.7.3/<(target_arch)/node.lib" "-Dmodule_root_dir=/__w/node-canvas/node-canvas" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/__w/node-canvas/node-canvas/build/config.gypi -I/__t/node/21.7.3/x64/lib/node_modules/node-gyp/addon.gypi -I/github/home/.cache/node-gyp/21.7.3/include/node/common.gypi "--toplevel-dir=." binding.gyp
|
||||
Makefile: $(srcdir)/build/config.gypi $(srcdir)/../../../__t/node/21.7.3/x64/lib/node_modules/node-gyp/addon.gypi $(srcdir)/../../../github/home/.cache/node-gyp/21.7.3/include/node/common.gypi $(srcdir)/binding.gyp
|
||||
$(call do_cmd,regen_makefile)
|
||||
|
||||
# "all" is a concatenation of the "all" targets from all the included
|
||||
# sub-makefiles. This is just here to clarify.
|
||||
all:
|
||||
|
||||
# Add in dependency-tracking rules. $(all_deps) is the list of every single
|
||||
# target in our tree. Only consider the ones with .d (dependency) info:
|
||||
d_files := $(wildcard $(foreach f,$(all_deps),$(depsdir)/$(f).d))
|
||||
ifneq ($(d_files),)
|
||||
include $(d_files)
|
||||
endif
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+6
@@ -0,0 +1,6 @@
|
||||
# This file is generated by gyp; do not edit.
|
||||
|
||||
export builddir_name ?= ./build/.
|
||||
.PHONY: all
|
||||
all:
|
||||
$(MAKE) canvas
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
# This file is generated by gyp; do not edit.
|
||||
|
||||
TOOLSET := target
|
||||
TARGET := canvas
|
||||
DEFS_Debug := \
|
||||
'-DNODE_GYP_MODULE_NAME=canvas' \
|
||||
'-DUSING_UV_SHARED=1' \
|
||||
'-DUSING_V8_SHARED=1' \
|
||||
'-DV8_DEPRECATION_WARNINGS=1' \
|
||||
'-D_GLIBCXX_USE_CXX11_ABI=1' \
|
||||
'-D_LARGEFILE_SOURCE' \
|
||||
'-D_FILE_OFFSET_BITS=64' \
|
||||
'-D__STDC_FORMAT_MACROS' \
|
||||
'-DOPENSSL_NO_PINSHARED' \
|
||||
'-DOPENSSL_THREADS' \
|
||||
'-DHAVE_GIF' \
|
||||
'-DHAVE_JPEG' \
|
||||
'-DHAVE_RSVG' \
|
||||
'-DNAPI_DISABLE_CPP_EXCEPTIONS' \
|
||||
'-DNODE_ADDON_API_ENABLE_MAYBE' \
|
||||
'-DBUILDING_NODE_EXTENSION' \
|
||||
'-DDEBUG' \
|
||||
'-D_DEBUG'
|
||||
|
||||
# Flags passed to all source files.
|
||||
CFLAGS_Debug := \
|
||||
-fPIC \
|
||||
-pthread \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-Wno-unused-parameter \
|
||||
-m64 \
|
||||
-g \
|
||||
-O0
|
||||
|
||||
# Flags passed to only C files.
|
||||
CFLAGS_C_Debug :=
|
||||
|
||||
# Flags passed to only C++ files.
|
||||
CFLAGS_CC_Debug := \
|
||||
-fno-rtti \
|
||||
-std=gnu++17
|
||||
|
||||
INCS_Debug := \
|
||||
-I/github/home/.cache/node-gyp/21.7.3/include/node \
|
||||
-I/github/home/.cache/node-gyp/21.7.3/src \
|
||||
-I/github/home/.cache/node-gyp/21.7.3/deps/openssl/config \
|
||||
-I/github/home/.cache/node-gyp/21.7.3/deps/openssl/openssl/include \
|
||||
-I/github/home/.cache/node-gyp/21.7.3/deps/uv/include \
|
||||
-I/github/home/.cache/node-gyp/21.7.3/deps/zlib \
|
||||
-I/github/home/.cache/node-gyp/21.7.3/deps/v8/include \
|
||||
-I$(srcdir)/node_modules/node-addon-api \
|
||||
-I/usr/local/include/cairo \
|
||||
-I/usr/local/include \
|
||||
-I/usr/local/include/glib-2.0 \
|
||||
-I/usr/local/lib/glib-2.0/include \
|
||||
-I/usr/local/include/pixman-1 \
|
||||
-I/usr/local/include/freetype2 \
|
||||
-I/usr/local/include/libpng16 \
|
||||
-I/usr/local/include/pango-1.0 \
|
||||
-I/usr/local/include/fribidi \
|
||||
-I/usr/local/include/harfbuzz \
|
||||
-I/usr/local/include/librsvg-2.0 \
|
||||
-I/usr/local/include/gdk-pixbuf-2.0
|
||||
|
||||
DEFS_Release := \
|
||||
'-DNODE_GYP_MODULE_NAME=canvas' \
|
||||
'-DUSING_UV_SHARED=1' \
|
||||
'-DUSING_V8_SHARED=1' \
|
||||
'-DV8_DEPRECATION_WARNINGS=1' \
|
||||
'-D_GLIBCXX_USE_CXX11_ABI=1' \
|
||||
'-D_LARGEFILE_SOURCE' \
|
||||
'-D_FILE_OFFSET_BITS=64' \
|
||||
'-D__STDC_FORMAT_MACROS' \
|
||||
'-DOPENSSL_NO_PINSHARED' \
|
||||
'-DOPENSSL_THREADS' \
|
||||
'-DHAVE_GIF' \
|
||||
'-DHAVE_JPEG' \
|
||||
'-DHAVE_RSVG' \
|
||||
'-DNAPI_DISABLE_CPP_EXCEPTIONS' \
|
||||
'-DNODE_ADDON_API_ENABLE_MAYBE' \
|
||||
'-DBUILDING_NODE_EXTENSION'
|
||||
|
||||
# Flags passed to all source files.
|
||||
CFLAGS_Release := \
|
||||
-fPIC \
|
||||
-pthread \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-Wno-unused-parameter \
|
||||
-m64 \
|
||||
-O3 \
|
||||
-fno-omit-frame-pointer
|
||||
|
||||
# Flags passed to only C files.
|
||||
CFLAGS_C_Release :=
|
||||
|
||||
# Flags passed to only C++ files.
|
||||
CFLAGS_CC_Release := \
|
||||
-fno-rtti \
|
||||
-std=gnu++17
|
||||
|
||||
INCS_Release := \
|
||||
-I/github/home/.cache/node-gyp/21.7.3/include/node \
|
||||
-I/github/home/.cache/node-gyp/21.7.3/src \
|
||||
-I/github/home/.cache/node-gyp/21.7.3/deps/openssl/config \
|
||||
-I/github/home/.cache/node-gyp/21.7.3/deps/openssl/openssl/include \
|
||||
-I/github/home/.cache/node-gyp/21.7.3/deps/uv/include \
|
||||
-I/github/home/.cache/node-gyp/21.7.3/deps/zlib \
|
||||
-I/github/home/.cache/node-gyp/21.7.3/deps/v8/include \
|
||||
-I$(srcdir)/node_modules/node-addon-api \
|
||||
-I/usr/local/include/cairo \
|
||||
-I/usr/local/include \
|
||||
-I/usr/local/include/glib-2.0 \
|
||||
-I/usr/local/lib/glib-2.0/include \
|
||||
-I/usr/local/include/pixman-1 \
|
||||
-I/usr/local/include/freetype2 \
|
||||
-I/usr/local/include/libpng16 \
|
||||
-I/usr/local/include/pango-1.0 \
|
||||
-I/usr/local/include/fribidi \
|
||||
-I/usr/local/include/harfbuzz \
|
||||
-I/usr/local/include/librsvg-2.0 \
|
||||
-I/usr/local/include/gdk-pixbuf-2.0
|
||||
|
||||
OBJS := \
|
||||
$(obj).target/$(TARGET)/src/backend/Backend.o \
|
||||
$(obj).target/$(TARGET)/src/backend/ImageBackend.o \
|
||||
$(obj).target/$(TARGET)/src/backend/PdfBackend.o \
|
||||
$(obj).target/$(TARGET)/src/backend/SvgBackend.o \
|
||||
$(obj).target/$(TARGET)/src/bmp/BMPParser.o \
|
||||
$(obj).target/$(TARGET)/src/Backends.o \
|
||||
$(obj).target/$(TARGET)/src/Canvas.o \
|
||||
$(obj).target/$(TARGET)/src/CanvasGradient.o \
|
||||
$(obj).target/$(TARGET)/src/CanvasPattern.o \
|
||||
$(obj).target/$(TARGET)/src/CanvasRenderingContext2d.o \
|
||||
$(obj).target/$(TARGET)/src/closure.o \
|
||||
$(obj).target/$(TARGET)/src/color.o \
|
||||
$(obj).target/$(TARGET)/src/Image.o \
|
||||
$(obj).target/$(TARGET)/src/ImageData.o \
|
||||
$(obj).target/$(TARGET)/src/init.o \
|
||||
$(obj).target/$(TARGET)/src/register_font.o \
|
||||
$(obj).target/$(TARGET)/src/FontParser.o
|
||||
|
||||
# Add to the list of files we specially track dependencies for.
|
||||
all_deps += $(OBJS)
|
||||
|
||||
# CFLAGS et al overrides must be target-local.
|
||||
# See "Target-specific Variable Values" in the GNU Make manual.
|
||||
$(OBJS): TOOLSET := $(TOOLSET)
|
||||
$(OBJS): GYP_CFLAGS := $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE)) $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE))
|
||||
$(OBJS): GYP_CXXFLAGS := $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE)) $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE))
|
||||
|
||||
# Suffix rules, putting all outputs into $(obj).
|
||||
|
||||
$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
|
||||
@$(call do_cmd,cxx,1)
|
||||
|
||||
# Try building from generated source, too.
|
||||
|
||||
$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
|
||||
@$(call do_cmd,cxx,1)
|
||||
|
||||
$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
|
||||
@$(call do_cmd,cxx,1)
|
||||
|
||||
# End of this set of suffix rules
|
||||
### Rules for final target.
|
||||
LDFLAGS_Debug := \
|
||||
-pthread \
|
||||
-rdynamic \
|
||||
-Wl,-rpath '-Wl,$$ORIGIN' \
|
||||
-m64
|
||||
|
||||
LDFLAGS_Release := \
|
||||
-pthread \
|
||||
-rdynamic \
|
||||
-Wl,-rpath '-Wl,$$ORIGIN' \
|
||||
-m64
|
||||
|
||||
LIBS := \
|
||||
-L/usr/local/lib \
|
||||
-lpixman-1 \
|
||||
-lcairo \
|
||||
-lpng16 \
|
||||
-lz \
|
||||
-lpangocairo-1.0 \
|
||||
-lpango-1.0 \
|
||||
-lgobject-2.0 \
|
||||
-lglib-2.0 \
|
||||
-lharfbuzz \
|
||||
-lfreetype \
|
||||
-lrsvg-2 \
|
||||
-lm \
|
||||
-lgio-2.0 \
|
||||
-lgdk_pixbuf-2.0 \
|
||||
-ljpeg \
|
||||
-lgif
|
||||
|
||||
$(obj).target/canvas.node: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
|
||||
$(obj).target/canvas.node: LIBS := $(LIBS)
|
||||
$(obj).target/canvas.node: TOOLSET := $(TOOLSET)
|
||||
$(obj).target/canvas.node: $(OBJS) FORCE_DO_CMD
|
||||
$(call do_cmd,solink_module)
|
||||
|
||||
all_deps += $(obj).target/canvas.node
|
||||
# Add target alias
|
||||
.PHONY: canvas
|
||||
canvas: $(builddir)/canvas.node
|
||||
|
||||
# Copy this to the executable output path.
|
||||
$(builddir)/canvas.node: TOOLSET := $(TOOLSET)
|
||||
$(builddir)/canvas.node: $(obj).target/canvas.node FORCE_DO_CMD
|
||||
$(call do_cmd,copy)
|
||||
|
||||
all_deps += $(builddir)/canvas.node
|
||||
# Short alias for building this executable.
|
||||
.PHONY: canvas.node
|
||||
canvas.node: $(obj).target/canvas.node $(builddir)/canvas.node
|
||||
|
||||
# Add executable to "all" target.
|
||||
.PHONY: all
|
||||
all: $(builddir)/canvas.node
|
||||
|
||||
+420
@@ -0,0 +1,420 @@
|
||||
# Do not edit. File was generated by node-gyp's "configure" step
|
||||
{
|
||||
"target_defaults": {
|
||||
"cflags": [],
|
||||
"default_configuration": "Release",
|
||||
"defines": [],
|
||||
"include_dirs": [],
|
||||
"libraries": []
|
||||
},
|
||||
"variables": {
|
||||
"asan": 0,
|
||||
"coverage": "false",
|
||||
"dcheck_always_on": 0,
|
||||
"debug_nghttp2": "false",
|
||||
"debug_node": "false",
|
||||
"enable_lto": "false",
|
||||
"enable_pgo_generate": "false",
|
||||
"enable_pgo_use": "false",
|
||||
"error_on_warn": "false",
|
||||
"force_dynamic_crt": 0,
|
||||
"gas_version": "2.35",
|
||||
"host_arch": "x64",
|
||||
"icu_data_in": "../../deps/icu-tmp/icudt74l.dat",
|
||||
"icu_endianness": "l",
|
||||
"icu_gyp_path": "tools/icu/icu-generic.gyp",
|
||||
"icu_path": "deps/icu-small",
|
||||
"icu_small": "false",
|
||||
"icu_ver_major": "74",
|
||||
"is_debug": 0,
|
||||
"libdir": "lib",
|
||||
"llvm_version": "0.0",
|
||||
"napi_build_version": "9",
|
||||
"node_builtin_shareable_builtins": [
|
||||
"deps/cjs-module-lexer/lexer.js",
|
||||
"deps/cjs-module-lexer/dist/lexer.js",
|
||||
"deps/undici/undici.js"
|
||||
],
|
||||
"node_byteorder": "little",
|
||||
"node_debug_lib": "false",
|
||||
"node_enable_d8": "false",
|
||||
"node_enable_v8_vtunejit": "false",
|
||||
"node_fipsinstall": "false",
|
||||
"node_install_corepack": "true",
|
||||
"node_install_npm": "true",
|
||||
"node_library_files": [
|
||||
"lib/_http_agent.js",
|
||||
"lib/_http_client.js",
|
||||
"lib/_http_common.js",
|
||||
"lib/_http_incoming.js",
|
||||
"lib/_http_outgoing.js",
|
||||
"lib/_http_server.js",
|
||||
"lib/_stream_duplex.js",
|
||||
"lib/_stream_passthrough.js",
|
||||
"lib/_stream_readable.js",
|
||||
"lib/_stream_transform.js",
|
||||
"lib/_stream_wrap.js",
|
||||
"lib/_stream_writable.js",
|
||||
"lib/_tls_common.js",
|
||||
"lib/_tls_wrap.js",
|
||||
"lib/assert.js",
|
||||
"lib/assert/strict.js",
|
||||
"lib/async_hooks.js",
|
||||
"lib/buffer.js",
|
||||
"lib/child_process.js",
|
||||
"lib/cluster.js",
|
||||
"lib/console.js",
|
||||
"lib/constants.js",
|
||||
"lib/crypto.js",
|
||||
"lib/dgram.js",
|
||||
"lib/diagnostics_channel.js",
|
||||
"lib/dns.js",
|
||||
"lib/dns/promises.js",
|
||||
"lib/domain.js",
|
||||
"lib/events.js",
|
||||
"lib/fs.js",
|
||||
"lib/fs/promises.js",
|
||||
"lib/http.js",
|
||||
"lib/http2.js",
|
||||
"lib/https.js",
|
||||
"lib/inspector.js",
|
||||
"lib/inspector/promises.js",
|
||||
"lib/internal/abort_controller.js",
|
||||
"lib/internal/assert.js",
|
||||
"lib/internal/assert/assertion_error.js",
|
||||
"lib/internal/assert/calltracker.js",
|
||||
"lib/internal/async_hooks.js",
|
||||
"lib/internal/blob.js",
|
||||
"lib/internal/blocklist.js",
|
||||
"lib/internal/bootstrap/node.js",
|
||||
"lib/internal/bootstrap/realm.js",
|
||||
"lib/internal/bootstrap/shadow_realm.js",
|
||||
"lib/internal/bootstrap/switches/does_not_own_process_state.js",
|
||||
"lib/internal/bootstrap/switches/does_own_process_state.js",
|
||||
"lib/internal/bootstrap/switches/is_main_thread.js",
|
||||
"lib/internal/bootstrap/switches/is_not_main_thread.js",
|
||||
"lib/internal/bootstrap/web/exposed-wildcard.js",
|
||||
"lib/internal/bootstrap/web/exposed-window-or-worker.js",
|
||||
"lib/internal/buffer.js",
|
||||
"lib/internal/child_process.js",
|
||||
"lib/internal/child_process/serialization.js",
|
||||
"lib/internal/cli_table.js",
|
||||
"lib/internal/cluster/child.js",
|
||||
"lib/internal/cluster/primary.js",
|
||||
"lib/internal/cluster/round_robin_handle.js",
|
||||
"lib/internal/cluster/shared_handle.js",
|
||||
"lib/internal/cluster/utils.js",
|
||||
"lib/internal/cluster/worker.js",
|
||||
"lib/internal/console/constructor.js",
|
||||
"lib/internal/console/global.js",
|
||||
"lib/internal/constants.js",
|
||||
"lib/internal/crypto/aes.js",
|
||||
"lib/internal/crypto/certificate.js",
|
||||
"lib/internal/crypto/cfrg.js",
|
||||
"lib/internal/crypto/cipher.js",
|
||||
"lib/internal/crypto/diffiehellman.js",
|
||||
"lib/internal/crypto/ec.js",
|
||||
"lib/internal/crypto/hash.js",
|
||||
"lib/internal/crypto/hashnames.js",
|
||||
"lib/internal/crypto/hkdf.js",
|
||||
"lib/internal/crypto/keygen.js",
|
||||
"lib/internal/crypto/keys.js",
|
||||
"lib/internal/crypto/mac.js",
|
||||
"lib/internal/crypto/pbkdf2.js",
|
||||
"lib/internal/crypto/random.js",
|
||||
"lib/internal/crypto/rsa.js",
|
||||
"lib/internal/crypto/scrypt.js",
|
||||
"lib/internal/crypto/sig.js",
|
||||
"lib/internal/crypto/util.js",
|
||||
"lib/internal/crypto/webcrypto.js",
|
||||
"lib/internal/crypto/webidl.js",
|
||||
"lib/internal/crypto/x509.js",
|
||||
"lib/internal/debugger/inspect.js",
|
||||
"lib/internal/debugger/inspect_client.js",
|
||||
"lib/internal/debugger/inspect_repl.js",
|
||||
"lib/internal/dgram.js",
|
||||
"lib/internal/dns/callback_resolver.js",
|
||||
"lib/internal/dns/promises.js",
|
||||
"lib/internal/dns/utils.js",
|
||||
"lib/internal/encoding.js",
|
||||
"lib/internal/error_serdes.js",
|
||||
"lib/internal/errors.js",
|
||||
"lib/internal/event_target.js",
|
||||
"lib/internal/events/symbols.js",
|
||||
"lib/internal/file.js",
|
||||
"lib/internal/fixed_queue.js",
|
||||
"lib/internal/freelist.js",
|
||||
"lib/internal/freeze_intrinsics.js",
|
||||
"lib/internal/fs/cp/cp-sync.js",
|
||||
"lib/internal/fs/cp/cp.js",
|
||||
"lib/internal/fs/dir.js",
|
||||
"lib/internal/fs/glob.js",
|
||||
"lib/internal/fs/promises.js",
|
||||
"lib/internal/fs/read/context.js",
|
||||
"lib/internal/fs/recursive_watch.js",
|
||||
"lib/internal/fs/rimraf.js",
|
||||
"lib/internal/fs/streams.js",
|
||||
"lib/internal/fs/sync_write_stream.js",
|
||||
"lib/internal/fs/utils.js",
|
||||
"lib/internal/fs/watchers.js",
|
||||
"lib/internal/heap_utils.js",
|
||||
"lib/internal/histogram.js",
|
||||
"lib/internal/http.js",
|
||||
"lib/internal/http2/compat.js",
|
||||
"lib/internal/http2/core.js",
|
||||
"lib/internal/http2/util.js",
|
||||
"lib/internal/idna.js",
|
||||
"lib/internal/inspector_async_hook.js",
|
||||
"lib/internal/js_stream_socket.js",
|
||||
"lib/internal/legacy/processbinding.js",
|
||||
"lib/internal/linkedlist.js",
|
||||
"lib/internal/main/check_syntax.js",
|
||||
"lib/internal/main/embedding.js",
|
||||
"lib/internal/main/eval_stdin.js",
|
||||
"lib/internal/main/eval_string.js",
|
||||
"lib/internal/main/inspect.js",
|
||||
"lib/internal/main/mksnapshot.js",
|
||||
"lib/internal/main/print_help.js",
|
||||
"lib/internal/main/prof_process.js",
|
||||
"lib/internal/main/repl.js",
|
||||
"lib/internal/main/run_main_module.js",
|
||||
"lib/internal/main/test_runner.js",
|
||||
"lib/internal/main/watch_mode.js",
|
||||
"lib/internal/main/worker_thread.js",
|
||||
"lib/internal/mime.js",
|
||||
"lib/internal/modules/cjs/loader.js",
|
||||
"lib/internal/modules/esm/assert.js",
|
||||
"lib/internal/modules/esm/create_dynamic_module.js",
|
||||
"lib/internal/modules/esm/fetch_module.js",
|
||||
"lib/internal/modules/esm/formats.js",
|
||||
"lib/internal/modules/esm/get_format.js",
|
||||
"lib/internal/modules/esm/handle_process_exit.js",
|
||||
"lib/internal/modules/esm/hooks.js",
|
||||
"lib/internal/modules/esm/initialize_import_meta.js",
|
||||
"lib/internal/modules/esm/load.js",
|
||||
"lib/internal/modules/esm/loader.js",
|
||||
"lib/internal/modules/esm/module_job.js",
|
||||
"lib/internal/modules/esm/module_map.js",
|
||||
"lib/internal/modules/esm/resolve.js",
|
||||
"lib/internal/modules/esm/shared_constants.js",
|
||||
"lib/internal/modules/esm/translators.js",
|
||||
"lib/internal/modules/esm/utils.js",
|
||||
"lib/internal/modules/esm/worker.js",
|
||||
"lib/internal/modules/helpers.js",
|
||||
"lib/internal/modules/package_json_reader.js",
|
||||
"lib/internal/modules/run_main.js",
|
||||
"lib/internal/navigator.js",
|
||||
"lib/internal/net.js",
|
||||
"lib/internal/options.js",
|
||||
"lib/internal/per_context/domexception.js",
|
||||
"lib/internal/per_context/messageport.js",
|
||||
"lib/internal/per_context/primordials.js",
|
||||
"lib/internal/perf/event_loop_delay.js",
|
||||
"lib/internal/perf/event_loop_utilization.js",
|
||||
"lib/internal/perf/nodetiming.js",
|
||||
"lib/internal/perf/observe.js",
|
||||
"lib/internal/perf/performance.js",
|
||||
"lib/internal/perf/performance_entry.js",
|
||||
"lib/internal/perf/resource_timing.js",
|
||||
"lib/internal/perf/timerify.js",
|
||||
"lib/internal/perf/usertiming.js",
|
||||
"lib/internal/perf/utils.js",
|
||||
"lib/internal/policy/manifest.js",
|
||||
"lib/internal/policy/sri.js",
|
||||
"lib/internal/priority_queue.js",
|
||||
"lib/internal/process/esm_loader.js",
|
||||
"lib/internal/process/execution.js",
|
||||
"lib/internal/process/per_thread.js",
|
||||
"lib/internal/process/permission.js",
|
||||
"lib/internal/process/policy.js",
|
||||
"lib/internal/process/pre_execution.js",
|
||||
"lib/internal/process/promises.js",
|
||||
"lib/internal/process/report.js",
|
||||
"lib/internal/process/signal.js",
|
||||
"lib/internal/process/task_queues.js",
|
||||
"lib/internal/process/warning.js",
|
||||
"lib/internal/process/worker_thread_only.js",
|
||||
"lib/internal/promise_hooks.js",
|
||||
"lib/internal/querystring.js",
|
||||
"lib/internal/readline/callbacks.js",
|
||||
"lib/internal/readline/emitKeypressEvents.js",
|
||||
"lib/internal/readline/interface.js",
|
||||
"lib/internal/readline/promises.js",
|
||||
"lib/internal/readline/utils.js",
|
||||
"lib/internal/repl.js",
|
||||
"lib/internal/repl/await.js",
|
||||
"lib/internal/repl/history.js",
|
||||
"lib/internal/repl/utils.js",
|
||||
"lib/internal/socket_list.js",
|
||||
"lib/internal/socketaddress.js",
|
||||
"lib/internal/source_map/prepare_stack_trace.js",
|
||||
"lib/internal/source_map/source_map.js",
|
||||
"lib/internal/source_map/source_map_cache.js",
|
||||
"lib/internal/stream_base_commons.js",
|
||||
"lib/internal/streams/add-abort-signal.js",
|
||||
"lib/internal/streams/compose.js",
|
||||
"lib/internal/streams/destroy.js",
|
||||
"lib/internal/streams/duplex.js",
|
||||
"lib/internal/streams/duplexify.js",
|
||||
"lib/internal/streams/end-of-stream.js",
|
||||
"lib/internal/streams/from.js",
|
||||
"lib/internal/streams/lazy_transform.js",
|
||||
"lib/internal/streams/legacy.js",
|
||||
"lib/internal/streams/operators.js",
|
||||
"lib/internal/streams/passthrough.js",
|
||||
"lib/internal/streams/pipeline.js",
|
||||
"lib/internal/streams/readable.js",
|
||||
"lib/internal/streams/state.js",
|
||||
"lib/internal/streams/transform.js",
|
||||
"lib/internal/streams/utils.js",
|
||||
"lib/internal/streams/writable.js",
|
||||
"lib/internal/test/binding.js",
|
||||
"lib/internal/test/transfer.js",
|
||||
"lib/internal/test_runner/coverage.js",
|
||||
"lib/internal/test_runner/harness.js",
|
||||
"lib/internal/test_runner/mock/mock.js",
|
||||
"lib/internal/test_runner/mock/mock_timers.js",
|
||||
"lib/internal/test_runner/reporter/dot.js",
|
||||
"lib/internal/test_runner/reporter/junit.js",
|
||||
"lib/internal/test_runner/reporter/lcov.js",
|
||||
"lib/internal/test_runner/reporter/spec.js",
|
||||
"lib/internal/test_runner/reporter/tap.js",
|
||||
"lib/internal/test_runner/reporter/v8-serializer.js",
|
||||
"lib/internal/test_runner/runner.js",
|
||||
"lib/internal/test_runner/test.js",
|
||||
"lib/internal/test_runner/tests_stream.js",
|
||||
"lib/internal/test_runner/utils.js",
|
||||
"lib/internal/timers.js",
|
||||
"lib/internal/tls/secure-context.js",
|
||||
"lib/internal/tls/secure-pair.js",
|
||||
"lib/internal/trace_events_async_hooks.js",
|
||||
"lib/internal/tty.js",
|
||||
"lib/internal/url.js",
|
||||
"lib/internal/util.js",
|
||||
"lib/internal/util/colors.js",
|
||||
"lib/internal/util/comparisons.js",
|
||||
"lib/internal/util/debuglog.js",
|
||||
"lib/internal/util/embedding.js",
|
||||
"lib/internal/util/inspect.js",
|
||||
"lib/internal/util/inspector.js",
|
||||
"lib/internal/util/iterable_weak_map.js",
|
||||
"lib/internal/util/parse_args/parse_args.js",
|
||||
"lib/internal/util/parse_args/utils.js",
|
||||
"lib/internal/util/types.js",
|
||||
"lib/internal/v8/startup_snapshot.js",
|
||||
"lib/internal/v8_prof_polyfill.js",
|
||||
"lib/internal/v8_prof_processor.js",
|
||||
"lib/internal/validators.js",
|
||||
"lib/internal/vm.js",
|
||||
"lib/internal/vm/module.js",
|
||||
"lib/internal/wasm_web_api.js",
|
||||
"lib/internal/watch_mode/files_watcher.js",
|
||||
"lib/internal/watchdog.js",
|
||||
"lib/internal/webidl.js",
|
||||
"lib/internal/webstreams/adapters.js",
|
||||
"lib/internal/webstreams/compression.js",
|
||||
"lib/internal/webstreams/encoding.js",
|
||||
"lib/internal/webstreams/queuingstrategies.js",
|
||||
"lib/internal/webstreams/readablestream.js",
|
||||
"lib/internal/webstreams/transfer.js",
|
||||
"lib/internal/webstreams/transformstream.js",
|
||||
"lib/internal/webstreams/util.js",
|
||||
"lib/internal/webstreams/writablestream.js",
|
||||
"lib/internal/worker.js",
|
||||
"lib/internal/worker/io.js",
|
||||
"lib/internal/worker/js_transferable.js",
|
||||
"lib/module.js",
|
||||
"lib/net.js",
|
||||
"lib/os.js",
|
||||
"lib/path.js",
|
||||
"lib/path/posix.js",
|
||||
"lib/path/win32.js",
|
||||
"lib/perf_hooks.js",
|
||||
"lib/process.js",
|
||||
"lib/punycode.js",
|
||||
"lib/querystring.js",
|
||||
"lib/readline.js",
|
||||
"lib/readline/promises.js",
|
||||
"lib/repl.js",
|
||||
"lib/sea.js",
|
||||
"lib/stream.js",
|
||||
"lib/stream/consumers.js",
|
||||
"lib/stream/promises.js",
|
||||
"lib/stream/web.js",
|
||||
"lib/string_decoder.js",
|
||||
"lib/sys.js",
|
||||
"lib/test.js",
|
||||
"lib/test/reporters.js",
|
||||
"lib/timers.js",
|
||||
"lib/timers/promises.js",
|
||||
"lib/tls.js",
|
||||
"lib/trace_events.js",
|
||||
"lib/tty.js",
|
||||
"lib/url.js",
|
||||
"lib/util.js",
|
||||
"lib/util/types.js",
|
||||
"lib/v8.js",
|
||||
"lib/vm.js",
|
||||
"lib/wasi.js",
|
||||
"lib/worker_threads.js",
|
||||
"lib/zlib.js"
|
||||
],
|
||||
"node_module_version": 120,
|
||||
"node_no_browser_globals": "false",
|
||||
"node_prefix": "/",
|
||||
"node_release_urlbase": "https://nodejs.org/download/release/",
|
||||
"node_section_ordering_info": "",
|
||||
"node_shared": "false",
|
||||
"node_shared_brotli": "false",
|
||||
"node_shared_cares": "false",
|
||||
"node_shared_http_parser": "false",
|
||||
"node_shared_libuv": "false",
|
||||
"node_shared_nghttp2": "false",
|
||||
"node_shared_nghttp3": "false",
|
||||
"node_shared_ngtcp2": "false",
|
||||
"node_shared_openssl": "false",
|
||||
"node_shared_zlib": "false",
|
||||
"node_tag": "",
|
||||
"node_target_type": "executable",
|
||||
"node_use_bundled_v8": "true",
|
||||
"node_use_node_code_cache": "true",
|
||||
"node_use_node_snapshot": "true",
|
||||
"node_use_openssl": "true",
|
||||
"node_use_v8_platform": "true",
|
||||
"node_with_ltcg": "false",
|
||||
"node_without_node_options": "false",
|
||||
"node_write_snapshot_as_array_literals": "false",
|
||||
"openssl_is_fips": "false",
|
||||
"openssl_quic": "true",
|
||||
"ossfuzz": "false",
|
||||
"shlib_suffix": "so.120",
|
||||
"single_executable_application": "true",
|
||||
"target_arch": "x64",
|
||||
"use_prefix_to_find_headers": "false",
|
||||
"v8_enable_31bit_smis_on_64bit_arch": 0,
|
||||
"v8_enable_extensible_ro_snapshot": 0,
|
||||
"v8_enable_gdbjit": 0,
|
||||
"v8_enable_hugepage": 0,
|
||||
"v8_enable_i18n_support": 1,
|
||||
"v8_enable_inspector": 1,
|
||||
"v8_enable_javascript_promise_hooks": 1,
|
||||
"v8_enable_lite_mode": 0,
|
||||
"v8_enable_maglev": 0,
|
||||
"v8_enable_object_print": 1,
|
||||
"v8_enable_pointer_compression": 0,
|
||||
"v8_enable_shared_ro_heap": 1,
|
||||
"v8_enable_short_builtin_calls": 1,
|
||||
"v8_enable_v8_checks": 0,
|
||||
"v8_enable_webassembly": 1,
|
||||
"v8_no_strict_aliasing": 1,
|
||||
"v8_optimized_debug": 1,
|
||||
"v8_promise_internal_field_count": 1,
|
||||
"v8_random_seed": 0,
|
||||
"v8_trace_maps": 0,
|
||||
"v8_use_siphash": 1,
|
||||
"want_separate_host_toolset": 0,
|
||||
"nodedir": "/github/home/.cache/node-gyp/21.7.3",
|
||||
"python": "/usr/local/bin/python3",
|
||||
"standalone_static_library": 1
|
||||
}
|
||||
}
|
||||
+506
@@ -0,0 +1,506 @@
|
||||
// TypeScript Version: 3.0
|
||||
|
||||
import { Readable } from 'stream'
|
||||
|
||||
export interface PngConfig {
|
||||
/** Specifies the ZLIB compression level. Defaults to 6. */
|
||||
compressionLevel?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
||||
/**
|
||||
* Any bitwise combination of `PNG_FILTER_NONE`, `PNG_FILTER_SUB`,
|
||||
* `PNG_FILTER_UP`, `PNG_FILTER_AVG` and `PNG_FILTER_PATETH`; or one of
|
||||
* `PNG_ALL_FILTERS` or `PNG_NO_FILTERS` (all are properties of the canvas
|
||||
* instance). These specify which filters *may* be used by libpng. During
|
||||
* encoding, libpng will select the best filter from this list of allowed
|
||||
* filters. Defaults to `canvas.PNG_ALL_FILTERS`.
|
||||
*/
|
||||
filters?: number
|
||||
/**
|
||||
* _For creating indexed PNGs._ The palette of colors. Entries should be in
|
||||
* RGBA order.
|
||||
*/
|
||||
palette?: Uint8ClampedArray
|
||||
/**
|
||||
* _For creating indexed PNGs._ The index of the background color. Defaults
|
||||
* to 0.
|
||||
*/
|
||||
backgroundIndex?: number
|
||||
/** pixels per inch */
|
||||
resolution?: number
|
||||
}
|
||||
|
||||
export interface JpegConfig {
|
||||
/** Specifies the quality, between 0 and 1. Defaults to 0.75. */
|
||||
quality?: number
|
||||
/** Enables progressive encoding. Defaults to `false`. */
|
||||
progressive?: boolean
|
||||
/** Enables 2x2 chroma subsampling. Defaults to `true`. */
|
||||
chromaSubsampling?: boolean
|
||||
}
|
||||
|
||||
export interface PdfConfig {
|
||||
title?: string
|
||||
author?: string
|
||||
subject?: string
|
||||
keywords?: string
|
||||
creator?: string
|
||||
creationDate?: Date
|
||||
modDate?: Date
|
||||
}
|
||||
|
||||
export interface NodeCanvasRenderingContext2DSettings {
|
||||
alpha?: boolean
|
||||
pixelFormat?: 'RGBA32' | 'RGB24' | 'A8' | 'RGB16_565' | 'A1' | 'RGB30'
|
||||
}
|
||||
|
||||
export class Canvas {
|
||||
width: number
|
||||
height: number
|
||||
|
||||
/** _Non standard._ The type of the canvas. */
|
||||
readonly type: 'image'|'pdf'|'svg'
|
||||
|
||||
/** _Non standard._ Getter. The stride used by the canvas. */
|
||||
readonly stride: number;
|
||||
|
||||
/** Constant used in PNG encoding methods. */
|
||||
static readonly PNG_NO_FILTERS: number
|
||||
/** Constant used in PNG encoding methods. */
|
||||
static readonly PNG_ALL_FILTERS: number
|
||||
/** Constant used in PNG encoding methods. */
|
||||
static readonly PNG_FILTER_NONE: number
|
||||
/** Constant used in PNG encoding methods. */
|
||||
static readonly PNG_FILTER_SUB: number
|
||||
/** Constant used in PNG encoding methods. */
|
||||
static readonly PNG_FILTER_UP: number
|
||||
/** Constant used in PNG encoding methods. */
|
||||
static readonly PNG_FILTER_AVG: number
|
||||
/** Constant used in PNG encoding methods. */
|
||||
static readonly PNG_FILTER_PAETH: number
|
||||
|
||||
constructor(width: number, height: number, type?: 'image'|'pdf'|'svg')
|
||||
|
||||
getContext(contextId: '2d', contextAttributes?: NodeCanvasRenderingContext2DSettings): CanvasRenderingContext2D
|
||||
|
||||
/**
|
||||
* For image canvases, encodes the canvas as a PNG. For PDF canvases,
|
||||
* encodes the canvas as a PDF. For SVG canvases, encodes the canvas as an
|
||||
* SVG.
|
||||
*/
|
||||
toBuffer(cb: (err: Error|null, result: Buffer) => void): void
|
||||
toBuffer(cb: (err: Error|null, result: Buffer) => void, mimeType: 'image/png', config?: PngConfig): void
|
||||
toBuffer(cb: (err: Error|null, result: Buffer) => void, mimeType: 'image/jpeg', config?: JpegConfig): void
|
||||
|
||||
/**
|
||||
* For image canvases, encodes the canvas as a PNG. For PDF canvases,
|
||||
* encodes the canvas as a PDF. For SVG canvases, encodes the canvas as an
|
||||
* SVG.
|
||||
*/
|
||||
toBuffer(): Buffer
|
||||
toBuffer(mimeType: 'image/png', config?: PngConfig): Buffer
|
||||
toBuffer(mimeType: 'image/jpeg', config?: JpegConfig): Buffer
|
||||
toBuffer(mimeType: 'application/pdf', config?: PdfConfig): Buffer
|
||||
|
||||
/**
|
||||
* Returns the unencoded pixel data, top-to-bottom. On little-endian (most)
|
||||
* systems, the array will be ordered BGRA; on big-endian systems, it will
|
||||
* be ARGB.
|
||||
*/
|
||||
toBuffer(mimeType: 'raw'): Buffer
|
||||
|
||||
createPNGStream(config?: PngConfig): PNGStream
|
||||
createJPEGStream(config?: JpegConfig): JPEGStream
|
||||
createPDFStream(config?: PdfConfig): PDFStream
|
||||
|
||||
/** Defaults to PNG image. */
|
||||
toDataURL(): string
|
||||
toDataURL(mimeType: 'image/png'): string
|
||||
toDataURL(mimeType: 'image/jpeg', quality?: number): string
|
||||
/** _Non-standard._ Defaults to PNG image. */
|
||||
toDataURL(cb: (err: Error|null, result: string) => void): void
|
||||
/** _Non-standard._ */
|
||||
toDataURL(mimeType: 'image/png', cb: (err: Error|null, result: string) => void): void
|
||||
/** _Non-standard._ */
|
||||
toDataURL(mimeType: 'image/jpeg', cb: (err: Error|null, result: string) => void): void
|
||||
/** _Non-standard._ */
|
||||
toDataURL(mimeType: 'image/jpeg', config: JpegConfig, cb: (err: Error|null, result: string) => void): void
|
||||
/** _Non-standard._ */
|
||||
toDataURL(mimeType: 'image/jpeg', quality: number, cb: (err: Error|null, result: string) => void): void
|
||||
}
|
||||
|
||||
export interface TextMetrics {
|
||||
readonly alphabeticBaseline: number;
|
||||
readonly actualBoundingBoxAscent: number;
|
||||
readonly actualBoundingBoxDescent: number;
|
||||
readonly actualBoundingBoxLeft: number;
|
||||
readonly actualBoundingBoxRight: number;
|
||||
readonly emHeightAscent: number;
|
||||
readonly emHeightDescent: number;
|
||||
readonly fontBoundingBoxAscent: number;
|
||||
readonly fontBoundingBoxDescent: number;
|
||||
readonly width: number;
|
||||
}
|
||||
|
||||
export type CanvasFillRule = 'evenodd' | 'nonzero';
|
||||
|
||||
export type GlobalCompositeOperation =
|
||||
| 'clear'
|
||||
| 'copy'
|
||||
| 'destination'
|
||||
| 'source-over'
|
||||
| 'destination-over'
|
||||
| 'source-in'
|
||||
| 'destination-in'
|
||||
| 'source-out'
|
||||
| 'destination-out'
|
||||
| 'source-atop'
|
||||
| 'destination-atop'
|
||||
| 'xor'
|
||||
| 'lighter'
|
||||
| 'normal'
|
||||
| 'multiply'
|
||||
| 'screen'
|
||||
| 'overlay'
|
||||
| 'darken'
|
||||
| 'lighten'
|
||||
| 'color-dodge'
|
||||
| 'color-burn'
|
||||
| 'hard-light'
|
||||
| 'soft-light'
|
||||
| 'difference'
|
||||
| 'exclusion'
|
||||
| 'hue'
|
||||
| 'saturation'
|
||||
| 'color'
|
||||
| 'luminosity'
|
||||
| 'saturate';
|
||||
|
||||
export type CanvasLineCap = 'butt' | 'round' | 'square';
|
||||
|
||||
export type CanvasLineJoin = 'bevel' | 'miter' | 'round';
|
||||
|
||||
export type CanvasTextBaseline = 'alphabetic' | 'bottom' | 'hanging' | 'ideographic' | 'middle' | 'top';
|
||||
|
||||
export type CanvasTextAlign = 'center' | 'end' | 'left' | 'right' | 'start';
|
||||
|
||||
export class CanvasRenderingContext2D {
|
||||
drawImage(image: Canvas|Image, dx: number, dy: number): void
|
||||
drawImage(image: Canvas|Image, dx: number, dy: number, dw: number, dh: number): void
|
||||
drawImage(image: Canvas|Image, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void
|
||||
putImageData(imagedata: ImageData, dx: number, dy: number): void;
|
||||
putImageData(imagedata: ImageData, dx: number, dy: number, dirtyX: number, dirtyY: number, dirtyWidth: number, dirtyHeight: number): void;
|
||||
getImageData(sx: number, sy: number, sw: number, sh: number): ImageData;
|
||||
createImageData(sw: number, sh: number): ImageData;
|
||||
createImageData(imagedata: ImageData): ImageData;
|
||||
/**
|
||||
* For PDF canvases, adds another page. If width and/or height are omitted,
|
||||
* the canvas's initial size is used.
|
||||
*/
|
||||
addPage(width?: number, height?: number): void
|
||||
save(): void;
|
||||
restore(): void;
|
||||
rotate(angle: number): void;
|
||||
translate(x: number, y: number): void;
|
||||
transform(a: number, b: number, c: number, d: number, e: number, f: number): void;
|
||||
getTransform(): DOMMatrix;
|
||||
resetTransform(): void;
|
||||
setTransform(transform?: DOMMatrix): void;
|
||||
setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void;
|
||||
isPointInPath(x: number, y: number, fillRule?: CanvasFillRule): boolean;
|
||||
scale(x: number, y: number): void;
|
||||
clip(fillRule?: CanvasFillRule): void;
|
||||
fill(fillRule?: CanvasFillRule): void;
|
||||
stroke(): void;
|
||||
fillText(text: string, x: number, y: number, maxWidth?: number): void;
|
||||
strokeText(text: string, x: number, y: number, maxWidth?: number): void;
|
||||
fillRect(x: number, y: number, w: number, h: number): void;
|
||||
strokeRect(x: number, y: number, w: number, h: number): void;
|
||||
clearRect(x: number, y: number, w: number, h: number): void;
|
||||
rect(x: number, y: number, w: number, h: number): void;
|
||||
roundRect(x: number, y: number, w: number, h: number, radii?: number | number[]): void;
|
||||
measureText(text: string): TextMetrics;
|
||||
moveTo(x: number, y: number): void;
|
||||
lineTo(x: number, y: number): void;
|
||||
bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void;
|
||||
quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void;
|
||||
beginPath(): void;
|
||||
closePath(): void;
|
||||
arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void;
|
||||
arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void;
|
||||
ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void;
|
||||
setLineDash(segments: number[]): void;
|
||||
getLineDash(): number[];
|
||||
createPattern(image: Canvas|Image, repetition: 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat' | '' | null): CanvasPattern
|
||||
createLinearGradient(x0: number, y0: number, x1: number, y1: number): CanvasGradient;
|
||||
createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): CanvasGradient;
|
||||
beginTag(tagName: string, attributes?: string): void;
|
||||
endTag(tagName: string): void;
|
||||
/**
|
||||
* _Non-standard_. Defaults to 'good'. Affects pattern (gradient, image,
|
||||
* etc.) rendering quality.
|
||||
*/
|
||||
patternQuality: 'fast' | 'good' | 'best' | 'nearest' | 'bilinear'
|
||||
imageSmoothingEnabled: boolean;
|
||||
globalCompositeOperation: GlobalCompositeOperation;
|
||||
globalAlpha: number;
|
||||
shadowColor: string;
|
||||
miterLimit: number;
|
||||
lineWidth: number;
|
||||
lineCap: CanvasLineCap;
|
||||
lineJoin: CanvasLineJoin;
|
||||
lineDashOffset: number;
|
||||
shadowOffsetX: number;
|
||||
shadowOffsetY: number;
|
||||
shadowBlur: number;
|
||||
/** _Non-standard_. Sets the antialiasing mode. */
|
||||
antialias: 'default' | 'gray' | 'none' | 'subpixel'
|
||||
/**
|
||||
* Defaults to 'path'. The effect depends on the canvas type:
|
||||
*
|
||||
* * **Standard (image)** `'glyph'` and `'path'` both result in rasterized
|
||||
* text. Glyph mode is faster than path, but may result in lower-quality
|
||||
* text, especially when rotated or translated.
|
||||
*
|
||||
* * **PDF** `'glyph'` will embed text instead of paths into the PDF. This
|
||||
* is faster to encode, faster to open with PDF viewers, yields a smaller
|
||||
* file size and makes the text selectable. The subset of the font needed
|
||||
* to render the glyphs will be embedded in the PDF. This is usually the
|
||||
* mode you want to use with PDF canvases.
|
||||
*
|
||||
* * **SVG** glyph does not cause `<text>` elements to be produced as one
|
||||
* might expect ([cairo bug](https://gitlab.freedesktop.org/cairo/cairo/issues/253)).
|
||||
* Rather, glyph will create a `<defs>` section with a `<symbol>` for each
|
||||
* glyph, then those glyphs be reused via `<use>` elements. `'path'` mode
|
||||
* creates a `<path>` element for each text string. glyph mode is faster
|
||||
* and yields a smaller file size.
|
||||
*
|
||||
* In glyph mode, `ctx.strokeText()` and `ctx.fillText()` behave the same
|
||||
* (aside from using the stroke and fill style, respectively).
|
||||
*/
|
||||
textDrawingMode: 'path' | 'glyph'
|
||||
/**
|
||||
* _Non-standard_. Defaults to 'good'. Like `patternQuality`, but applies to
|
||||
* transformations affecting more than just patterns.
|
||||
*/
|
||||
quality: 'fast' | 'good' | 'best' | 'nearest' | 'bilinear'
|
||||
/** Returns or sets a `DOMMatrix` for the current transformation matrix. */
|
||||
currentTransform: DOMMatrix
|
||||
fillStyle: string | CanvasGradient | CanvasPattern;
|
||||
strokeStyle: string | CanvasGradient | CanvasPattern;
|
||||
font: string;
|
||||
textBaseline: CanvasTextBaseline;
|
||||
textAlign: CanvasTextAlign;
|
||||
canvas: Canvas;
|
||||
direction: 'ltr' | 'rtl';
|
||||
}
|
||||
|
||||
export class CanvasGradient {
|
||||
addColorStop(offset: number, color: string): void;
|
||||
}
|
||||
|
||||
export class CanvasPattern {
|
||||
setTransform(transform?: DOMMatrix): void;
|
||||
}
|
||||
|
||||
// This does not extend HTMLImageElement because there are dozens of inherited
|
||||
// methods and properties that we do not provide.
|
||||
export class Image {
|
||||
/** Track image data */
|
||||
static readonly MODE_IMAGE: number
|
||||
/** Track MIME data */
|
||||
static readonly MODE_MIME: number
|
||||
|
||||
/**
|
||||
* The URL, `data:` URI or local file path of the image to be loaded, or a
|
||||
* Buffer instance containing an encoded image.
|
||||
*/
|
||||
src: string | Buffer
|
||||
/** Retrieves whether the object is fully loaded. */
|
||||
readonly complete: boolean
|
||||
/** Sets or retrieves the height of the image. */
|
||||
height: number
|
||||
/** Sets or retrieves the width of the image. */
|
||||
width: number
|
||||
|
||||
/** The original height of the image resource before sizing. */
|
||||
readonly naturalHeight: number
|
||||
/** The original width of the image resource before sizing. */
|
||||
readonly naturalWidth: number
|
||||
/**
|
||||
* Applies to JPEG images drawn to PDF canvases only. Setting
|
||||
* `img.dataMode = Image.MODE_MIME` or `Image.MODE_MIME|Image.MODE_IMAGE`
|
||||
* enables image MIME data tracking. When MIME data is tracked, PDF canvases
|
||||
* can embed JPEGs directly into the output, rather than re-encoding into
|
||||
* PNG. This can drastically reduce filesize and speed up rendering.
|
||||
*/
|
||||
dataMode: number
|
||||
|
||||
onload: (() => void) | null;
|
||||
onerror: ((err: Error) => void) | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Canvas instance. This function works in both Node.js and Web
|
||||
* browsers, where there is no Canvas constructor.
|
||||
* @param type Optionally specify to create a PDF or SVG canvas. Defaults to an
|
||||
* image canvas.
|
||||
*/
|
||||
export function createCanvas(width: number, height: number, type?: 'pdf'|'svg'): Canvas
|
||||
|
||||
/**
|
||||
* Creates an ImageData instance. This function works in both Node.js and Web
|
||||
* browsers.
|
||||
* @param data An array containing the pixel representation of the image.
|
||||
* @param height If omitted, the height is calculated based on the array's size
|
||||
* and `width`.
|
||||
*/
|
||||
export function createImageData(data: Uint8ClampedArray, width: number, height?: number): ImageData
|
||||
/**
|
||||
* _Non-standard._ Creates an ImageData instance for an alternative pixel
|
||||
* format, such as RGB16_565
|
||||
* @param data An array containing the pixel representation of the image.
|
||||
* @param height If omitted, the height is calculated based on the array's size
|
||||
* and `width`.
|
||||
*/
|
||||
export function createImageData(data: Uint16Array, width: number, height?: number): ImageData
|
||||
/**
|
||||
* Creates an ImageData instance. This function works in both Node.js and Web
|
||||
* browsers.
|
||||
*/
|
||||
export function createImageData(width: number, height: number): ImageData
|
||||
|
||||
/**
|
||||
* Convenience function for loading an image with a Promise interface. This
|
||||
* function works in both Node.js and Web browsers; however, the `src` must be
|
||||
* a string in Web browsers (it can only be a Buffer in Node.js).
|
||||
* @param src URL, `data: ` URI or (Node.js only) a local file path or Buffer
|
||||
* instance.
|
||||
*/
|
||||
export function loadImage(src: string|Buffer, options?: any): Promise<Image>
|
||||
|
||||
/**
|
||||
* Registers a font that is not installed as a system font. This must be used
|
||||
* before creating Canvas instances.
|
||||
* @param path Path to local font file.
|
||||
* @param fontFace Description of the font face, corresponding to CSS properties
|
||||
* used in `@font-face` rules.
|
||||
*/
|
||||
export function registerFont(path: string, fontFace: {family: string, weight?: string, style?: string}): void
|
||||
|
||||
/**
|
||||
* Unloads all fonts
|
||||
*/
|
||||
export function deregisterAllFonts(): void;
|
||||
|
||||
/** This class must not be constructed directly; use `canvas.createPNGStream()`. */
|
||||
export class PNGStream extends Readable {}
|
||||
/** This class must not be constructed directly; use `canvas.createJPEGStream()`. */
|
||||
export class JPEGStream extends Readable {}
|
||||
/** This class must not be constructed directly; use `canvas.createPDFStream()`. */
|
||||
export class PDFStream extends Readable {}
|
||||
|
||||
// TODO: this is wrong. See matrixTransform in lib/DOMMatrix.js
|
||||
type DOMMatrixInit = DOMMatrix | string | number[];
|
||||
|
||||
interface DOMPointInit {
|
||||
w?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
z?: number;
|
||||
}
|
||||
|
||||
export class DOMPoint {
|
||||
w: number;
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
matrixTransform(matrix?: DOMMatrixInit): DOMPoint;
|
||||
toJSON(): any;
|
||||
static fromPoint(other?: DOMPointInit): DOMPoint;
|
||||
}
|
||||
|
||||
export class DOMMatrix {
|
||||
constructor(init?: string | number[]);
|
||||
toString(): string;
|
||||
multiply(other?: DOMMatrix): DOMMatrix;
|
||||
multiplySelf(other?: DOMMatrix): DOMMatrix;
|
||||
preMultiplySelf(other?: DOMMatrix): DOMMatrix;
|
||||
translate(tx?: number, ty?: number, tz?: number): DOMMatrix;
|
||||
translateSelf(tx?: number, ty?: number, tz?: number): DOMMatrix;
|
||||
scale(scaleX?: number, scaleY?: number, scaleZ?: number, originX?: number, originY?: number, originZ?: number): DOMMatrix;
|
||||
scale3d(scale?: number, originX?: number, originY?: number, originZ?: number): DOMMatrix;
|
||||
scale3dSelf(scale?: number, originX?: number, originY?: number, originZ?: number): DOMMatrix;
|
||||
scaleSelf(scaleX?: number, scaleY?: number, scaleZ?: number, originX?: number, originY?: number, originZ?: number): DOMMatrix;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
scaleNonUniform(scaleX?: number, scaleY?: number): DOMMatrix;
|
||||
rotateFromVector(x?: number, y?: number): DOMMatrix;
|
||||
rotateFromVectorSelf(x?: number, y?: number): DOMMatrix;
|
||||
rotate(rotX?: number, rotY?: number, rotZ?: number): DOMMatrix;
|
||||
rotateSelf(rotX?: number, rotY?: number, rotZ?: number): DOMMatrix;
|
||||
rotateAxisAngle(x?: number, y?: number, z?: number, angle?: number): DOMMatrix;
|
||||
rotateAxisAngleSelf(x?: number, y?: number, z?: number, angle?: number): DOMMatrix;
|
||||
skewX(sx?: number): DOMMatrix;
|
||||
skewXSelf(sx?: number): DOMMatrix;
|
||||
skewY(sy?: number): DOMMatrix;
|
||||
skewYSelf(sy?: number): DOMMatrix;
|
||||
flipX(): DOMMatrix;
|
||||
flipY(): DOMMatrix;
|
||||
inverse(): DOMMatrix;
|
||||
invertSelf(): DOMMatrix;
|
||||
setMatrixValue(transformList: string): DOMMatrix;
|
||||
transformPoint(point?: DOMPoint): DOMPoint;
|
||||
toJSON(): any;
|
||||
toFloat32Array(): Float32Array;
|
||||
toFloat64Array(): Float64Array;
|
||||
readonly is2D: boolean;
|
||||
readonly isIdentity: boolean;
|
||||
a: number;
|
||||
b: number;
|
||||
c: number;
|
||||
d: number;
|
||||
e: number;
|
||||
f: number;
|
||||
m11: number;
|
||||
m12: number;
|
||||
m13: number;
|
||||
m14: number;
|
||||
m21: number;
|
||||
m22: number;
|
||||
m23: number;
|
||||
m24: number;
|
||||
m31: number;
|
||||
m32: number;
|
||||
m33: number;
|
||||
m34: number;
|
||||
m41: number;
|
||||
m42: number;
|
||||
m43: number;
|
||||
m44: number;
|
||||
static fromMatrix(other: DOMMatrix): DOMMatrix;
|
||||
static fromFloat32Array(a: Float32Array): DOMMatrix;
|
||||
static fromFloat64Array(a: Float64Array): DOMMatrix;
|
||||
}
|
||||
|
||||
export class ImageData {
|
||||
constructor(sw: number, sh: number);
|
||||
constructor(data: Uint8ClampedArray, sw: number, sh?: number);
|
||||
readonly data: Uint8ClampedArray;
|
||||
readonly height: number;
|
||||
readonly width: number;
|
||||
}
|
||||
|
||||
// Not documented: backends
|
||||
|
||||
/** Library version. */
|
||||
export const version: string
|
||||
/** Cairo version. */
|
||||
export const cairoVersion: string
|
||||
/** jpeglib version, if built with JPEG support. */
|
||||
export const jpegVersion: string | undefined
|
||||
/** giflib version, if built with GIF support. */
|
||||
export const gifVersion: string | undefined
|
||||
/** freetype version. */
|
||||
export const freetypeVersion: string
|
||||
/** rsvg version. */
|
||||
export const rsvgVersion: string | undefined
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
const Canvas = require('./lib/canvas')
|
||||
const Image = require('./lib/image')
|
||||
const CanvasRenderingContext2D = require('./lib/context2d')
|
||||
const CanvasPattern = require('./lib/pattern')
|
||||
const packageJson = require('./package.json')
|
||||
const bindings = require('./lib/bindings')
|
||||
const fs = require('fs')
|
||||
const PNGStream = require('./lib/pngstream')
|
||||
const PDFStream = require('./lib/pdfstream')
|
||||
const JPEGStream = require('./lib/jpegstream')
|
||||
const { DOMPoint, DOMMatrix } = require('./lib/DOMMatrix')
|
||||
|
||||
bindings.setDOMMatrix(DOMMatrix)
|
||||
|
||||
function createCanvas (width, height, type) {
|
||||
return new Canvas(width, height, type)
|
||||
}
|
||||
|
||||
function createImageData (array, width, height) {
|
||||
return new bindings.ImageData(array, width, height)
|
||||
}
|
||||
|
||||
function loadImage (src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const image = new Image()
|
||||
|
||||
function cleanup () {
|
||||
image.onload = null
|
||||
image.onerror = null
|
||||
}
|
||||
|
||||
image.onload = () => { cleanup(); resolve(image) }
|
||||
image.onerror = (err) => { cleanup(); reject(err) }
|
||||
|
||||
image.src = src
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve paths for registerFont. Must be called *before* creating a Canvas
|
||||
* instance.
|
||||
* @param src {string} Path to font file.
|
||||
* @param fontFace {{family: string, weight?: string, style?: string}} Object
|
||||
* specifying font information. `weight` and `style` default to `"normal"`.
|
||||
*/
|
||||
function registerFont (src, fontFace) {
|
||||
// TODO this doesn't need to be on Canvas; it should just be a static method
|
||||
// of `bindings`.
|
||||
return Canvas._registerFont(fs.realpathSync(src), fontFace)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload all fonts from pango to free up memory
|
||||
*/
|
||||
function deregisterAllFonts () {
|
||||
return Canvas._deregisterAllFonts()
|
||||
}
|
||||
|
||||
exports.Canvas = Canvas
|
||||
exports.Context2d = CanvasRenderingContext2D // Legacy/compat export
|
||||
exports.CanvasRenderingContext2D = CanvasRenderingContext2D
|
||||
exports.CanvasGradient = bindings.CanvasGradient
|
||||
exports.CanvasPattern = CanvasPattern
|
||||
exports.Image = Image
|
||||
exports.ImageData = bindings.ImageData
|
||||
exports.PNGStream = PNGStream
|
||||
exports.PDFStream = PDFStream
|
||||
exports.JPEGStream = JPEGStream
|
||||
exports.DOMMatrix = DOMMatrix
|
||||
exports.DOMPoint = DOMPoint
|
||||
|
||||
exports.registerFont = registerFont
|
||||
exports.deregisterAllFonts = deregisterAllFonts
|
||||
|
||||
exports.createCanvas = createCanvas
|
||||
exports.createImageData = createImageData
|
||||
exports.loadImage = loadImage
|
||||
|
||||
exports.backends = bindings.Backends
|
||||
|
||||
/** Library version. */
|
||||
exports.version = packageJson.version
|
||||
/** Cairo version. */
|
||||
exports.cairoVersion = bindings.cairoVersion
|
||||
/** jpeglib version. */
|
||||
exports.jpegVersion = bindings.jpegVersion
|
||||
/** gif_lib version. */
|
||||
exports.gifVersion = bindings.gifVersion ? bindings.gifVersion.replace(/[^.\d]/g, '') : undefined
|
||||
/** freetype version. */
|
||||
exports.freetypeVersion = bindings.freetypeVersion
|
||||
/** rsvg version. */
|
||||
exports.rsvgVersion = bindings.rsvgVersion
|
||||
/** pango version. */
|
||||
exports.pangoVersion = bindings.pangoVersion
|
||||
+678
@@ -0,0 +1,678 @@
|
||||
'use strict'
|
||||
|
||||
const util = require('util')
|
||||
|
||||
// DOMMatrix per https://drafts.fxtf.org/geometry/#DOMMatrix
|
||||
|
||||
class DOMPoint {
|
||||
constructor (x, y, z, w) {
|
||||
if (typeof x === 'object' && x !== null) {
|
||||
w = x.w
|
||||
z = x.z
|
||||
y = x.y
|
||||
x = x.x
|
||||
}
|
||||
this.x = typeof x === 'number' ? x : 0
|
||||
this.y = typeof y === 'number' ? y : 0
|
||||
this.z = typeof z === 'number' ? z : 0
|
||||
this.w = typeof w === 'number' ? w : 1
|
||||
}
|
||||
|
||||
matrixTransform(init) {
|
||||
// TODO: this next line is wrong. matrixTransform is supposed to only take
|
||||
// an object with the DOMMatrix properties called DOMMatrixInit
|
||||
const m = init instanceof DOMMatrix ? init : new DOMMatrix(init)
|
||||
return m.transformPoint(this)
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
z: this.z,
|
||||
w: this.w
|
||||
}
|
||||
}
|
||||
|
||||
static fromPoint(other) {
|
||||
return new this(other.x, other.y, other.z, other.w)
|
||||
}
|
||||
}
|
||||
|
||||
// Constants to index into _values (col-major)
|
||||
const M11 = 0; const M12 = 1; const M13 = 2; const M14 = 3
|
||||
const M21 = 4; const M22 = 5; const M23 = 6; const M24 = 7
|
||||
const M31 = 8; const M32 = 9; const M33 = 10; const M34 = 11
|
||||
const M41 = 12; const M42 = 13; const M43 = 14; const M44 = 15
|
||||
|
||||
const DEGREE_PER_RAD = 180 / Math.PI
|
||||
const RAD_PER_DEGREE = Math.PI / 180
|
||||
|
||||
function parseMatrix (init) {
|
||||
let parsed = init.replace('matrix(', '')
|
||||
parsed = parsed.split(',', 7) // 6 + 1 to handle too many params
|
||||
if (parsed.length !== 6) throw new Error(`Failed to parse ${init}`)
|
||||
parsed = parsed.map(parseFloat)
|
||||
return [
|
||||
parsed[0], parsed[1], 0, 0,
|
||||
parsed[2], parsed[3], 0, 0,
|
||||
0, 0, 1, 0,
|
||||
parsed[4], parsed[5], 0, 1
|
||||
]
|
||||
}
|
||||
|
||||
function parseMatrix3d (init) {
|
||||
let parsed = init.replace('matrix3d(', '')
|
||||
parsed = parsed.split(',', 17) // 16 + 1 to handle too many params
|
||||
if (parsed.length !== 16) throw new Error(`Failed to parse ${init}`)
|
||||
return parsed.map(parseFloat)
|
||||
}
|
||||
|
||||
function parseTransform (tform) {
|
||||
const type = tform.split('(', 1)[0]
|
||||
switch (type) {
|
||||
case 'matrix':
|
||||
return parseMatrix(tform)
|
||||
case 'matrix3d':
|
||||
return parseMatrix3d(tform)
|
||||
// TODO This is supposed to support any CSS transform value.
|
||||
default:
|
||||
throw new Error(`${type} parsing not implemented`)
|
||||
}
|
||||
}
|
||||
|
||||
class DOMMatrix {
|
||||
constructor (init) {
|
||||
this._is2D = true
|
||||
this._values = new Float64Array([
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
])
|
||||
|
||||
let i
|
||||
|
||||
if (typeof init === 'string') { // parse CSS transformList
|
||||
if (init === '') return // default identity matrix
|
||||
const tforms = init.split(/\)\s+/, 20).map(parseTransform)
|
||||
if (tforms.length === 0) return
|
||||
init = tforms[0]
|
||||
for (i = 1; i < tforms.length; i++) init = multiply(tforms[i], init)
|
||||
}
|
||||
|
||||
i = 0
|
||||
if (init && init.length === 6) {
|
||||
setNumber2D(this, M11, init[i++])
|
||||
setNumber2D(this, M12, init[i++])
|
||||
setNumber2D(this, M21, init[i++])
|
||||
setNumber2D(this, M22, init[i++])
|
||||
setNumber2D(this, M41, init[i++])
|
||||
setNumber2D(this, M42, init[i++])
|
||||
} else if (init && init.length === 16) {
|
||||
setNumber2D(this, M11, init[i++])
|
||||
setNumber2D(this, M12, init[i++])
|
||||
setNumber3D(this, M13, init[i++])
|
||||
setNumber3D(this, M14, init[i++])
|
||||
setNumber2D(this, M21, init[i++])
|
||||
setNumber2D(this, M22, init[i++])
|
||||
setNumber3D(this, M23, init[i++])
|
||||
setNumber3D(this, M24, init[i++])
|
||||
setNumber3D(this, M31, init[i++])
|
||||
setNumber3D(this, M32, init[i++])
|
||||
setNumber3D(this, M33, init[i++])
|
||||
setNumber3D(this, M34, init[i++])
|
||||
setNumber2D(this, M41, init[i++])
|
||||
setNumber2D(this, M42, init[i++])
|
||||
setNumber3D(this, M43, init[i++])
|
||||
setNumber3D(this, M44, init[i])
|
||||
} else if (init !== undefined) {
|
||||
throw new TypeError('Expected string or array.')
|
||||
}
|
||||
}
|
||||
|
||||
toString () {
|
||||
return this.is2D
|
||||
? `matrix(${this.a}, ${this.b}, ${this.c}, ${this.d}, ${this.e}, ${this.f})`
|
||||
: `matrix3d(${this._values.join(', ')})`
|
||||
}
|
||||
|
||||
multiply (other) {
|
||||
return newInstance(this._values).multiplySelf(other)
|
||||
}
|
||||
|
||||
multiplySelf (other) {
|
||||
this._values = multiply(other._values, this._values)
|
||||
if (!other.is2D) this._is2D = false
|
||||
return this
|
||||
}
|
||||
|
||||
preMultiplySelf (other) {
|
||||
this._values = multiply(this._values, other._values)
|
||||
if (!other.is2D) this._is2D = false
|
||||
return this
|
||||
}
|
||||
|
||||
translate (tx, ty, tz) {
|
||||
return newInstance(this._values).translateSelf(tx, ty, tz)
|
||||
}
|
||||
|
||||
translateSelf (tx, ty, tz) {
|
||||
if (typeof tx !== 'number') tx = 0
|
||||
if (typeof ty !== 'number') ty = 0
|
||||
if (typeof tz !== 'number') tz = 0
|
||||
this._values = multiply([
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
tx, ty, tz, 1
|
||||
], this._values)
|
||||
if (tz !== 0) this._is2D = false
|
||||
return this
|
||||
}
|
||||
|
||||
scale (scaleX, scaleY, scaleZ, originX, originY, originZ) {
|
||||
return newInstance(this._values).scaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ)
|
||||
}
|
||||
|
||||
scale3d (scale, originX, originY, originZ) {
|
||||
return newInstance(this._values).scale3dSelf(scale, originX, originY, originZ)
|
||||
}
|
||||
|
||||
scale3dSelf (scale, originX, originY, originZ) {
|
||||
return this.scaleSelf(scale, scale, scale, originX, originY, originZ)
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
scaleNonUniform(scaleX, scaleY) {
|
||||
return this.scale(scaleX, scaleY)
|
||||
}
|
||||
|
||||
scaleSelf (scaleX, scaleY, scaleZ, originX, originY, originZ) {
|
||||
// Not redundant with translate's checks because we need to negate the values later.
|
||||
if (typeof originX !== 'number') originX = 0
|
||||
if (typeof originY !== 'number') originY = 0
|
||||
if (typeof originZ !== 'number') originZ = 0
|
||||
this.translateSelf(originX, originY, originZ)
|
||||
if (typeof scaleX !== 'number') scaleX = 1
|
||||
if (typeof scaleY !== 'number') scaleY = scaleX
|
||||
if (typeof scaleZ !== 'number') scaleZ = 1
|
||||
this._values = multiply([
|
||||
scaleX, 0, 0, 0,
|
||||
0, scaleY, 0, 0,
|
||||
0, 0, scaleZ, 0,
|
||||
0, 0, 0, 1
|
||||
], this._values)
|
||||
this.translateSelf(-originX, -originY, -originZ)
|
||||
if (scaleZ !== 1 || originZ !== 0) this._is2D = false
|
||||
return this
|
||||
}
|
||||
|
||||
rotateFromVector (x, y) {
|
||||
return newInstance(this._values).rotateFromVectorSelf(x, y)
|
||||
}
|
||||
|
||||
rotateFromVectorSelf (x, y) {
|
||||
if (typeof x !== 'number') x = 0
|
||||
if (typeof y !== 'number') y = 0
|
||||
const theta = (x === 0 && y === 0) ? 0 : Math.atan2(y, x) * DEGREE_PER_RAD
|
||||
return this.rotateSelf(theta)
|
||||
}
|
||||
|
||||
rotate (rotX, rotY, rotZ) {
|
||||
return newInstance(this._values).rotateSelf(rotX, rotY, rotZ)
|
||||
}
|
||||
|
||||
rotateSelf (rotX, rotY, rotZ) {
|
||||
if (rotY === undefined && rotZ === undefined) {
|
||||
rotZ = rotX
|
||||
rotX = rotY = 0
|
||||
}
|
||||
if (typeof rotY !== 'number') rotY = 0
|
||||
if (typeof rotZ !== 'number') rotZ = 0
|
||||
if (rotX !== 0 || rotY !== 0) this._is2D = false
|
||||
rotX *= RAD_PER_DEGREE
|
||||
rotY *= RAD_PER_DEGREE
|
||||
rotZ *= RAD_PER_DEGREE
|
||||
let c, s
|
||||
c = Math.cos(rotZ)
|
||||
s = Math.sin(rotZ)
|
||||
this._values = multiply([
|
||||
c, s, 0, 0,
|
||||
-s, c, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
], this._values)
|
||||
c = Math.cos(rotY)
|
||||
s = Math.sin(rotY)
|
||||
this._values = multiply([
|
||||
c, 0, -s, 0,
|
||||
0, 1, 0, 0,
|
||||
s, 0, c, 0,
|
||||
0, 0, 0, 1
|
||||
], this._values)
|
||||
c = Math.cos(rotX)
|
||||
s = Math.sin(rotX)
|
||||
this._values = multiply([
|
||||
1, 0, 0, 0,
|
||||
0, c, s, 0,
|
||||
0, -s, c, 0,
|
||||
0, 0, 0, 1
|
||||
], this._values)
|
||||
return this
|
||||
}
|
||||
|
||||
rotateAxisAngle (x, y, z, angle) {
|
||||
return newInstance(this._values).rotateAxisAngleSelf(x, y, z, angle)
|
||||
}
|
||||
|
||||
rotateAxisAngleSelf (x, y, z, angle) {
|
||||
if (typeof x !== 'number') x = 0
|
||||
if (typeof y !== 'number') y = 0
|
||||
if (typeof z !== 'number') z = 0
|
||||
// Normalize axis
|
||||
const length = Math.sqrt(x * x + y * y + z * z)
|
||||
if (length === 0) return this
|
||||
if (length !== 1) {
|
||||
x /= length
|
||||
y /= length
|
||||
z /= length
|
||||
}
|
||||
angle *= RAD_PER_DEGREE
|
||||
const c = Math.cos(angle)
|
||||
const s = Math.sin(angle)
|
||||
const t = 1 - c
|
||||
const tx = t * x
|
||||
const ty = t * y
|
||||
// NB: This is the generic transform. If the axis is a major axis, there are
|
||||
// faster transforms.
|
||||
this._values = multiply([
|
||||
tx * x + c, tx * y + s * z, tx * z - s * y, 0,
|
||||
tx * y - s * z, ty * y + c, ty * z + s * x, 0,
|
||||
tx * z + s * y, ty * z - s * x, t * z * z + c, 0,
|
||||
0, 0, 0, 1
|
||||
], this._values)
|
||||
if (x !== 0 || y !== 0) this._is2D = false
|
||||
return this
|
||||
}
|
||||
|
||||
skewX (sx) {
|
||||
return newInstance(this._values).skewXSelf(sx)
|
||||
}
|
||||
|
||||
skewXSelf (sx) {
|
||||
if (typeof sx !== 'number') return this
|
||||
const t = Math.tan(sx * RAD_PER_DEGREE)
|
||||
this._values = multiply([
|
||||
1, 0, 0, 0,
|
||||
t, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
], this._values)
|
||||
return this
|
||||
}
|
||||
|
||||
skewY (sy) {
|
||||
return newInstance(this._values).skewYSelf(sy)
|
||||
}
|
||||
|
||||
skewYSelf (sy) {
|
||||
if (typeof sy !== 'number') return this
|
||||
const t = Math.tan(sy * RAD_PER_DEGREE)
|
||||
this._values = multiply([
|
||||
1, t, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
], this._values)
|
||||
return this
|
||||
}
|
||||
|
||||
flipX () {
|
||||
return newInstance(multiply([
|
||||
-1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
], this._values))
|
||||
}
|
||||
|
||||
flipY () {
|
||||
return newInstance(multiply([
|
||||
1, 0, 0, 0,
|
||||
0, -1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
], this._values))
|
||||
}
|
||||
|
||||
inverse () {
|
||||
return newInstance(this._values).invertSelf()
|
||||
}
|
||||
|
||||
invertSelf () {
|
||||
const m = this._values
|
||||
const inv = m.map(v => 0)
|
||||
|
||||
inv[0] = m[5] * m[10] * m[15] -
|
||||
m[5] * m[11] * m[14] -
|
||||
m[9] * m[6] * m[15] +
|
||||
m[9] * m[7] * m[14] +
|
||||
m[13] * m[6] * m[11] -
|
||||
m[13] * m[7] * m[10]
|
||||
|
||||
inv[4] = -m[4] * m[10] * m[15] +
|
||||
m[4] * m[11] * m[14] +
|
||||
m[8] * m[6] * m[15] -
|
||||
m[8] * m[7] * m[14] -
|
||||
m[12] * m[6] * m[11] +
|
||||
m[12] * m[7] * m[10]
|
||||
|
||||
inv[8] = m[4] * m[9] * m[15] -
|
||||
m[4] * m[11] * m[13] -
|
||||
m[8] * m[5] * m[15] +
|
||||
m[8] * m[7] * m[13] +
|
||||
m[12] * m[5] * m[11] -
|
||||
m[12] * m[7] * m[9]
|
||||
|
||||
inv[12] = -m[4] * m[9] * m[14] +
|
||||
m[4] * m[10] * m[13] +
|
||||
m[8] * m[5] * m[14] -
|
||||
m[8] * m[6] * m[13] -
|
||||
m[12] * m[5] * m[10] +
|
||||
m[12] * m[6] * m[9]
|
||||
|
||||
// If the determinant is zero, this matrix cannot be inverted, and all
|
||||
// values should be set to NaN, with the is2D flag set to false.
|
||||
|
||||
const det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]
|
||||
|
||||
if (det === 0) {
|
||||
this._values = m.map(v => NaN)
|
||||
this._is2D = false
|
||||
return this
|
||||
}
|
||||
|
||||
inv[1] = -m[1] * m[10] * m[15] +
|
||||
m[1] * m[11] * m[14] +
|
||||
m[9] * m[2] * m[15] -
|
||||
m[9] * m[3] * m[14] -
|
||||
m[13] * m[2] * m[11] +
|
||||
m[13] * m[3] * m[10]
|
||||
|
||||
inv[5] = m[0] * m[10] * m[15] -
|
||||
m[0] * m[11] * m[14] -
|
||||
m[8] * m[2] * m[15] +
|
||||
m[8] * m[3] * m[14] +
|
||||
m[12] * m[2] * m[11] -
|
||||
m[12] * m[3] * m[10]
|
||||
|
||||
inv[9] = -m[0] * m[9] * m[15] +
|
||||
m[0] * m[11] * m[13] +
|
||||
m[8] * m[1] * m[15] -
|
||||
m[8] * m[3] * m[13] -
|
||||
m[12] * m[1] * m[11] +
|
||||
m[12] * m[3] * m[9]
|
||||
|
||||
inv[13] = m[0] * m[9] * m[14] -
|
||||
m[0] * m[10] * m[13] -
|
||||
m[8] * m[1] * m[14] +
|
||||
m[8] * m[2] * m[13] +
|
||||
m[12] * m[1] * m[10] -
|
||||
m[12] * m[2] * m[9]
|
||||
|
||||
inv[2] = m[1] * m[6] * m[15] -
|
||||
m[1] * m[7] * m[14] -
|
||||
m[5] * m[2] * m[15] +
|
||||
m[5] * m[3] * m[14] +
|
||||
m[13] * m[2] * m[7] -
|
||||
m[13] * m[3] * m[6]
|
||||
|
||||
inv[6] = -m[0] * m[6] * m[15] +
|
||||
m[0] * m[7] * m[14] +
|
||||
m[4] * m[2] * m[15] -
|
||||
m[4] * m[3] * m[14] -
|
||||
m[12] * m[2] * m[7] +
|
||||
m[12] * m[3] * m[6]
|
||||
|
||||
inv[10] = m[0] * m[5] * m[15] -
|
||||
m[0] * m[7] * m[13] -
|
||||
m[4] * m[1] * m[15] +
|
||||
m[4] * m[3] * m[13] +
|
||||
m[12] * m[1] * m[7] -
|
||||
m[12] * m[3] * m[5]
|
||||
|
||||
inv[14] = -m[0] * m[5] * m[14] +
|
||||
m[0] * m[6] * m[13] +
|
||||
m[4] * m[1] * m[14] -
|
||||
m[4] * m[2] * m[13] -
|
||||
m[12] * m[1] * m[6] +
|
||||
m[12] * m[2] * m[5]
|
||||
|
||||
inv[3] = -m[1] * m[6] * m[11] +
|
||||
m[1] * m[7] * m[10] +
|
||||
m[5] * m[2] * m[11] -
|
||||
m[5] * m[3] * m[10] -
|
||||
m[9] * m[2] * m[7] +
|
||||
m[9] * m[3] * m[6]
|
||||
|
||||
inv[7] = m[0] * m[6] * m[11] -
|
||||
m[0] * m[7] * m[10] -
|
||||
m[4] * m[2] * m[11] +
|
||||
m[4] * m[3] * m[10] +
|
||||
m[8] * m[2] * m[7] -
|
||||
m[8] * m[3] * m[6]
|
||||
|
||||
inv[11] = -m[0] * m[5] * m[11] +
|
||||
m[0] * m[7] * m[9] +
|
||||
m[4] * m[1] * m[11] -
|
||||
m[4] * m[3] * m[9] -
|
||||
m[8] * m[1] * m[7] +
|
||||
m[8] * m[3] * m[5]
|
||||
|
||||
inv[15] = m[0] * m[5] * m[10] -
|
||||
m[0] * m[6] * m[9] -
|
||||
m[4] * m[1] * m[10] +
|
||||
m[4] * m[2] * m[9] +
|
||||
m[8] * m[1] * m[6] -
|
||||
m[8] * m[2] * m[5]
|
||||
|
||||
inv.forEach((v, i) => { inv[i] = v / det })
|
||||
this._values = inv
|
||||
return this
|
||||
}
|
||||
|
||||
setMatrixValue (transformList) {
|
||||
const temp = new DOMMatrix(transformList)
|
||||
this._values = temp._values
|
||||
this._is2D = temp._is2D
|
||||
return this
|
||||
}
|
||||
|
||||
transformPoint (point) {
|
||||
point = new DOMPoint(point)
|
||||
const x = point.x
|
||||
const y = point.y
|
||||
const z = point.z
|
||||
const w = point.w
|
||||
const values = this._values
|
||||
const nx = values[M11] * x + values[M21] * y + values[M31] * z + values[M41] * w
|
||||
const ny = values[M12] * x + values[M22] * y + values[M32] * z + values[M42] * w
|
||||
const nz = values[M13] * x + values[M23] * y + values[M33] * z + values[M43] * w
|
||||
const nw = values[M14] * x + values[M24] * y + values[M34] * z + values[M44] * w
|
||||
return new DOMPoint(nx, ny, nz, nw)
|
||||
}
|
||||
|
||||
toFloat32Array () {
|
||||
return Float32Array.from(this._values)
|
||||
}
|
||||
|
||||
toFloat64Array () {
|
||||
return this._values.slice(0)
|
||||
}
|
||||
|
||||
static fromMatrix (init) {
|
||||
if (!(init instanceof DOMMatrix)) throw new TypeError('Expected DOMMatrix')
|
||||
return new DOMMatrix(init._values)
|
||||
}
|
||||
|
||||
static fromFloat32Array (init) {
|
||||
if (!(init instanceof Float32Array)) throw new TypeError('Expected Float32Array')
|
||||
return new DOMMatrix(init)
|
||||
}
|
||||
|
||||
static fromFloat64Array (init) {
|
||||
if (!(init instanceof Float64Array)) throw new TypeError('Expected Float64Array')
|
||||
return new DOMMatrix(init)
|
||||
}
|
||||
|
||||
[util.inspect.custom || 'inspect'] (depth, options) {
|
||||
if (depth < 0) return '[DOMMatrix]'
|
||||
|
||||
return `DOMMatrix [
|
||||
a: ${this.a}
|
||||
b: ${this.b}
|
||||
c: ${this.c}
|
||||
d: ${this.d}
|
||||
e: ${this.e}
|
||||
f: ${this.f}
|
||||
m11: ${this.m11}
|
||||
m12: ${this.m12}
|
||||
m13: ${this.m13}
|
||||
m14: ${this.m14}
|
||||
m21: ${this.m21}
|
||||
m22: ${this.m22}
|
||||
m23: ${this.m23}
|
||||
m23: ${this.m23}
|
||||
m31: ${this.m31}
|
||||
m32: ${this.m32}
|
||||
m33: ${this.m33}
|
||||
m34: ${this.m34}
|
||||
m41: ${this.m41}
|
||||
m42: ${this.m42}
|
||||
m43: ${this.m43}
|
||||
m44: ${this.m44}
|
||||
is2D: ${this.is2D}
|
||||
isIdentity: ${this.isIdentity} ]`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that `value` is a number and sets the value.
|
||||
*/
|
||||
function setNumber2D (receiver, index, value) {
|
||||
if (typeof value !== 'number') throw new TypeError('Expected number')
|
||||
return (receiver._values[index] = value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that `value` is a number, sets `_is2D = false` if necessary and sets
|
||||
* the value.
|
||||
*/
|
||||
function setNumber3D (receiver, index, value) {
|
||||
if (typeof value !== 'number') throw new TypeError('Expected number')
|
||||
if (index === M33 || index === M44) {
|
||||
if (value !== 1) receiver._is2D = false
|
||||
} else if (value !== 0) receiver._is2D = false
|
||||
return (receiver._values[index] = value)
|
||||
}
|
||||
|
||||
Object.defineProperties(DOMMatrix.prototype, {
|
||||
m11: { get () { return this._values[M11] }, set (v) { return setNumber2D(this, M11, v) } },
|
||||
m12: { get () { return this._values[M12] }, set (v) { return setNumber2D(this, M12, v) } },
|
||||
m13: { get () { return this._values[M13] }, set (v) { return setNumber3D(this, M13, v) } },
|
||||
m14: { get () { return this._values[M14] }, set (v) { return setNumber3D(this, M14, v) } },
|
||||
m21: { get () { return this._values[M21] }, set (v) { return setNumber2D(this, M21, v) } },
|
||||
m22: { get () { return this._values[M22] }, set (v) { return setNumber2D(this, M22, v) } },
|
||||
m23: { get () { return this._values[M23] }, set (v) { return setNumber3D(this, M23, v) } },
|
||||
m24: { get () { return this._values[M24] }, set (v) { return setNumber3D(this, M24, v) } },
|
||||
m31: { get () { return this._values[M31] }, set (v) { return setNumber3D(this, M31, v) } },
|
||||
m32: { get () { return this._values[M32] }, set (v) { return setNumber3D(this, M32, v) } },
|
||||
m33: { get () { return this._values[M33] }, set (v) { return setNumber3D(this, M33, v) } },
|
||||
m34: { get () { return this._values[M34] }, set (v) { return setNumber3D(this, M34, v) } },
|
||||
m41: { get () { return this._values[M41] }, set (v) { return setNumber2D(this, M41, v) } },
|
||||
m42: { get () { return this._values[M42] }, set (v) { return setNumber2D(this, M42, v) } },
|
||||
m43: { get () { return this._values[M43] }, set (v) { return setNumber3D(this, M43, v) } },
|
||||
m44: { get () { return this._values[M44] }, set (v) { return setNumber3D(this, M44, v) } },
|
||||
|
||||
a: { get () { return this.m11 }, set (v) { return (this.m11 = v) } },
|
||||
b: { get () { return this.m12 }, set (v) { return (this.m12 = v) } },
|
||||
c: { get () { return this.m21 }, set (v) { return (this.m21 = v) } },
|
||||
d: { get () { return this.m22 }, set (v) { return (this.m22 = v) } },
|
||||
e: { get () { return this.m41 }, set (v) { return (this.m41 = v) } },
|
||||
f: { get () { return this.m42 }, set (v) { return (this.m42 = v) } },
|
||||
|
||||
is2D: { get () { return this._is2D } }, // read-only
|
||||
|
||||
isIdentity: {
|
||||
get () {
|
||||
const values = this._values
|
||||
return (values[M11] === 1 && values[M12] === 0 && values[M13] === 0 && values[M14] === 0 &&
|
||||
values[M21] === 0 && values[M22] === 1 && values[M23] === 0 && values[M24] === 0 &&
|
||||
values[M31] === 0 && values[M32] === 0 && values[M33] === 1 && values[M34] === 0 &&
|
||||
values[M41] === 0 && values[M42] === 0 && values[M43] === 0 && values[M44] === 1)
|
||||
}
|
||||
},
|
||||
|
||||
toJSON: {
|
||||
value() {
|
||||
return {
|
||||
a: this.a,
|
||||
b: this.b,
|
||||
c: this.c,
|
||||
d: this.d,
|
||||
e: this.e,
|
||||
f: this.f,
|
||||
m11: this.m11,
|
||||
m12: this.m12,
|
||||
m13: this.m13,
|
||||
m14: this.m14,
|
||||
m21: this.m21,
|
||||
m22: this.m22,
|
||||
m23: this.m23,
|
||||
m23: this.m23,
|
||||
m31: this.m31,
|
||||
m32: this.m32,
|
||||
m33: this.m33,
|
||||
m34: this.m34,
|
||||
m41: this.m41,
|
||||
m42: this.m42,
|
||||
m43: this.m43,
|
||||
m44: this.m44,
|
||||
is2D: this.is2D,
|
||||
isIdentity: this.isIdentity,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Instantiates a DOMMatrix, bypassing the constructor.
|
||||
* @param {Float64Array} values Value to assign to `_values`. This is assigned
|
||||
* without copying (okay because all usages are followed by a multiply).
|
||||
*/
|
||||
function newInstance (values) {
|
||||
const instance = Object.create(DOMMatrix.prototype)
|
||||
instance.constructor = DOMMatrix
|
||||
instance._is2D = true
|
||||
instance._values = values
|
||||
return instance
|
||||
}
|
||||
|
||||
function multiply (A, B) {
|
||||
const dest = new Float64Array(16)
|
||||
for (let i = 0; i < 4; i++) {
|
||||
for (let j = 0; j < 4; j++) {
|
||||
let sum = 0
|
||||
for (let k = 0; k < 4; k++) {
|
||||
sum += A[i * 4 + k] * B[k * 4 + j]
|
||||
}
|
||||
dest[i * 4 + j] = sum
|
||||
}
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
module.exports = { DOMMatrix, DOMPoint }
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
'use strict'
|
||||
|
||||
const bindings = require('../build/Release/canvas.node')
|
||||
|
||||
module.exports = bindings
|
||||
|
||||
Object.defineProperty(bindings.Canvas.prototype, Symbol.toStringTag, {
|
||||
value: 'HTMLCanvasElement',
|
||||
configurable: true
|
||||
})
|
||||
|
||||
Object.defineProperty(bindings.Image.prototype, Symbol.toStringTag, {
|
||||
value: 'HTMLImageElement',
|
||||
configurable: true
|
||||
})
|
||||
|
||||
bindings.ImageData.prototype.toString = function () {
|
||||
return '[object ImageData]'
|
||||
}
|
||||
|
||||
Object.defineProperty(bindings.ImageData.prototype, Symbol.toStringTag, {
|
||||
value: 'ImageData',
|
||||
configurable: true
|
||||
})
|
||||
|
||||
bindings.CanvasGradient.prototype.toString = function () {
|
||||
return '[object CanvasGradient]'
|
||||
}
|
||||
|
||||
Object.defineProperty(bindings.CanvasGradient.prototype, Symbol.toStringTag, {
|
||||
value: 'CanvasGradient',
|
||||
configurable: true
|
||||
})
|
||||
|
||||
Object.defineProperty(bindings.CanvasPattern.prototype, Symbol.toStringTag, {
|
||||
value: 'CanvasPattern',
|
||||
configurable: true
|
||||
})
|
||||
|
||||
Object.defineProperty(bindings.CanvasRenderingContext2d.prototype, Symbol.toStringTag, {
|
||||
value: 'CanvasRenderingContext2d',
|
||||
configurable: true
|
||||
})
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
'use strict'
|
||||
|
||||
/*!
|
||||
* Canvas
|
||||
* Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
const bindings = require('./bindings')
|
||||
const Canvas = module.exports = bindings.Canvas
|
||||
const Context2d = require('./context2d')
|
||||
const PNGStream = require('./pngstream')
|
||||
const PDFStream = require('./pdfstream')
|
||||
const JPEGStream = require('./jpegstream')
|
||||
const FORMATS = ['image/png', 'image/jpeg']
|
||||
const util = require('util')
|
||||
|
||||
// TODO || is for Node.js pre-v6.6.0
|
||||
Canvas.prototype[util.inspect.custom || 'inspect'] = function () {
|
||||
return `[Canvas ${this.width}x${this.height}]`
|
||||
}
|
||||
|
||||
Canvas.prototype.getContext = function (contextType, contextAttributes) {
|
||||
if (contextType == '2d') {
|
||||
const ctx = this._context2d || (this._context2d = new Context2d(this, contextAttributes))
|
||||
this.context = ctx
|
||||
ctx.canvas = this
|
||||
return ctx
|
||||
}
|
||||
}
|
||||
|
||||
Canvas.prototype.pngStream =
|
||||
Canvas.prototype.createPNGStream = function (options) {
|
||||
return new PNGStream(this, options)
|
||||
}
|
||||
|
||||
Canvas.prototype.pdfStream =
|
||||
Canvas.prototype.createPDFStream = function (options) {
|
||||
return new PDFStream(this, options)
|
||||
}
|
||||
|
||||
Canvas.prototype.jpegStream =
|
||||
Canvas.prototype.createJPEGStream = function (options) {
|
||||
return new JPEGStream(this, options)
|
||||
}
|
||||
|
||||
Canvas.prototype.toDataURL = function (a1, a2, a3) {
|
||||
// valid arg patterns (args -> [type, opts, fn]):
|
||||
// [] -> ['image/png', null, null]
|
||||
// [qual] -> ['image/png', null, null]
|
||||
// [undefined] -> ['image/png', null, null]
|
||||
// ['image/png'] -> ['image/png', null, null]
|
||||
// ['image/png', qual] -> ['image/png', null, null]
|
||||
// [fn] -> ['image/png', null, fn]
|
||||
// [type, fn] -> [type, null, fn]
|
||||
// [undefined, fn] -> ['image/png', null, fn]
|
||||
// ['image/png', qual, fn] -> ['image/png', null, fn]
|
||||
// ['image/jpeg', fn] -> ['image/jpeg', null, fn]
|
||||
// ['image/jpeg', opts, fn] -> ['image/jpeg', opts, fn]
|
||||
// ['image/jpeg', qual, fn] -> ['image/jpeg', {quality: qual}, fn]
|
||||
// ['image/jpeg', undefined, fn] -> ['image/jpeg', null, fn]
|
||||
// ['image/jpeg'] -> ['image/jpeg', null, fn]
|
||||
// ['image/jpeg', opts] -> ['image/jpeg', opts, fn]
|
||||
// ['image/jpeg', qual] -> ['image/jpeg', {quality: qual}, fn]
|
||||
|
||||
let type = 'image/png'
|
||||
let opts = {}
|
||||
let fn
|
||||
|
||||
if (typeof a1 === 'function') {
|
||||
fn = a1
|
||||
} else {
|
||||
if (typeof a1 === 'string' && FORMATS.includes(a1.toLowerCase())) {
|
||||
type = a1.toLowerCase()
|
||||
}
|
||||
|
||||
if (typeof a2 === 'function') {
|
||||
fn = a2
|
||||
} else {
|
||||
if (typeof a2 === 'object') {
|
||||
opts = a2
|
||||
} else if (typeof a2 === 'number') {
|
||||
opts = { quality: Math.max(0, Math.min(1, a2)) }
|
||||
}
|
||||
|
||||
if (typeof a3 === 'function') {
|
||||
fn = a3
|
||||
} else if (undefined !== a3) {
|
||||
throw new TypeError(`${typeof a3} is not a function`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.width === 0 || this.height === 0) {
|
||||
// Per spec, if the bitmap has no pixels, return this string:
|
||||
const str = 'data:,'
|
||||
if (fn) {
|
||||
setTimeout(() => fn(null, str))
|
||||
return
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
if (fn) {
|
||||
this.toBuffer((err, buf) => {
|
||||
if (err) return fn(err)
|
||||
fn(null, `data:${type};base64,${buf.toString('base64')}`)
|
||||
}, type, opts)
|
||||
} else {
|
||||
return `data:${type};base64,${this.toBuffer(type, opts).toString('base64')}`
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
/*!
|
||||
* Canvas - Context2d
|
||||
* Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
const bindings = require('./bindings')
|
||||
|
||||
module.exports = bindings.CanvasRenderingContext2d
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
'use strict'
|
||||
|
||||
/*!
|
||||
* Canvas - Image
|
||||
* Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const bindings = require('./bindings')
|
||||
const Image = module.exports = bindings.Image
|
||||
const util = require('util')
|
||||
|
||||
const { GetSource, SetSource } = bindings
|
||||
|
||||
Object.defineProperty(Image.prototype, 'src', {
|
||||
/**
|
||||
* src setter. Valid values:
|
||||
* * `data:` URI
|
||||
* * Local file path
|
||||
* * HTTP or HTTPS URL
|
||||
* * Buffer containing image data (i.e. not a `data:` URI stored in a Buffer)
|
||||
*
|
||||
* @param {String|Buffer} val filename, buffer, data URI, URL
|
||||
* @api public
|
||||
*/
|
||||
set (val) {
|
||||
if (typeof val === 'string') {
|
||||
if (/^\s*data:/.test(val)) { // data: URI
|
||||
const commaI = val.indexOf(',')
|
||||
// 'base64' must come before the comma
|
||||
const isBase64 = val.lastIndexOf('base64', commaI) !== -1
|
||||
const content = val.slice(commaI + 1)
|
||||
setSource(this, Buffer.from(content, isBase64 ? 'base64' : 'utf8'), val)
|
||||
} else if (/^\s*https?:\/\//.test(val)) { // remote URL
|
||||
const onerror = err => {
|
||||
if (typeof this.onerror === 'function') {
|
||||
this.onerror(err)
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
fetch(val, {
|
||||
method: 'GET',
|
||||
headers: { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36' }
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`Server responded with ${res.statusCode}`)
|
||||
}
|
||||
return res.arrayBuffer()
|
||||
})
|
||||
.then(data => {
|
||||
setSource(this, Buffer.from(data))
|
||||
})
|
||||
.catch(onerror)
|
||||
} else { // local file path assumed
|
||||
setSource(this, val)
|
||||
}
|
||||
} else if (Buffer.isBuffer(val)) {
|
||||
setSource(this, val)
|
||||
}
|
||||
},
|
||||
|
||||
get () {
|
||||
// TODO https://github.com/Automattic/node-canvas/issues/118
|
||||
return getSource(this)
|
||||
},
|
||||
|
||||
configurable: true
|
||||
})
|
||||
|
||||
// TODO || is for Node.js pre-v6.6.0
|
||||
Image.prototype[util.inspect.custom || 'inspect'] = function () {
|
||||
return '[Image' +
|
||||
(this.complete ? ':' + this.width + 'x' + this.height : '') +
|
||||
(this.src ? ' ' + this.src : '') +
|
||||
(this.complete ? ' complete' : '') +
|
||||
']'
|
||||
}
|
||||
|
||||
function getSource (img) {
|
||||
return img._originalSource || GetSource.call(img)
|
||||
}
|
||||
|
||||
function setSource (img, src, origSrc) {
|
||||
SetSource.call(img, src)
|
||||
img._originalSource = origSrc
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
'use strict'
|
||||
|
||||
/*!
|
||||
* Canvas - JPEGStream
|
||||
* Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
const { Readable } = require('stream')
|
||||
function noop () {}
|
||||
|
||||
class JPEGStream extends Readable {
|
||||
constructor (canvas, options) {
|
||||
super()
|
||||
|
||||
if (canvas.streamJPEGSync === undefined) {
|
||||
throw new Error('node-canvas was built without JPEG support.')
|
||||
}
|
||||
|
||||
this.options = options
|
||||
this.canvas = canvas
|
||||
}
|
||||
|
||||
_read () {
|
||||
// For now we're not controlling the c++ code's data emission, so we only
|
||||
// call canvas.streamJPEGSync once and let it emit data at will.
|
||||
this._read = noop
|
||||
|
||||
this.canvas.streamJPEGSync(this.options, (err, chunk) => {
|
||||
if (err) {
|
||||
this.emit('error', err)
|
||||
} else if (chunk) {
|
||||
this.push(chunk)
|
||||
} else {
|
||||
this.push(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = JPEGStream
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
'use strict'
|
||||
|
||||
/*!
|
||||
* Canvas - CanvasPattern
|
||||
* Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
const bindings = require('./bindings')
|
||||
|
||||
module.exports = bindings.CanvasPattern
|
||||
|
||||
bindings.CanvasPattern.prototype.toString = function () {
|
||||
return '[object CanvasPattern]'
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
'use strict'
|
||||
|
||||
/*!
|
||||
* Canvas - PDFStream
|
||||
*/
|
||||
|
||||
const { Readable } = require('stream')
|
||||
function noop () {}
|
||||
|
||||
class PDFStream extends Readable {
|
||||
constructor (canvas, options) {
|
||||
super()
|
||||
|
||||
this.canvas = canvas
|
||||
this.options = options
|
||||
}
|
||||
|
||||
_read () {
|
||||
// For now we're not controlling the c++ code's data emission, so we only
|
||||
// call canvas.streamPDFSync once and let it emit data at will.
|
||||
this._read = noop
|
||||
|
||||
this.canvas.streamPDFSync((err, chunk, len) => {
|
||||
if (err) {
|
||||
this.emit('error', err)
|
||||
} else if (len) {
|
||||
this.push(chunk)
|
||||
} else {
|
||||
this.push(null)
|
||||
}
|
||||
}, this.options)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PDFStream
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
'use strict'
|
||||
|
||||
/*!
|
||||
* Canvas - PNGStream
|
||||
* Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
const { Readable } = require('stream')
|
||||
function noop () {}
|
||||
|
||||
class PNGStream extends Readable {
|
||||
constructor (canvas, options) {
|
||||
super()
|
||||
|
||||
if (options &&
|
||||
options.palette instanceof Uint8ClampedArray &&
|
||||
options.palette.length % 4 !== 0) {
|
||||
throw new Error('Palette length must be a multiple of 4.')
|
||||
}
|
||||
this.canvas = canvas
|
||||
this.options = options || {}
|
||||
}
|
||||
|
||||
_read () {
|
||||
// For now we're not controlling the c++ code's data emission, so we only
|
||||
// call canvas.streamPNGSync once and let it emit data at will.
|
||||
this._read = noop
|
||||
|
||||
this.canvas.streamPNGSync((err, chunk, len) => {
|
||||
if (err) {
|
||||
this.emit('error', err)
|
||||
} else if (len) {
|
||||
this.push(chunk)
|
||||
} else {
|
||||
this.push(null)
|
||||
}
|
||||
}, this.options)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PNGStream
|
||||
+1
@@ -0,0 +1 @@
|
||||
../prebuild-install/bin.js
|
||||
Generated
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
name: Run npm Tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [22, 23]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 inspiredware
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# napi-build-utils
|
||||
|
||||
[](https://www.npmjs.com/package/napi-build-utils)
|
||||

|
||||

|
||||
[](http://standardjs.com/)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
A set of utilities to assist developers of tools that build [Node-API](https://nodejs.org/api/n-api.html#n_api_n_api) native add-ons.
|
||||
|
||||
## Background
|
||||
|
||||
This module is targeted to developers creating tools that build Node-API native add-ons.
|
||||
|
||||
It implements a set of functions that aid in determining the Node-API version supported by the currently running Node instance and the set of Node-API versions against which the Node-API native add-on is designed to be built. Other functions determine whether a particular Node-API version can be built and can issue console warnings for unsupported Node-API versions.
|
||||
|
||||
Unlike the modules this code is designed to facilitate building, this module is written entirely in JavaScript.
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
npm install napi-build-utils
|
||||
```
|
||||
|
||||
The module exports a set of functions documented [here](./index.md). For example:
|
||||
|
||||
```javascript
|
||||
var napiBuildUtils = require('napi-build-utils');
|
||||
var napiVersion = napiBuildUtils.getNapiVersion(); // Node-API version supported by Node, or undefined.
|
||||
```
|
||||
|
||||
## Declaring supported Node-API versions
|
||||
|
||||
Native modules that are designed to work with [Node-API](https://nodejs.org/api/n-api.html#n_api_n_api) must explicitly declare the Node-API version(s) against which they are coded to build. This is accomplished by including a `binary.napi_versions` property in the module's `package.json` file. For example:
|
||||
|
||||
```json
|
||||
"binary": {
|
||||
"napi_versions": [2,3]
|
||||
}
|
||||
```
|
||||
|
||||
In the absence of a need to compile against a specific Node-API version, the value `3` is a good choice as this is the Node-API version that was supported when Node-API left experimental status.
|
||||
|
||||
Modules that are built against a specific Node-API version will continue to operate indefinitely, even as later versions of Node-API are introduced.
|
||||
|
||||
## History
|
||||
|
||||
**v2.0.0** This version was introduced to address a limitation when the Node-API version reached `10` in NodeJS `v23.6.0`. There was no change in the API, but a SemVer bump to `2.0.0` was made out of an abundance of caution.
|
||||
|
||||
## Support
|
||||
|
||||
If you run into problems or limitations, please file an issue and we'll take a look. Pull requests are also welcome.
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
'use strict'
|
||||
// Copyright (c) 2018 inspiredware
|
||||
|
||||
var path = require('path')
|
||||
var pkg = require(path.resolve('package.json'))
|
||||
|
||||
var versionArray = process.version
|
||||
.substr(1)
|
||||
.replace(/-.*$/, '')
|
||||
.split('.')
|
||||
.map(function (item) {
|
||||
return +item
|
||||
})
|
||||
|
||||
/**
|
||||
*
|
||||
* A set of utilities to assist developers of tools that build
|
||||
* [N-API](https://nodejs.org/api/n-api.html#n_api_n_api) native add-ons.
|
||||
*
|
||||
* The main repository can be found
|
||||
* [here](https://github.com/inspiredware/napi-build-utils#napi-build-utils).
|
||||
*
|
||||
* @module napi-build-utils
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements a consistent name of `napi` for N-API runtimes.
|
||||
*
|
||||
* @param {string} runtime The runtime string.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
exports.isNapiRuntime = function (runtime) {
|
||||
return runtime === 'napi'
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the specified N-API version is supported
|
||||
* by both the currently running Node instance and the package.
|
||||
*
|
||||
* @param {string} napiVersion The N-API version to check.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
exports.isSupportedVersion = function (napiVersion) {
|
||||
var version = parseInt(napiVersion, 10)
|
||||
return version <= exports.getNapiVersion() && exports.packageSupportsVersion(version)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the specified N-API version is supported by the package.
|
||||
* The N-API version must be present in the `package.json`
|
||||
* `binary.napi_versions` array.
|
||||
*
|
||||
* @param {number} napiVersion The N-API version to check.
|
||||
* @returns {boolean}
|
||||
* @private
|
||||
*/
|
||||
exports.packageSupportsVersion = function (napiVersion) {
|
||||
if (pkg.binary && pkg.binary.napi_versions &&
|
||||
pkg.binary.napi_versions instanceof Array) { // integer array
|
||||
for (var i = 0; i < pkg.binary.napi_versions.length; i++) {
|
||||
if (pkg.binary.napi_versions[i] === napiVersion) return true
|
||||
};
|
||||
};
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a warning to the supplied log if the N-API version is not supported
|
||||
* by the current Node instance or if the N-API version is not supported
|
||||
* by the package.
|
||||
*
|
||||
* @param {string} napiVersion The N-API version to check.
|
||||
* @param {Object} log The log object to which the warnings are to be issued.
|
||||
* Must implement the `warn` method.
|
||||
*/
|
||||
exports.logUnsupportedVersion = function (napiVersion, log) {
|
||||
if (!exports.isSupportedVersion(napiVersion)) {
|
||||
if (exports.packageSupportsVersion(napiVersion)) {
|
||||
log.warn('This Node instance does not support N-API version ' + napiVersion)
|
||||
} else {
|
||||
log.warn('This package does not support N-API version ' + napiVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues warnings to the supplied log for those N-API versions not supported
|
||||
* by the N-API runtime or the package.
|
||||
*
|
||||
* Note that this function is specific to the
|
||||
* [`prebuild`](https://github.com/prebuild/prebuild#prebuild) package.
|
||||
*
|
||||
* `target` is the list of targets to be built and is determined in one of
|
||||
* three ways from the command line arguments:
|
||||
* (1) `--target` specifies a specific target to build.
|
||||
* (2) `--all` specifies all N-API versions supported by the package.
|
||||
* (3) Neither of these specifies to build the single "best version available."
|
||||
*
|
||||
* `prebuild` is an array of objects in the form `{runtime: 'napi', target: '2'}`.
|
||||
* The array contains the list of N-API versions that are supported by both the
|
||||
* package being built and the currently running Node instance.
|
||||
*
|
||||
* The objective of this function is to issue a warning for those items that appear
|
||||
* in the `target` argument but not in the `prebuild` argument.
|
||||
* If a specific target is supported by the package (`packageSupportsVersion`) but
|
||||
* but note in `prebuild`, the assumption is that the target is not supported by
|
||||
* Node.
|
||||
*
|
||||
* @param {(Array<string>|string)} target The N-API version(s) to check. Target is
|
||||
* @param {Array<Object>} prebuild A config object created by the `prebuild` package.
|
||||
* @param {Object} log The log object to which the warnings are to be issued.
|
||||
* Must implement the `warn` method.
|
||||
* @private
|
||||
*/
|
||||
exports.logMissingNapiVersions = function (target, prebuild, log) {
|
||||
if (exports.getNapiBuildVersions()) {
|
||||
var targets = [].concat(target)
|
||||
targets.forEach(function (napiVersion) {
|
||||
if (!prebuildExists(prebuild, napiVersion)) {
|
||||
if (exports.packageSupportsVersion(parseInt(napiVersion, 10))) {
|
||||
log.warn('This Node instance does not support N-API version ' + napiVersion)
|
||||
} else {
|
||||
log.warn('This package does not support N-API version ' + napiVersion)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
log.error('Builds with runtime \'napi\' require a binary.napi_versions ' +
|
||||
'property on the package.json file')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the specified N-API version exists in the prebuild
|
||||
* configuration object.
|
||||
*
|
||||
* Note that this function is specific to the `prebuild` and `prebuild-install`
|
||||
* packages.
|
||||
*
|
||||
* @param {Object} prebuild A config object created by the `prebuild` package.
|
||||
* @param {string} napiVersion The N-APi version to be checked.
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
var prebuildExists = function (prebuild, napiVersion) {
|
||||
if (prebuild) {
|
||||
for (var i = 0; i < prebuild.length; i++) {
|
||||
if (prebuild[i].target === napiVersion) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best N-API version to build given the highest N-API
|
||||
* version supported by the current Node instance and the N-API versions
|
||||
* supported by the package, or undefined if a suitable N-API version
|
||||
* cannot be determined.
|
||||
*
|
||||
* The best build version is the greatest N-API version supported by
|
||||
* the package that is less than or equal to the highest N-API version
|
||||
* supported by the current Node instance.
|
||||
*
|
||||
* @returns {number|undefined}
|
||||
*/
|
||||
exports.getBestNapiBuildVersion = function () {
|
||||
var bestNapiBuildVersion = 0
|
||||
var napiBuildVersions = exports.getNapiBuildVersions(pkg) // array of integer strings
|
||||
if (napiBuildVersions) {
|
||||
var ourNapiVersion = exports.getNapiVersion()
|
||||
napiBuildVersions.forEach(function (napiBuildVersionStr) {
|
||||
var napiBuildVersion = parseInt(napiBuildVersionStr, 10)
|
||||
if (napiBuildVersion > bestNapiBuildVersion &&
|
||||
napiBuildVersion <= ourNapiVersion) {
|
||||
bestNapiBuildVersion = napiBuildVersion
|
||||
}
|
||||
})
|
||||
}
|
||||
return bestNapiBuildVersion === 0 ? undefined : bestNapiBuildVersion
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of N-API versions supported by the package.
|
||||
*
|
||||
* @returns {Array<string>|undefined}
|
||||
*/
|
||||
exports.getNapiBuildVersions = function () {
|
||||
var napiBuildVersions = []
|
||||
// remove duplicates, convert to text
|
||||
if (pkg.binary && pkg.binary.napi_versions) {
|
||||
pkg.binary.napi_versions.forEach(function (napiVersion) {
|
||||
var duplicated = napiBuildVersions.indexOf('' + napiVersion) !== -1
|
||||
if (!duplicated) {
|
||||
napiBuildVersions.push('' + napiVersion)
|
||||
}
|
||||
})
|
||||
}
|
||||
return napiBuildVersions.length ? napiBuildVersions : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the highest N-API version supported by the current node instance
|
||||
* or undefined if N-API is not supported.
|
||||
*
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
exports.getNapiVersion = function () {
|
||||
var version = process.versions.napi // integer string, can be undefined
|
||||
if (!version) { // this code should never need to be updated
|
||||
if (versionArray[0] === 9 && versionArray[1] >= 3) version = '2' // 9.3.0+
|
||||
else if (versionArray[0] === 8) version = '1' // 8.0.0+
|
||||
}
|
||||
return version
|
||||
}
|
||||
Whitespace-only changes.
+42
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "napi-build-utils",
|
||||
"version": "2.0.0",
|
||||
"description": "A set of utilities to assist developers of tools that build N-API native add-ons",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"doc": "jsdoc2md index.js >index.md",
|
||||
"test": "mocha test/ && npm run lint",
|
||||
"lint": "standard",
|
||||
"prepublishOnly": "npm run test"
|
||||
},
|
||||
"keywords": [
|
||||
"n-api",
|
||||
"prebuild",
|
||||
"prebuild-install"
|
||||
],
|
||||
"author": "Jim Schlight",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/inspiredware/napi-build-utils#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/inspiredware/napi-build-utils.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/inspiredware/napi-build-utils/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.1.2",
|
||||
"jsdoc-to-markdown": "^4.0.1",
|
||||
"mocha": "^5.2.0",
|
||||
"standard": "^12.0.1"
|
||||
},
|
||||
"binary": {
|
||||
"note": "napi-build-tools is not an N-API module. This entry is for unit testing.",
|
||||
"napi_versions": [
|
||||
2,
|
||||
2,
|
||||
3,
|
||||
10
|
||||
]
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
# Changelog
|
||||
|
||||
## [7.1.3] - 2025-01-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bump napi-build-utils from 1 to 2 ([#204](https://github.com/prebuild/prebuild-install/issues/204)) ([`1bf4a15`](https://github.com/prebuild/prebuild-install/commit/1bf4a15)) (Bailey Pearson)
|
||||
|
||||
## [7.1.2] - 2024-02-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- Support environments where MD5 is prohibited ([#191](https://github.com/prebuild/prebuild-install/issues/191)) ([`9140468`](https://github.com/prebuild/prebuild-install/commit/9140468)) (Tomasz Szuba)
|
||||
|
||||
## [7.1.1] - 2022-06-07
|
||||
|
||||
### Fixed
|
||||
|
||||
- Replace use of npmlog dependency with console.error ([#182](https://github.com/prebuild/prebuild-install/issues/182)) ([`4e2284c`](https://github.com/prebuild/prebuild-install/commit/4e2284c)) (Lovell Fuller)
|
||||
- Ensure script output can be captured by tests ([#181](https://github.com/prebuild/prebuild-install/issues/181)) ([`d1853cb`](https://github.com/prebuild/prebuild-install/commit/d1853cb)) (Lovell Fuller)
|
||||
|
||||
## [7.1.0] - 2022-04-20
|
||||
|
||||
### Changed
|
||||
|
||||
- Allow setting libc to glibc on non-glibc platform ([#176](https://github.com/prebuild/prebuild-install/issues/176)) ([`f729abb`](https://github.com/prebuild/prebuild-install/commit/f729abb)) (Joona Heinikoski)
|
||||
|
||||
## [7.0.1] - 2022-01-28
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgrade to the latest version of `detect-libc` ([#166](https://github.com/prebuild/prebuild-install/issues/166)) ([`f71c6b9`](https://github.com/prebuild/prebuild-install/commit/f71c6b9)) (Lovell Fuller)
|
||||
|
||||
## [7.0.0] - 2021-11-12
|
||||
|
||||
### Changed
|
||||
|
||||
- **Breaking:** bump `node-abi` so that Electron 14+ gets correct ABI ([#161](https://github.com/prebuild/prebuild-install/issues/161)) ([`477f347`](https://github.com/prebuild/prebuild-install/commit/477f347)) (csett86). Drops support of Node.js < 10.
|
||||
- Bump `simple-get` ([`7468c14`](https://github.com/prebuild/prebuild-install/commit/7468c14)) (Vincent Weevers).
|
||||
|
||||
## [6.1.4] - 2021-08-11
|
||||
|
||||
### Fixed
|
||||
|
||||
- Move auth token to header instead of query param ([#160](https://github.com/prebuild/prebuild-install/issues/160)) ([`b3fad76`](https://github.com/prebuild/prebuild-install/commit/b3fad76)) (nicolai-nordic)
|
||||
- Remove `_` prefix as it isn't allowed by npm config ([#153](https://github.com/prebuild/prebuild-install/issues/153)) ([`a964e5b`](https://github.com/prebuild/prebuild-install/commit/a964e5b)) (Tom Boothman)
|
||||
- Make `rc.path` absolute ([#158](https://github.com/prebuild/prebuild-install/issues/158)) ([`57bcc06`](https://github.com/prebuild/prebuild-install/commit/57bcc06)) (George Waters).
|
||||
|
||||
## [6.1.3] - 2021-06-03
|
||||
|
||||
### Changed
|
||||
|
||||
- Inline no longer maintained `noop-logger` ([#155](https://github.com/prebuild/prebuild-install/issues/155)) ([`e08d75a`](https://github.com/prebuild/prebuild-install/commit/e08d75a)) (Alexandru Dima)
|
||||
- Point users towards `prebuildify` in README ([#150](https://github.com/prebuild/prebuild-install/issues/150)) ([`5ee1a2f`](https://github.com/prebuild/prebuild-install/commit/5ee1a2f)) (Vincent Weevers)
|
||||
|
||||
## [6.1.2] - 2021-04-24
|
||||
|
||||
### Fixed
|
||||
|
||||
- Support URL-safe strings in scoped packages ([#148](https://github.com/prebuild/prebuild-install/issues/148)) ([`db36c7a`](https://github.com/prebuild/prebuild-install/commit/db36c7a)) (Marco)
|
||||
|
||||
## [6.1.1] - 2021-04-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- Support `force` & `buildFromSource` options in yarn ([#140](https://github.com/prebuild/prebuild-install/issues/140)) ([`8cb1ced`](https://github.com/prebuild/prebuild-install/commit/8cb1ced)) (João Moreno)
|
||||
- Bump `node-abi` to prevent dedupe (closes [#135](https://github.com/prebuild/prebuild-install/issues/135)) ([`2950fb2`](https://github.com/prebuild/prebuild-install/commit/2950fb2)) (Vincent Weevers)
|
||||
|
||||
## [6.1.0] - 2021-04-03
|
||||
|
||||
### Added
|
||||
|
||||
- Restore local prebuilds feature ([#137](https://github.com/prebuild/prebuild-install/issues/137)) ([`dc4e5ea`](https://github.com/prebuild/prebuild-install/commit/dc4e5ea)) (Wes Roberts). Previously removed in [#81](https://github.com/prebuild/prebuild-install/issues/81) / [`a069253`](https://github.com/prebuild/prebuild-install/commit/a06925378d38ca821bfa93aa4c1fdedc253b2420).
|
||||
|
||||
## [6.0.1] - 2021-02-14
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixes empty `--tag-prefix` ([#143](https://github.com/prebuild/prebuild-install/issues/143)) ([**@mathiask88**](https://github.com/mathiask88))
|
||||
|
||||
## [6.0.0] - 2020-10-23
|
||||
|
||||
### Changed
|
||||
|
||||
- **Breaking:** don't skip downloads in standalone mode ([`b6f3b36`](https://github.com/prebuild/prebuild-install/commit/b6f3b36)) ([**@vweevers**](https://github.com/vweevers))
|
||||
|
||||
### Added
|
||||
|
||||
- Document cross platform options ([`e5c9a5a`](https://github.com/prebuild/prebuild-install/commit/e5c9a5a)) ([**@fishbone1**](https://github.com/fishbone1))
|
||||
|
||||
### Removed
|
||||
|
||||
- **Breaking:** remove `--compile` and `--prebuild` options ([`94f2492`](https://github.com/prebuild/prebuild-install/commit/94f2492)) ([**@vweevers**](https://github.com/vweevers))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Support npm 7 ([`8acccac`](https://github.com/prebuild/prebuild-install/commit/8acccac), [`08eaf6d`](https://github.com/prebuild/prebuild-install/commit/08eaf6d), [`22175b8`](https://github.com/prebuild/prebuild-install/commit/22175b8)) ([**@vweevers**](https://github.com/vweevers))
|
||||
|
||||
## [5.3.6] - 2020-10-20
|
||||
|
||||
### Changed
|
||||
|
||||
- Replace `mkdirp` dependency with `mkdirp-classic` ([**@ralphtheninja**](https://github.com/ralphtheninja))
|
||||
|
||||
[7.1.3]: https://github.com/prebuild/prebuild-install/releases/tag/v7.1.3
|
||||
|
||||
[7.1.2]: https://github.com/prebuild/prebuild-install/releases/tag/v7.1.2
|
||||
|
||||
[7.1.1]: https://github.com/prebuild/prebuild-install/releases/tag/v7.1.1
|
||||
|
||||
[7.1.0]: https://github.com/prebuild/prebuild-install/releases/tag/v7.1.0
|
||||
|
||||
[7.0.1]: https://github.com/prebuild/prebuild-install/releases/tag/v7.0.1
|
||||
|
||||
[7.0.0]: https://github.com/prebuild/prebuild-install/releases/tag/v7.0.0
|
||||
|
||||
[6.1.4]: https://github.com/prebuild/prebuild-install/releases/tag/v6.1.4
|
||||
|
||||
[6.1.3]: https://github.com/prebuild/prebuild-install/releases/tag/v6.1.3
|
||||
|
||||
[6.1.2]: https://github.com/prebuild/prebuild-install/releases/tag/v6.1.2
|
||||
|
||||
[6.1.1]: https://github.com/prebuild/prebuild-install/releases/tag/v6.1.1
|
||||
|
||||
[6.1.0]: https://github.com/prebuild/prebuild-install/releases/tag/v6.1.0
|
||||
|
||||
[6.0.1]: https://github.com/prebuild/prebuild-install/releases/tag/v6.0.1
|
||||
|
||||
[6.0.0]: https://github.com/prebuild/prebuild-install/releases/tag/v6.0.0
|
||||
|
||||
[5.3.6]: https://github.com/prebuild/prebuild-install/releases/tag/v5.3.6
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
# Contributing to prebuild
|
||||
|
||||
- no commits direct to master
|
||||
- all commits as pull requests (one or several per PR)
|
||||
- each commit solves one identifiable problem
|
||||
- never merge one's own PRs, another contributor does this
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Mathias Buus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
# prebuild-install
|
||||
|
||||
> **A command line tool to easily install prebuilt binaries for multiple versions of Node.js & Electron on a specific platform.**
|
||||
> By default it downloads prebuilt binaries from a GitHub release.
|
||||
|
||||
[](https://www.npmjs.com/package/prebuild-install)
|
||||

|
||||
[](https://github.com/prebuild/prebuild-install/actions/workflows/test.yml)
|
||||
[](https://standardjs.com)
|
||||
[](https://common-changelog.org)
|
||||
|
||||
## Note
|
||||
|
||||
**Instead of [`prebuild`](https://github.com/prebuild/prebuild) paired with [`prebuild-install`](https://github.com/prebuild/prebuild-install), we recommend [`prebuildify`](https://github.com/prebuild/prebuildify) paired with [`node-gyp-build`](https://github.com/prebuild/node-gyp-build).**
|
||||
|
||||
With `prebuildify`, all prebuilt binaries are shipped inside the package that is published to npm, which means there's no need for a separate download step like you find in `prebuild`. The irony of this approach is that it is faster to download all prebuilt binaries for every platform when they are bundled than it is to download a single prebuilt binary as an install script.
|
||||
|
||||
Upsides:
|
||||
|
||||
1. No extra download step, making it more reliable and faster to install.
|
||||
2. Supports changing runtime versions locally and using the same install between Node.js and Electron. Reinstalling or rebuilding is not necessary, as all prebuilt binaries are in the npm tarball and the correct one is simply picked on runtime.
|
||||
3. The `node-gyp-build` runtime dependency is dependency-free and will remain so out of principle, because introducing dependencies would negate the shorter install time.
|
||||
4. Prebuilt binaries work even if npm install scripts are disabled.
|
||||
5. The npm package checksum covers prebuilt binaries too.
|
||||
|
||||
Downsides:
|
||||
|
||||
1. The installed npm package is larger on disk. Using [Node-API](https://nodejs.org/api/n-api.html) alleviates this because Node-API binaries are runtime-agnostic and forward-compatible.
|
||||
2. Publishing is mildly more complicated, because `npm publish` must be done after compiling and fetching prebuilt binaries (typically in CI).
|
||||
|
||||
## Usage
|
||||
|
||||
Use [`prebuild`](https://github.com/prebuild/prebuild) to create and upload prebuilt binaries. Then change your package.json install script to:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"install": "prebuild-install || node-gyp rebuild"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When a consumer then installs your package with npm thus triggering the above install script, `prebuild-install` will download a suitable prebuilt binary, or exit with a non-zero exit code if there is none, which triggers `node-gyp rebuild` in order to build from source.
|
||||
|
||||
Options (see below) can be passed to `prebuild-install` like so:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"install": "prebuild-install -r napi || node-gyp rebuild"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Help
|
||||
|
||||
```
|
||||
prebuild-install [options]
|
||||
|
||||
--download -d [url] (download prebuilds, no url means github)
|
||||
--target -t version (version to install for)
|
||||
--runtime -r runtime (Node runtime [node, napi or electron] to build or install for, default is node)
|
||||
--path -p path (make a prebuild-install here)
|
||||
--token -T gh-token (github token for private repos)
|
||||
--arch arch (target CPU architecture, see Node OS module docs, default is current arch)
|
||||
--platform platform (target platform, see Node OS module docs, default is current platform)
|
||||
--tag-prefix <prefix> (github tag prefix, default is "v")
|
||||
--build-from-source (skip prebuild download)
|
||||
--verbose (log verbosely)
|
||||
--libc (use provided libc rather than system default)
|
||||
--debug (set Debug or Release configuration)
|
||||
--version (print prebuild-install version and exit)
|
||||
```
|
||||
|
||||
When `prebuild-install` is run via an `npm` script, options `--build-from-source`, `--debug`, `--download`, `--target`, `--runtime`, `--arch` `--platform` and `--libc` may be passed through via arguments given to the `npm` command.
|
||||
|
||||
Alternatively you can set environment variables `npm_config_build_from_source=true`, `npm_config_platform`, `npm_config_arch`, `npm_config_target` `npm_config_runtime` and `npm_config_libc`.
|
||||
|
||||
### Libc
|
||||
|
||||
On non-glibc Linux platforms, the Libc name is appended to platform name. For example, musl-based environments are called `linuxmusl`. If `--libc=glibc` is passed as option, glibc is discarded and platform is called as just `linux`. This can be used for example to build cross-platform packages on Alpine Linux.
|
||||
|
||||
### Private Repositories
|
||||
|
||||
`prebuild-install` supports downloading prebuilds from private GitHub repositories using the `-T <github-token>`:
|
||||
|
||||
```
|
||||
$ prebuild-install -T <github-token>
|
||||
```
|
||||
|
||||
If you don't want to use the token on cli you can put it in `~/.prebuild-installrc`:
|
||||
|
||||
```
|
||||
token=<github-token>
|
||||
```
|
||||
|
||||
Alternatively you can specify it in the `prebuild-install_token` environment variable.
|
||||
|
||||
Note that using a GitHub token uses the API to resolve the correct release meaning that you are subject to the ([GitHub Rate Limit](https://developer.github.com/v3/rate_limit/)).
|
||||
|
||||
### Create GitHub Token
|
||||
|
||||
To create a token:
|
||||
|
||||
- Go to [this page](https://github.com/settings/tokens)
|
||||
- Click the `Generate new token` button
|
||||
- Give the token a name and click the `Generate token` button, see below
|
||||
|
||||

|
||||
|
||||
The default scopes should be fine.
|
||||
|
||||
### Custom binaries
|
||||
|
||||
The end user can override binary download location through environment variables in their .npmrc file.
|
||||
The variable needs to meet the mask `% your package name %_binary_host` or `% your package name %_binary_host_mirror`. For example:
|
||||
|
||||
```
|
||||
leveldown_binary_host=http://overriden-host.com/overriden-path
|
||||
```
|
||||
|
||||
Note that the package version subpath and file name will still be appended.
|
||||
So if you are installing `leveldown@1.2.3` the resulting url will be:
|
||||
|
||||
```
|
||||
http://overriden-host.com/overriden-path/v1.2.3/leveldown-v1.2.3-node-v57-win32-x64.tar.gz
|
||||
```
|
||||
|
||||
#### Local prebuilds
|
||||
|
||||
If you want to use prebuilds from your local filesystem, you can use the `% your package name %_local_prebuilds` .npmrc variable to set a path to the folder containing prebuilds. For example:
|
||||
|
||||
```
|
||||
leveldown_local_prebuilds=/path/to/prebuilds
|
||||
```
|
||||
|
||||
This option will look directly in that folder for bundles created with `prebuild`, for example:
|
||||
|
||||
```
|
||||
/path/to/prebuilds/leveldown-v1.2.3-node-v57-win32-x64.tar.gz
|
||||
```
|
||||
|
||||
Non-absolute paths resolve relative to the directory of the package invoking prebuild-install, e.g. for nested dependencies.
|
||||
|
||||
### Cache
|
||||
|
||||
All prebuilt binaries are cached to minimize traffic. So first `prebuild-install` picks binaries from the cache and if no binary could be found, it will be downloaded. Depending on the environment, the cache folder is determined in the following order:
|
||||
|
||||
- `${npm_config_cache}/_prebuilds`
|
||||
- `${APP_DATA}/npm-cache/_prebuilds`
|
||||
- `${HOME}/.npm/_prebuilds`
|
||||
|
||||
## Install
|
||||
|
||||
With [npm](https://npmjs.org) do:
|
||||
|
||||
```
|
||||
npm install prebuild-install
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[MIT](./LICENSE)
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
const get = require('simple-get')
|
||||
const util = require('./util')
|
||||
const proxy = require('./proxy')
|
||||
|
||||
function findAssetId (opts, cb) {
|
||||
const downloadUrl = util.getDownloadUrl(opts)
|
||||
const apiUrl = util.getApiUrl(opts)
|
||||
const log = opts.log || util.noopLogger
|
||||
|
||||
log.http('request', 'GET ' + apiUrl)
|
||||
const reqOpts = proxy({
|
||||
url: apiUrl,
|
||||
json: true,
|
||||
headers: {
|
||||
'User-Agent': 'simple-get',
|
||||
Authorization: 'token ' + opts.token
|
||||
}
|
||||
}, opts)
|
||||
|
||||
const req = get.concat(reqOpts, function (err, res, data) {
|
||||
if (err) return cb(err)
|
||||
log.http(res.statusCode, apiUrl)
|
||||
if (res.statusCode !== 200) return cb(err)
|
||||
|
||||
// Find asset id in release
|
||||
for (const release of data) {
|
||||
if (release.tag_name === opts['tag-prefix'] + opts.pkg.version) {
|
||||
for (const asset of release.assets) {
|
||||
if (asset.browser_download_url === downloadUrl) {
|
||||
return cb(null, asset.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cb(new Error('Could not find GitHub release for version'))
|
||||
})
|
||||
|
||||
req.setTimeout(30 * 1000, function () {
|
||||
req.abort()
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = findAssetId
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const napi = require('napi-build-utils')
|
||||
|
||||
const pkg = require(path.resolve('package.json'))
|
||||
const rc = require('./rc')(pkg)
|
||||
const log = require('./log')(rc, process.env)
|
||||
const download = require('./download')
|
||||
const asset = require('./asset')
|
||||
const util = require('./util')
|
||||
|
||||
const prebuildClientVersion = require('./package.json').version
|
||||
if (rc.version) {
|
||||
console.log(prebuildClientVersion)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (rc.path) process.chdir(rc.path)
|
||||
|
||||
if (rc.runtime === 'electron' && rc.target[0] === '4' && rc.abi === '64') {
|
||||
log.error(`Electron version ${rc.target} found - skipping prebuild-install work due to known ABI issue`)
|
||||
log.error('More information about this issue can be found at https://github.com/lgeiger/node-abi/issues/54')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!fs.existsSync('package.json')) {
|
||||
log.error('setup', 'No package.json found. Aborting...')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (rc.help) {
|
||||
console.error(fs.readFileSync(path.join(__dirname, 'help.txt'), 'utf-8'))
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
log.info('begin', 'Prebuild-install version', prebuildClientVersion)
|
||||
|
||||
const opts = Object.assign({}, rc, { pkg: pkg, log: log })
|
||||
|
||||
if (napi.isNapiRuntime(rc.runtime)) napi.logUnsupportedVersion(rc.target, log)
|
||||
|
||||
const origin = util.packageOrigin(process.env, pkg)
|
||||
|
||||
if (opts.force) {
|
||||
log.warn('install', 'prebuilt binaries enforced with --force!')
|
||||
log.warn('install', 'prebuilt binaries may be out of date!')
|
||||
} else if (origin && origin.length > 4 && origin.substr(0, 4) === 'git+') {
|
||||
log.info('install', 'installing from git repository, skipping download.')
|
||||
process.exit(1)
|
||||
} else if (opts.buildFromSource) {
|
||||
log.info('install', '--build-from-source specified, not attempting download.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const startDownload = function (downloadUrl) {
|
||||
download(downloadUrl, opts, function (err) {
|
||||
if (err) {
|
||||
log.warn('install', err.message)
|
||||
return process.exit(1)
|
||||
}
|
||||
log.info('install', 'Successfully installed prebuilt binary!')
|
||||
})
|
||||
}
|
||||
|
||||
if (opts.token) {
|
||||
asset(opts, function (err, assetId) {
|
||||
if (err) {
|
||||
log.warn('install', err.message)
|
||||
return process.exit(1)
|
||||
}
|
||||
|
||||
startDownload(util.getAssetUrl(opts, assetId))
|
||||
})
|
||||
} else {
|
||||
startDownload(util.getDownloadUrl(opts))
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const get = require('simple-get')
|
||||
const pump = require('pump')
|
||||
const tfs = require('tar-fs')
|
||||
const zlib = require('zlib')
|
||||
const util = require('./util')
|
||||
const error = require('./error')
|
||||
const proxy = require('./proxy')
|
||||
const mkdirp = require('mkdirp-classic')
|
||||
|
||||
function downloadPrebuild (downloadUrl, opts, cb) {
|
||||
let cachedPrebuild = util.cachedPrebuild(downloadUrl)
|
||||
const localPrebuild = util.localPrebuild(downloadUrl, opts)
|
||||
const tempFile = util.tempFile(cachedPrebuild)
|
||||
const log = opts.log || util.noopLogger
|
||||
|
||||
if (opts.nolocal) return download()
|
||||
|
||||
log.info('looking for local prebuild @', localPrebuild)
|
||||
fs.access(localPrebuild, fs.R_OK | fs.W_OK, function (err) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
return download()
|
||||
}
|
||||
|
||||
log.info('found local prebuild')
|
||||
cachedPrebuild = localPrebuild
|
||||
unpack()
|
||||
})
|
||||
|
||||
function download () {
|
||||
ensureNpmCacheDir(function (err) {
|
||||
if (err) return onerror(err)
|
||||
|
||||
log.info('looking for cached prebuild @', cachedPrebuild)
|
||||
fs.access(cachedPrebuild, fs.R_OK | fs.W_OK, function (err) {
|
||||
if (!(err && err.code === 'ENOENT')) {
|
||||
log.info('found cached prebuild')
|
||||
return unpack()
|
||||
}
|
||||
|
||||
log.http('request', 'GET ' + downloadUrl)
|
||||
const reqOpts = proxy({ url: downloadUrl }, opts)
|
||||
|
||||
if (opts.token) {
|
||||
reqOpts.headers = {
|
||||
'User-Agent': 'simple-get',
|
||||
Accept: 'application/octet-stream',
|
||||
Authorization: 'token ' + opts.token
|
||||
}
|
||||
}
|
||||
|
||||
const req = get(reqOpts, function (err, res) {
|
||||
if (err) return onerror(err)
|
||||
log.http(res.statusCode, downloadUrl)
|
||||
if (res.statusCode !== 200) return onerror()
|
||||
mkdirp(util.prebuildCache(), function () {
|
||||
log.info('downloading to @', tempFile)
|
||||
pump(res, fs.createWriteStream(tempFile), function (err) {
|
||||
if (err) return onerror(err)
|
||||
fs.rename(tempFile, cachedPrebuild, function (err) {
|
||||
if (err) return cb(err)
|
||||
log.info('renaming to @', cachedPrebuild)
|
||||
unpack()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
req.setTimeout(30 * 1000, function () {
|
||||
req.abort()
|
||||
})
|
||||
})
|
||||
|
||||
function onerror (err) {
|
||||
fs.unlink(tempFile, function () {
|
||||
cb(err || error.noPrebuilts(opts))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function unpack () {
|
||||
let binaryName
|
||||
|
||||
const updateName = opts.updateName || function (entry) {
|
||||
if (/\.node$/i.test(entry.name)) binaryName = entry.name
|
||||
}
|
||||
|
||||
log.info('unpacking @', cachedPrebuild)
|
||||
|
||||
const options = {
|
||||
readable: true,
|
||||
writable: true,
|
||||
hardlinkAsFilesFallback: true
|
||||
}
|
||||
const extract = tfs.extract(opts.path, options).on('entry', updateName)
|
||||
|
||||
pump(fs.createReadStream(cachedPrebuild), zlib.createGunzip(), extract,
|
||||
function (err) {
|
||||
if (err) return cb(err)
|
||||
|
||||
let resolved
|
||||
if (binaryName) {
|
||||
try {
|
||||
resolved = path.resolve(opts.path || '.', binaryName)
|
||||
} catch (err) {
|
||||
return cb(err)
|
||||
}
|
||||
log.info('unpack', 'resolved to ' + resolved)
|
||||
|
||||
if (opts.runtime === 'node' && opts.platform === process.platform && opts.abi === process.versions.modules && opts.arch === process.arch) {
|
||||
try {
|
||||
require(resolved)
|
||||
} catch (err) {
|
||||
return cb(err)
|
||||
}
|
||||
log.info('unpack', 'required ' + resolved + ' successfully')
|
||||
}
|
||||
}
|
||||
|
||||
cb(null, resolved)
|
||||
})
|
||||
}
|
||||
|
||||
function ensureNpmCacheDir (cb) {
|
||||
const cacheFolder = util.npmCache()
|
||||
fs.access(cacheFolder, fs.R_OK | fs.W_OK, function (err) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
return makeNpmCacheDir()
|
||||
}
|
||||
cb(err)
|
||||
})
|
||||
|
||||
function makeNpmCacheDir () {
|
||||
log.info('npm cache directory missing, creating it...')
|
||||
mkdirp(cacheFolder, cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = downloadPrebuild
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
exports.noPrebuilts = function (opts) {
|
||||
return new Error([
|
||||
'No prebuilt binaries found',
|
||||
'(target=' + opts.target,
|
||||
'runtime=' + opts.runtime,
|
||||
'arch=' + opts.arch,
|
||||
'libc=' + opts.libc,
|
||||
'platform=' + opts.platform + ')'
|
||||
].join(' '))
|
||||
}
|
||||
|
||||
exports.invalidArchive = function () {
|
||||
return new Error('Missing .node file in archive')
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
prebuild-install [options]
|
||||
|
||||
--download -d [url] (download prebuilds, no url means github)
|
||||
--target -t version (version to install for)
|
||||
--runtime -r runtime (Node runtime [node or electron] to build or install for, default is node)
|
||||
--path -p path (make a prebuild-install here)
|
||||
--token -T gh-token (github token for private repos)
|
||||
--arch arch (target CPU architecture, see Node OS module docs, default is current arch)
|
||||
--platform platform (target platform, see Node OS module docs, default is current platform)
|
||||
--tag-prefix <prefix> (github tag prefix, default is "v")
|
||||
--force (always use prebuilt binaries when available)
|
||||
--build-from-source (skip prebuild download)
|
||||
--verbose (log verbosely)
|
||||
--libc (use provided libc rather than system default)
|
||||
--debug (set Debug or Release configuration)
|
||||
--version (print prebuild-install version and exit)
|
||||
+1
@@ -0,0 +1 @@
|
||||
exports.download = require('./download')
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
const levels = {
|
||||
silent: 0,
|
||||
error: 1,
|
||||
warn: 2,
|
||||
notice: 3,
|
||||
http: 4,
|
||||
timing: 5,
|
||||
info: 6,
|
||||
verbose: 7,
|
||||
silly: 8
|
||||
}
|
||||
|
||||
module.exports = function (rc, env) {
|
||||
const level = rc.verbose
|
||||
? 'verbose'
|
||||
: env.npm_config_loglevel || 'notice'
|
||||
|
||||
const logAtLevel = function (messageLevel) {
|
||||
return function (...args) {
|
||||
if (levels[messageLevel] <= levels[level]) {
|
||||
console.error(`prebuild-install ${messageLevel} ${args.join(' ')}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
error: logAtLevel('error'),
|
||||
warn: logAtLevel('warn'),
|
||||
http: logAtLevel('http'),
|
||||
info: logAtLevel('info'),
|
||||
level
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../../../rc/cli.js
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "prebuild-install",
|
||||
"version": "7.1.3",
|
||||
"description": "A command line tool to easily install prebuilt binaries for multiple version of node/iojs on a specific platform",
|
||||
"scripts": {
|
||||
"test": "standard && hallmark && tape test/*-test.js",
|
||||
"hallmark": "hallmark --fix"
|
||||
},
|
||||
"keywords": [
|
||||
"prebuilt",
|
||||
"binaries",
|
||||
"native",
|
||||
"addon",
|
||||
"module",
|
||||
"c",
|
||||
"c++",
|
||||
"bindings",
|
||||
"devops",
|
||||
"napi"
|
||||
],
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^2.0.0",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"a-native-module": "^1.0.0",
|
||||
"hallmark": "^4.0.0",
|
||||
"nock": "^10.0.6",
|
||||
"rimraf": "^2.5.2",
|
||||
"standard": "^16.0.4",
|
||||
"tape": "^5.3.1",
|
||||
"tempy": "0.2.1"
|
||||
},
|
||||
"bin": "./bin.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/prebuild/prebuild-install.git"
|
||||
},
|
||||
"author": "Mathias Buus (@mafintosh)",
|
||||
"contributors": [
|
||||
"Julian Gruber <julian@juliangruber.com> (https://github.com/juliangruber)",
|
||||
"Brett Lawson <brett19@gmail.com> (https://github.com/brett19)",
|
||||
"Pieter Hintjens <ph@imatix.com> (https://github.com/hintjens)",
|
||||
"Lars-Magnus Skog <ralphtheninja@riseup.net> (https://github.com/ralphtheninja)",
|
||||
"Jesús Leganés Combarro <piranna@gmail.com> (https://github.com/piranna)",
|
||||
"Mathias Küsel <mathiask@hotmail.de> (https://github.com/mathiask88)",
|
||||
"Lukas Geiger <lukas.geiger94@gmail.com> (https://github.com/lgeiger)"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/prebuild/prebuild-install/issues"
|
||||
},
|
||||
"homepage": "https://github.com/prebuild/prebuild-install",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
const url = require('url')
|
||||
const tunnel = require('tunnel-agent')
|
||||
const util = require('./util')
|
||||
|
||||
function applyProxy (reqOpts, opts) {
|
||||
const log = opts.log || util.noopLogger
|
||||
|
||||
const proxy = opts['https-proxy'] || opts.proxy
|
||||
|
||||
if (proxy) {
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
const parsedDownloadUrl = url.parse(reqOpts.url)
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
const parsedProxy = url.parse(proxy)
|
||||
const uriProtocol = (parsedDownloadUrl.protocol === 'https:' ? 'https' : 'http')
|
||||
const proxyProtocol = (parsedProxy.protocol === 'https:' ? 'Https' : 'Http')
|
||||
const tunnelFnName = [uriProtocol, proxyProtocol].join('Over')
|
||||
reqOpts.agent = tunnel[tunnelFnName]({
|
||||
proxy: {
|
||||
host: parsedProxy.hostname,
|
||||
port: +parsedProxy.port,
|
||||
proxyAuth: parsedProxy.auth
|
||||
}
|
||||
})
|
||||
log.http('request', 'Proxy setup detected (Host: ' +
|
||||
parsedProxy.hostname + ', Port: ' +
|
||||
parsedProxy.port + ', Authentication: ' +
|
||||
(parsedProxy.auth ? 'Yes' : 'No') + ')' +
|
||||
' Tunneling with ' + tunnelFnName)
|
||||
}
|
||||
|
||||
return reqOpts
|
||||
}
|
||||
|
||||
module.exports = applyProxy
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
const path = require('path')
|
||||
const minimist = require('minimist')
|
||||
const getAbi = require('node-abi').getAbi
|
||||
const detectLibc = require('detect-libc')
|
||||
const napi = require('napi-build-utils')
|
||||
|
||||
const env = process.env
|
||||
|
||||
const libc = env.LIBC || process.env.npm_config_libc ||
|
||||
(detectLibc.isNonGlibcLinuxSync() && detectLibc.familySync()) || ''
|
||||
|
||||
// Get the configuration
|
||||
module.exports = function (pkg) {
|
||||
const pkgConf = pkg.config || {}
|
||||
const buildFromSource = env.npm_config_build_from_source
|
||||
|
||||
const rc = require('rc')('prebuild-install', {
|
||||
target: pkgConf.target || env.npm_config_target || process.versions.node,
|
||||
runtime: pkgConf.runtime || env.npm_config_runtime || 'node',
|
||||
arch: pkgConf.arch || env.npm_config_arch || process.arch,
|
||||
libc: libc,
|
||||
platform: env.npm_config_platform || process.platform,
|
||||
debug: env.npm_config_debug === 'true',
|
||||
force: false,
|
||||
verbose: env.npm_config_verbose === 'true',
|
||||
buildFromSource: buildFromSource === pkg.name || buildFromSource === 'true',
|
||||
path: '.',
|
||||
proxy: env.npm_config_proxy || env.http_proxy || env.HTTP_PROXY,
|
||||
'https-proxy': env.npm_config_https_proxy || env.https_proxy || env.HTTPS_PROXY,
|
||||
'local-address': env.npm_config_local_address,
|
||||
'local-prebuilds': 'prebuilds',
|
||||
'tag-prefix': 'v',
|
||||
download: env.npm_config_download
|
||||
}, minimist(process.argv, {
|
||||
alias: {
|
||||
target: 't',
|
||||
runtime: 'r',
|
||||
help: 'h',
|
||||
arch: 'a',
|
||||
path: 'p',
|
||||
version: 'v',
|
||||
download: 'd',
|
||||
buildFromSource: 'build-from-source',
|
||||
token: 'T'
|
||||
}
|
||||
}))
|
||||
|
||||
rc.path = path.resolve(rc.path === true ? '.' : rc.path || '.')
|
||||
|
||||
if (napi.isNapiRuntime(rc.runtime) && rc.target === process.versions.node) {
|
||||
rc.target = napi.getBestNapiBuildVersion()
|
||||
}
|
||||
|
||||
rc.abi = napi.isNapiRuntime(rc.runtime) ? rc.target : getAbi(rc.target, rc.runtime)
|
||||
|
||||
rc.libc = rc.platform !== 'linux' || rc.libc === detectLibc.GLIBC ? '' : rc.libc
|
||||
|
||||
return rc
|
||||
}
|
||||
|
||||
// Print the configuration values when executed standalone for testing purposses
|
||||
if (!module.parent) {
|
||||
console.log(JSON.stringify(module.exports({}), null, 2))
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
const path = require('path')
|
||||
const github = require('github-from-package')
|
||||
const home = require('os').homedir
|
||||
const crypto = require('crypto')
|
||||
const expandTemplate = require('expand-template')()
|
||||
|
||||
function getDownloadUrl (opts) {
|
||||
const pkgName = opts.pkg.name.replace(/^@[a-zA-Z0-9_\-.~]+\//, '')
|
||||
return expandTemplate(urlTemplate(opts), {
|
||||
name: pkgName,
|
||||
package_name: pkgName,
|
||||
version: opts.pkg.version,
|
||||
major: opts.pkg.version.split('.')[0],
|
||||
minor: opts.pkg.version.split('.')[1],
|
||||
patch: opts.pkg.version.split('.')[2],
|
||||
prerelease: opts.pkg.version.split('-')[1],
|
||||
build: opts.pkg.version.split('+')[1],
|
||||
abi: opts.abi || process.versions.modules,
|
||||
node_abi: process.versions.modules,
|
||||
runtime: opts.runtime || 'node',
|
||||
platform: opts.platform,
|
||||
arch: opts.arch,
|
||||
libc: opts.libc || '',
|
||||
configuration: (opts.debug ? 'Debug' : 'Release'),
|
||||
module_name: opts.pkg.binary && opts.pkg.binary.module_name,
|
||||
tag_prefix: opts['tag-prefix']
|
||||
})
|
||||
}
|
||||
|
||||
function getApiUrl (opts) {
|
||||
return github(opts.pkg).replace('github.com', 'api.github.com/repos') + '/releases'
|
||||
}
|
||||
|
||||
function getAssetUrl (opts, assetId) {
|
||||
return getApiUrl(opts) + '/assets/' + assetId
|
||||
}
|
||||
|
||||
function urlTemplate (opts) {
|
||||
if (typeof opts.download === 'string') {
|
||||
return opts.download
|
||||
}
|
||||
|
||||
const packageName = '{name}-v{version}-{runtime}-v{abi}-{platform}{libc}-{arch}.tar.gz'
|
||||
const hostMirrorUrl = getHostMirrorUrl(opts)
|
||||
|
||||
if (hostMirrorUrl) {
|
||||
return hostMirrorUrl + '/{tag_prefix}{version}/' + packageName
|
||||
}
|
||||
|
||||
if (opts.pkg.binary && opts.pkg.binary.host) {
|
||||
return [
|
||||
opts.pkg.binary.host,
|
||||
opts.pkg.binary.remote_path,
|
||||
opts.pkg.binary.package_name || packageName
|
||||
].map(function (path) {
|
||||
return trimSlashes(path)
|
||||
}).filter(Boolean).join('/')
|
||||
}
|
||||
|
||||
return github(opts.pkg) + '/releases/download/{tag_prefix}{version}/' + packageName
|
||||
}
|
||||
|
||||
function getEnvPrefix (pkgName) {
|
||||
return 'npm_config_' + (pkgName || '').replace(/[^a-zA-Z0-9]/g, '_').replace(/^_/, '')
|
||||
}
|
||||
|
||||
function getHostMirrorUrl (opts) {
|
||||
const propName = getEnvPrefix(opts.pkg.name) + '_binary_host'
|
||||
return process.env[propName] || process.env[propName + '_mirror']
|
||||
}
|
||||
|
||||
function trimSlashes (str) {
|
||||
if (str) return str.replace(/^\.\/|^\/|\/$/g, '')
|
||||
}
|
||||
|
||||
function cachedPrebuild (url) {
|
||||
const digest = crypto.createHash('sha512').update(url).digest('hex').slice(0, 6)
|
||||
return path.join(prebuildCache(), digest + '-' + path.basename(url).replace(/[^a-zA-Z0-9.]+/g, '-'))
|
||||
}
|
||||
|
||||
function npmCache () {
|
||||
const env = process.env
|
||||
return env.npm_config_cache || (env.APPDATA ? path.join(env.APPDATA, 'npm-cache') : path.join(home(), '.npm'))
|
||||
}
|
||||
|
||||
function prebuildCache () {
|
||||
return path.join(npmCache(), '_prebuilds')
|
||||
}
|
||||
|
||||
function tempFile (cached) {
|
||||
return cached + '.' + process.pid + '-' + Math.random().toString(16).slice(2) + '.tmp'
|
||||
}
|
||||
|
||||
function packageOrigin (env, pkg) {
|
||||
// npm <= 6: metadata is stored on disk in node_modules
|
||||
if (pkg._from) {
|
||||
return pkg._from
|
||||
}
|
||||
|
||||
// npm 7: metadata is exposed to environment by arborist
|
||||
if (env.npm_package_from) {
|
||||
// NOTE: seems undefined atm (npm 7.0.2)
|
||||
return env.npm_package_from
|
||||
}
|
||||
|
||||
if (env.npm_package_resolved) {
|
||||
// NOTE: not sure about the difference with _from, but it's all we have
|
||||
return env.npm_package_resolved
|
||||
}
|
||||
}
|
||||
|
||||
function localPrebuild (url, opts) {
|
||||
const propName = getEnvPrefix(opts.pkg.name) + '_local_prebuilds'
|
||||
const prefix = process.env[propName] || opts['local-prebuilds'] || 'prebuilds'
|
||||
return path.join(prefix, path.basename(url))
|
||||
}
|
||||
|
||||
const noopLogger = {
|
||||
http: function () {},
|
||||
silly: function () {},
|
||||
debug: function () {},
|
||||
info: function () {},
|
||||
warn: function () {},
|
||||
error: function () {},
|
||||
critical: function () {},
|
||||
alert: function () {},
|
||||
emergency: function () {},
|
||||
notice: function () {},
|
||||
verbose: function () {},
|
||||
fatal: function () {}
|
||||
}
|
||||
|
||||
exports.getDownloadUrl = getDownloadUrl
|
||||
exports.getApiUrl = getApiUrl
|
||||
exports.getAssetUrl = getAssetUrl
|
||||
exports.urlTemplate = urlTemplate
|
||||
exports.cachedPrebuild = cachedPrebuild
|
||||
exports.localPrebuild = localPrebuild
|
||||
exports.prebuildCache = prebuildCache
|
||||
exports.npmCache = npmCache
|
||||
exports.tempFile = tempFile
|
||||
exports.packageOrigin = packageOrigin
|
||||
exports.noopLogger = noopLogger
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"name": "canvas",
|
||||
"description": "Canvas graphics API backed by Cairo",
|
||||
"version": "3.1.0",
|
||||
"author": "TJ Holowaychuk <tj@learnboost.com>",
|
||||
"main": "index.js",
|
||||
"browser": "browser.js",
|
||||
"types": "index.d.ts",
|
||||
"contributors": [
|
||||
"Nathan Rajlich <nathan@tootallnate.net>",
|
||||
"Rod Vagg <r@va.gg>",
|
||||
"Juriy Zaytsev <kangax@gmail.com>"
|
||||
],
|
||||
"keywords": [
|
||||
"canvas",
|
||||
"graphic",
|
||||
"graphics",
|
||||
"pixman",
|
||||
"cairo",
|
||||
"image",
|
||||
"images",
|
||||
"pdf"
|
||||
],
|
||||
"homepage": "https://github.com/Automattic/node-canvas",
|
||||
"repository": "git://github.com/Automattic/node-canvas.git",
|
||||
"scripts": {
|
||||
"prebenchmark": "node-gyp build",
|
||||
"benchmark": "node benchmarks/run.js",
|
||||
"lint": "standard examples/*.js test/server.js test/public/*.js benchmarks/run.js lib/context2d.js util/has_lib.js browser.js index.js",
|
||||
"test": "mocha test/*.test.js",
|
||||
"pretest-server": "node-gyp build",
|
||||
"test-server": "node test/server.js",
|
||||
"generate-wpt": "node ./test/wpt/generate.js",
|
||||
"test-wpt": "mocha test/wpt/generated/*.js",
|
||||
"install": "prebuild-install -r napi || node-gyp rebuild",
|
||||
"tsd": "tsd"
|
||||
},
|
||||
"files": [
|
||||
"binding.gyp",
|
||||
"browser.js",
|
||||
"index.d.ts",
|
||||
"index.js",
|
||||
"lib/",
|
||||
"src/",
|
||||
"util/"
|
||||
],
|
||||
"dependencies": {
|
||||
"node-addon-api": "^7.0.0",
|
||||
"prebuild-install": "^7.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^10.12.18",
|
||||
"assert-rejects": "^1.0.0",
|
||||
"express": "^4.16.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"mocha": "^5.2.0",
|
||||
"pixelmatch": "^4.0.2",
|
||||
"standard": "^12.0.1",
|
||||
"tsd": "^0.29.0",
|
||||
"typescript": "^4.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.12.0 || >= 20.9.0"
|
||||
},
|
||||
"binary": {
|
||||
"napi_versions": [7]
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#include "Backends.h"
|
||||
|
||||
#include "backend/ImageBackend.h"
|
||||
#include "backend/PdfBackend.h"
|
||||
#include "backend/SvgBackend.h"
|
||||
|
||||
using namespace Napi;
|
||||
|
||||
void
|
||||
Backends::Initialize(Napi::Env env, Napi::Object exports) {
|
||||
Napi::Object obj = Napi::Object::New(env);
|
||||
|
||||
ImageBackend::Initialize(obj);
|
||||
PdfBackend::Initialize(obj);
|
||||
SvgBackend::Initialize(obj);
|
||||
|
||||
exports.Set("Backends", obj);
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "backend/Backend.h"
|
||||
#include <napi.h>
|
||||
|
||||
class Backends : public Napi::ObjectWrap<Backends> {
|
||||
public:
|
||||
static void Initialize(Napi::Env env, Napi::Object exports);
|
||||
};
|
||||
+938
@@ -0,0 +1,938 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#include "Canvas.h"
|
||||
#include "InstanceData.h"
|
||||
#include <algorithm> // std::min
|
||||
#include <assert.h>
|
||||
#include <cairo-pdf.h>
|
||||
#include <cairo-svg.h>
|
||||
#include "CanvasRenderingContext2d.h"
|
||||
#include "closure.h"
|
||||
#include <cstring>
|
||||
#include <cctype>
|
||||
#include <ctime>
|
||||
#include <glib.h>
|
||||
#include "PNG.h"
|
||||
#include "register_font.h"
|
||||
#include <sstream>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include "Util.h"
|
||||
#include <vector>
|
||||
#include "node_buffer.h"
|
||||
#include "FontParser.h"
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
#include "JPEGStream.h"
|
||||
#endif
|
||||
|
||||
#include "backend/ImageBackend.h"
|
||||
#include "backend/PdfBackend.h"
|
||||
#include "backend/SvgBackend.h"
|
||||
|
||||
#define GENERIC_FACE_ERROR \
|
||||
"The second argument to registerFont is required, and should be an object " \
|
||||
"with at least a family (string) and optionally weight (string/number) " \
|
||||
"and style (string)."
|
||||
|
||||
using namespace std;
|
||||
|
||||
std::vector<FontFace> Canvas::font_face_list;
|
||||
|
||||
// Increases each time a font is (de)registered
|
||||
int Canvas::fontSerial = 1;
|
||||
|
||||
/*
|
||||
* Initialize Canvas.
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::Initialize(Napi::Env& env, Napi::Object& exports) {
|
||||
Napi::HandleScope scope(env);
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
|
||||
// Constructor
|
||||
Napi::Function ctor = DefineClass(env, "Canvas", {
|
||||
InstanceMethod<&Canvas::ToBuffer>("toBuffer", napi_default_method),
|
||||
InstanceMethod<&Canvas::StreamPNGSync>("streamPNGSync", napi_default_method),
|
||||
InstanceMethod<&Canvas::StreamPDFSync>("streamPDFSync", napi_default_method),
|
||||
#ifdef HAVE_JPEG
|
||||
InstanceMethod<&Canvas::StreamJPEGSync>("streamJPEGSync", napi_default_method),
|
||||
#endif
|
||||
InstanceAccessor<&Canvas::GetType>("type", napi_default_jsproperty),
|
||||
InstanceAccessor<&Canvas::GetStride>("stride", napi_default_jsproperty),
|
||||
InstanceAccessor<&Canvas::GetWidth, &Canvas::SetWidth>("width", napi_default_jsproperty),
|
||||
InstanceAccessor<&Canvas::GetHeight, &Canvas::SetHeight>("height", napi_default_jsproperty),
|
||||
StaticValue("PNG_NO_FILTERS", Napi::Number::New(env, PNG_NO_FILTERS), napi_default_jsproperty),
|
||||
StaticValue("PNG_FILTER_NONE", Napi::Number::New(env, PNG_FILTER_NONE), napi_default_jsproperty),
|
||||
StaticValue("PNG_FILTER_SUB", Napi::Number::New(env, PNG_FILTER_SUB), napi_default_jsproperty),
|
||||
StaticValue("PNG_FILTER_UP", Napi::Number::New(env, PNG_FILTER_UP), napi_default_jsproperty),
|
||||
StaticValue("PNG_FILTER_AVG", Napi::Number::New(env, PNG_FILTER_AVG), napi_default_jsproperty),
|
||||
StaticValue("PNG_FILTER_PAETH", Napi::Number::New(env, PNG_FILTER_PAETH), napi_default_jsproperty),
|
||||
StaticValue("PNG_ALL_FILTERS", Napi::Number::New(env, PNG_ALL_FILTERS), napi_default_jsproperty),
|
||||
StaticMethod<&Canvas::RegisterFont>("_registerFont", napi_default_method),
|
||||
StaticMethod<&Canvas::DeregisterAllFonts>("_deregisterAllFonts", napi_default_method),
|
||||
StaticMethod<&Canvas::ParseFont>("parseFont", napi_default_method)
|
||||
});
|
||||
|
||||
data->CanvasCtor = Napi::Persistent(ctor);
|
||||
exports.Set("Canvas", ctor);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a Canvas with the given width and height.
|
||||
*/
|
||||
|
||||
Canvas::Canvas(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Canvas>(info), env(info.Env()) {
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
ctor = Napi::Persistent(data->CanvasCtor.Value());
|
||||
Backend* backend = NULL;
|
||||
Napi::Object jsBackend;
|
||||
|
||||
if (info[0].IsNumber()) {
|
||||
Napi::Number width = info[0].As<Napi::Number>();
|
||||
Napi::Number height = Napi::Number::New(env, 0);
|
||||
|
||||
if (info[1].IsNumber()) height = info[1].As<Napi::Number>();
|
||||
|
||||
if (info[2].IsString()) {
|
||||
std::string str = info[2].As<Napi::String>();
|
||||
if (str == "pdf") {
|
||||
Napi::Maybe<Napi::Object> instance = data->PdfBackendCtor.New({ width, height });
|
||||
if (instance.IsJust()) backend = PdfBackend::Unwrap(jsBackend = instance.Unwrap());
|
||||
} else if (str == "svg") {
|
||||
Napi::Maybe<Napi::Object> instance = data->SvgBackendCtor.New({ width, height });
|
||||
if (instance.IsJust()) backend = SvgBackend::Unwrap(jsBackend = instance.Unwrap());
|
||||
} else {
|
||||
Napi::Maybe<Napi::Object> instance = data->ImageBackendCtor.New({ width, height });
|
||||
if (instance.IsJust()) backend = ImageBackend::Unwrap(jsBackend = instance.Unwrap());
|
||||
}
|
||||
} else {
|
||||
Napi::Maybe<Napi::Object> instance = data->ImageBackendCtor.New({ width, height });
|
||||
if (instance.IsJust()) backend = ImageBackend::Unwrap(jsBackend = instance.Unwrap());
|
||||
}
|
||||
} else if (info[0].IsObject()) {
|
||||
jsBackend = info[0].As<Napi::Object>();
|
||||
if (jsBackend.InstanceOf(data->ImageBackendCtor.Value()).UnwrapOr(false)) {
|
||||
backend = ImageBackend::Unwrap(jsBackend);
|
||||
} else if (jsBackend.InstanceOf(data->PdfBackendCtor.Value()).UnwrapOr(false)) {
|
||||
backend = PdfBackend::Unwrap(jsBackend);
|
||||
} else if (jsBackend.InstanceOf(data->SvgBackendCtor.Value()).UnwrapOr(false)) {
|
||||
backend = SvgBackend::Unwrap(jsBackend);
|
||||
} else {
|
||||
Napi::TypeError::New(env, "Invalid arguments").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Napi::Number width = Napi::Number::New(env, 0);
|
||||
Napi::Number height = Napi::Number::New(env, 0);
|
||||
Napi::Maybe<Napi::Object> instance = data->ImageBackendCtor.New({ width, height });
|
||||
if (instance.IsJust()) backend = ImageBackend::Unwrap(jsBackend = instance.Unwrap());
|
||||
}
|
||||
|
||||
if (!backend->isSurfaceValid()) {
|
||||
Napi::Error::New(env, backend->getError()).ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
backend->setCanvas(this);
|
||||
|
||||
// Note: the backend gets destroyed when the jsBackend is GC'd. The cleaner
|
||||
// way would be to only store the jsBackend and unwrap it when the c++ ref is
|
||||
// needed, but that's slower and a burden. The _backend might be null if we
|
||||
// returned early, but since an exception was thrown it gets destroyed soon.
|
||||
_backend = backend;
|
||||
_jsBackend = Napi::Persistent(jsBackend);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get type string.
|
||||
*/
|
||||
|
||||
Napi::Value
|
||||
Canvas::GetType(const Napi::CallbackInfo& info) {
|
||||
return Napi::String::New(env, backend()->getName());
|
||||
}
|
||||
|
||||
/*
|
||||
* Get stride.
|
||||
*/
|
||||
Napi::Value
|
||||
Canvas::GetStride(const Napi::CallbackInfo& info) {
|
||||
return Napi::Number::New(env, stride());
|
||||
}
|
||||
|
||||
/*
|
||||
* Get width.
|
||||
*/
|
||||
|
||||
Napi::Value
|
||||
Canvas::GetWidth(const Napi::CallbackInfo& info) {
|
||||
return Napi::Number::New(env, getWidth());
|
||||
}
|
||||
|
||||
/*
|
||||
* Set width.
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value) {
|
||||
if (value.IsNumber()) {
|
||||
backend()->setWidth(value.As<Napi::Number>().Uint32Value());
|
||||
resurface(info.This().As<Napi::Object>());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Get height.
|
||||
*/
|
||||
|
||||
Napi::Value
|
||||
Canvas::GetHeight(const Napi::CallbackInfo& info) {
|
||||
return Napi::Number::New(env, getHeight());
|
||||
}
|
||||
|
||||
/*
|
||||
* Set height.
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value) {
|
||||
if (value.IsNumber()) {
|
||||
backend()->setHeight(value.As<Napi::Number>().Uint32Value());
|
||||
resurface(info.This().As<Napi::Object>());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* EIO toBuffer callback.
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::ToPngBufferAsync(Closure* base) {
|
||||
PngClosure* closure = static_cast<PngClosure*>(base);
|
||||
|
||||
closure->status = canvas_write_to_png_stream(
|
||||
closure->canvas->surface(),
|
||||
PngClosure::writeVec,
|
||||
closure);
|
||||
}
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
void
|
||||
Canvas::ToJpegBufferAsync(Closure* base) {
|
||||
JpegClosure* closure = static_cast<JpegClosure*>(base);
|
||||
write_to_jpeg_buffer(closure->canvas->surface(), closure);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
parsePNGArgs(Napi::Value arg, PngClosure& pngargs) {
|
||||
if (arg.IsObject()) {
|
||||
Napi::Object obj = arg.As<Napi::Object>();
|
||||
Napi::Value cLevel;
|
||||
|
||||
if (obj.Get("compressionLevel").UnwrapTo(&cLevel) && cLevel.IsNumber()) {
|
||||
uint32_t val = cLevel.As<Napi::Number>().Uint32Value();
|
||||
// See quote below from spec section 4.12.5.5.
|
||||
if (val <= 9) pngargs.compressionLevel = val;
|
||||
}
|
||||
|
||||
Napi::Value rez;
|
||||
if (obj.Get("resolution").UnwrapTo(&rez) && rez.IsNumber()) {
|
||||
uint32_t val = rez.As<Napi::Number>().Uint32Value();
|
||||
if (val > 0) pngargs.resolution = val;
|
||||
}
|
||||
|
||||
Napi::Value filters;
|
||||
if (obj.Get("filters").UnwrapTo(&filters) && filters.IsNumber()) {
|
||||
pngargs.filters = filters.As<Napi::Number>().Uint32Value();
|
||||
}
|
||||
|
||||
Napi::Value palette;
|
||||
if (obj.Get("palette").UnwrapTo(&palette) && palette.IsTypedArray()) {
|
||||
Napi::TypedArray palette_ta = palette.As<Napi::TypedArray>();
|
||||
if (palette_ta.TypedArrayType() == napi_uint8_clamped_array) {
|
||||
pngargs.nPaletteColors = palette_ta.ElementLength();
|
||||
if (pngargs.nPaletteColors % 4 != 0) {
|
||||
throw "Palette length must be a multiple of 4.";
|
||||
}
|
||||
pngargs.palette = palette_ta.As<Napi::Uint8Array>().Data();
|
||||
pngargs.nPaletteColors /= 4;
|
||||
// Optional background color index:
|
||||
Napi::Value backgroundIndexVal;
|
||||
if (obj.Get("backgroundIndex").UnwrapTo(&backgroundIndexVal) && backgroundIndexVal.IsNumber()) {
|
||||
pngargs.backgroundIndex = backgroundIndexVal.As<Napi::Number>().Uint32Value();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
static void parseJPEGArgs(Napi::Value arg, JpegClosure& jpegargs) {
|
||||
// "If Type(quality) is not Number, or if quality is outside that range, the
|
||||
// user agent must use its default quality value, as if the quality argument
|
||||
// had not been given." - 4.12.5.5
|
||||
if (arg.IsObject()) {
|
||||
Napi::Object obj = arg.As<Napi::Object>();
|
||||
|
||||
Napi::Value qual;
|
||||
if (obj.Get("quality").UnwrapTo(&qual) && qual.IsNumber()) {
|
||||
double quality = qual.As<Napi::Number>().DoubleValue();
|
||||
if (quality >= 0.0 && quality <= 1.0) {
|
||||
jpegargs.quality = static_cast<uint32_t>(100.0 * quality);
|
||||
}
|
||||
}
|
||||
|
||||
Napi::Value chroma;
|
||||
if (obj.Get("chromaSubsampling").UnwrapTo(&chroma)) {
|
||||
if (chroma.IsBoolean()) {
|
||||
bool subsample = chroma.As<Napi::Boolean>().Value();
|
||||
jpegargs.chromaSubsampling = subsample ? 2 : 1;
|
||||
} else if (chroma.IsNumber()) {
|
||||
jpegargs.chromaSubsampling = chroma.As<Napi::Number>().Uint32Value();
|
||||
}
|
||||
}
|
||||
|
||||
Napi::Value progressive;
|
||||
if (obj.Get("progressive").UnwrapTo(&progressive) && progressive.IsBoolean()) {
|
||||
jpegargs.progressive = progressive.As<Napi::Boolean>().Value();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
|
||||
|
||||
static inline void setPdfMetaStr(cairo_surface_t* surf, Napi::Object opts,
|
||||
cairo_pdf_metadata_t t, const char* propName) {
|
||||
Napi::Value propValue;
|
||||
if (opts.Get(propName).UnwrapTo(&propValue) && propValue.IsString()) {
|
||||
// (copies char data)
|
||||
cairo_pdf_surface_set_metadata(surf, t, propValue.As<Napi::String>().Utf8Value().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static inline void setPdfMetaDate(cairo_surface_t* surf, Napi::Object opts,
|
||||
cairo_pdf_metadata_t t, const char* propName) {
|
||||
Napi::Value propValue;
|
||||
if (opts.Get(propName).UnwrapTo(&propValue) && propValue.IsDate()) {
|
||||
auto date = static_cast<time_t>(propValue.As<Napi::Date>().ValueOf() / 1000); // ms -> s
|
||||
char buf[sizeof "2011-10-08T07:07:09Z"];
|
||||
strftime(buf, sizeof buf, "%FT%TZ", gmtime(&date));
|
||||
cairo_pdf_surface_set_metadata(surf, t, buf);
|
||||
}
|
||||
}
|
||||
|
||||
static void setPdfMetadata(Canvas* canvas, Napi::Object opts) {
|
||||
cairo_surface_t* surf = canvas->surface();
|
||||
|
||||
setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_TITLE, "title");
|
||||
setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_AUTHOR, "author");
|
||||
setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_SUBJECT, "subject");
|
||||
setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_KEYWORDS, "keywords");
|
||||
setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_CREATOR, "creator");
|
||||
setPdfMetaDate(surf, opts, CAIRO_PDF_METADATA_CREATE_DATE, "creationDate");
|
||||
setPdfMetaDate(surf, opts, CAIRO_PDF_METADATA_MOD_DATE, "modDate");
|
||||
}
|
||||
|
||||
#endif // CAIRO 16+
|
||||
|
||||
/*
|
||||
* Converts/encodes data to a Buffer. Async when a callback function is passed.
|
||||
|
||||
* PDF canvases:
|
||||
(any) => Buffer
|
||||
("application/pdf", config) => Buffer
|
||||
|
||||
* SVG canvases:
|
||||
(any) => Buffer
|
||||
|
||||
* ARGB data:
|
||||
("raw") => Buffer
|
||||
|
||||
* PNG-encoded
|
||||
() => Buffer
|
||||
(undefined|"image/png", {compressionLevel?: number, filter?: number}) => Buffer
|
||||
((err: null|Error, buffer) => any)
|
||||
((err: null|Error, buffer) => any, undefined|"image/png", {compressionLevel?: number, filter?: number})
|
||||
|
||||
* JPEG-encoded
|
||||
("image/jpeg") => Buffer
|
||||
("image/jpeg", {quality?: number, progressive?: Boolean, chromaSubsampling?: Boolean|number}) => Buffer
|
||||
((err: null|Error, buffer) => any, "image/jpeg")
|
||||
((err: null|Error, buffer) => any, "image/jpeg", {quality?: number, progressive?: Boolean, chromaSubsampling?: Boolean|number})
|
||||
*/
|
||||
|
||||
Napi::Value
|
||||
Canvas::ToBuffer(const Napi::CallbackInfo& info) {
|
||||
EncodingWorker *worker = new EncodingWorker(info.Env());
|
||||
cairo_status_t status;
|
||||
|
||||
// Vector canvases, sync only
|
||||
const std::string name = backend()->getName();
|
||||
if (name == "pdf" || name == "svg") {
|
||||
// mime type may be present, but it's not checked
|
||||
PdfSvgClosure* closure;
|
||||
if (name == "pdf") {
|
||||
closure = static_cast<PdfBackend*>(backend())->closure();
|
||||
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
|
||||
if (info[1].IsObject()) { // toBuffer("application/pdf", config)
|
||||
setPdfMetadata(this, info[1].As<Napi::Object>());
|
||||
}
|
||||
#endif // CAIRO 16+
|
||||
} else {
|
||||
closure = static_cast<SvgBackend*>(backend())->closure();
|
||||
}
|
||||
|
||||
cairo_surface_t *surf = surface();
|
||||
cairo_surface_finish(surf);
|
||||
|
||||
cairo_status_t status = cairo_surface_status(surf);
|
||||
if (status != CAIRO_STATUS_SUCCESS) {
|
||||
Napi::Error::New(env, cairo_status_to_string(status)).ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
return Napi::Buffer<uint8_t>::Copy(env, &closure->vec[0], closure->vec.size());
|
||||
}
|
||||
|
||||
// Raw ARGB data -- just a memcpy()
|
||||
if (info[0].StrictEquals(Napi::String::New(env, "raw"))) {
|
||||
cairo_surface_t *surface = this->surface();
|
||||
cairo_surface_flush(surface);
|
||||
if (nBytes() > node::Buffer::kMaxLength) {
|
||||
Napi::Error::New(env, "Data exceeds maximum buffer length.").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
return Napi::Buffer<uint8_t>::Copy(env, cairo_image_surface_get_data(surface), nBytes());
|
||||
}
|
||||
|
||||
// Sync PNG, default
|
||||
if (info[0].IsUndefined() || info[0].StrictEquals(Napi::String::New(env, "image/png"))) {
|
||||
try {
|
||||
PngClosure closure(this);
|
||||
parsePNGArgs(info[1], closure);
|
||||
if (closure.nPaletteColors == 0xFFFFFFFF) {
|
||||
Napi::Error::New(env, "Palette length must be a multiple of 4.").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
status = canvas_write_to_png_stream(surface(), PngClosure::writeVec, &closure);
|
||||
|
||||
if (!env.IsExceptionPending()) {
|
||||
if (status) {
|
||||
throw status; // TODO: throw in js?
|
||||
} else {
|
||||
// TODO it's possible to avoid this copy
|
||||
return Napi::Buffer<uint8_t>::Copy(env, &closure.vec[0], closure.vec.size());
|
||||
}
|
||||
}
|
||||
} catch (cairo_status_t ex) {
|
||||
CairoError(ex).ThrowAsJavaScriptException();
|
||||
} catch (const char* ex) {
|
||||
Napi::Error::New(env, ex).ThrowAsJavaScriptException();
|
||||
|
||||
}
|
||||
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
// Async PNG
|
||||
if (info[0].IsFunction() &&
|
||||
(info[1].IsUndefined() || info[1].StrictEquals(Napi::String::New(env, "image/png")))) {
|
||||
|
||||
PngClosure* closure;
|
||||
try {
|
||||
closure = new PngClosure(this);
|
||||
parsePNGArgs(info[2], *closure);
|
||||
} catch (cairo_status_t ex) {
|
||||
CairoError(ex).ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
} catch (const char* ex) {
|
||||
Napi::Error::New(env, ex).ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
Ref();
|
||||
closure->cb = Napi::Persistent(info[0].As<Napi::Function>());
|
||||
|
||||
// Make sure the surface exists since we won't have an isolate context in the async block:
|
||||
surface();
|
||||
worker->Init(&ToPngBufferAsync, closure);
|
||||
worker->Queue();
|
||||
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
// Sync JPEG
|
||||
Napi::Value jpegStr = Napi::String::New(env, "image/jpeg");
|
||||
if (info[0].StrictEquals(jpegStr)) {
|
||||
try {
|
||||
JpegClosure closure(this);
|
||||
parseJPEGArgs(info[1], closure);
|
||||
|
||||
write_to_jpeg_buffer(surface(), &closure);
|
||||
|
||||
if (!env.IsExceptionPending()) {
|
||||
// TODO it's possible to avoid this copy.
|
||||
return Napi::Buffer<uint8_t>::Copy(env, &closure.vec[0], closure.vec.size());
|
||||
}
|
||||
} catch (cairo_status_t ex) {
|
||||
CairoError(ex).ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
// Async JPEG
|
||||
if (info[0].IsFunction() && info[1].StrictEquals(jpegStr)) {
|
||||
JpegClosure* closure = new JpegClosure(this);
|
||||
parseJPEGArgs(info[2], *closure);
|
||||
|
||||
Ref();
|
||||
closure->cb = Napi::Persistent(info[0].As<Napi::Function>());
|
||||
|
||||
// Make sure the surface exists since we won't have an isolate context in the async block:
|
||||
surface();
|
||||
worker->Init(&ToJpegBufferAsync, closure);
|
||||
worker->Queue();
|
||||
return env.Undefined();
|
||||
}
|
||||
#endif
|
||||
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
/*
|
||||
* Canvas::StreamPNG callback.
|
||||
*/
|
||||
|
||||
static cairo_status_t
|
||||
streamPNG(void *c, const uint8_t *data, unsigned len) {
|
||||
PngClosure* closure = (PngClosure*) c;
|
||||
Napi::Env env = closure->canvas->env;
|
||||
Napi::HandleScope scope(env);
|
||||
Napi::AsyncContext async(env, "canvas:StreamPNG");
|
||||
Napi::Value buf = Napi::Buffer<uint8_t>::Copy(env, data, len);
|
||||
closure->cb.MakeCallback(env.Global(), { env.Null(), buf, Napi::Number::New(env, len) }, async);
|
||||
return CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stream PNG data synchronously. TODO async
|
||||
* StreamPngSync(this, options: {palette?: Uint8ClampedArray, backgroundIndex?: uint32, compressionLevel: uint32, filters: uint32})
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::StreamPNGSync(const Napi::CallbackInfo& info) {
|
||||
if (!info[0].IsFunction()) {
|
||||
Napi::TypeError::New(env, "callback function required").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
PngClosure closure(this);
|
||||
parsePNGArgs(info[1], closure);
|
||||
|
||||
closure.cb = Napi::Persistent(info[0].As<Napi::Function>());
|
||||
|
||||
cairo_status_t status = canvas_write_to_png_stream(surface(), streamPNG, &closure);
|
||||
|
||||
if (!env.IsExceptionPending()) {
|
||||
if (status) {
|
||||
closure.cb.Call(env.Global(), { CairoError(status).Value() });
|
||||
} else {
|
||||
closure.cb.Call(env.Global(), { env.Null(), env.Null(), Napi::Number::New(env, 0) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct PdfStreamInfo {
|
||||
Napi::Function fn;
|
||||
uint32_t len;
|
||||
uint8_t* data;
|
||||
};
|
||||
|
||||
/*
|
||||
* Canvas::StreamPDF callback.
|
||||
*/
|
||||
|
||||
static cairo_status_t
|
||||
streamPDF(void *c, const uint8_t *data, unsigned len) {
|
||||
PdfStreamInfo* streaminfo = static_cast<PdfStreamInfo*>(c);
|
||||
Napi::Env env = streaminfo->fn.Env();
|
||||
Napi::HandleScope scope(env);
|
||||
Napi::AsyncContext async(env, "canvas:StreamPDF");
|
||||
// TODO this is technically wrong, we're returning a pointer to the data in a
|
||||
// vector in a class with automatic storage duration. If the canvas goes out
|
||||
// of scope while we're in the handler, a use-after-free could happen.
|
||||
Napi::Value buf = Napi::Buffer<uint8_t>::New(env, (uint8_t *)(data), len);
|
||||
streaminfo->fn.MakeCallback(env.Global(), { env.Null(), buf, Napi::Number::New(env, len) }, async);
|
||||
return CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
cairo_status_t canvas_write_to_pdf_stream(cairo_surface_t *surface, cairo_write_func_t write_func, PdfStreamInfo* streaminfo) {
|
||||
size_t whole_chunks = streaminfo->len / PAGE_SIZE;
|
||||
size_t remainder = streaminfo->len - whole_chunks * PAGE_SIZE;
|
||||
|
||||
for (size_t i = 0; i < whole_chunks; ++i) {
|
||||
write_func(streaminfo, &streaminfo->data[i * PAGE_SIZE], PAGE_SIZE);
|
||||
}
|
||||
|
||||
if (remainder) {
|
||||
write_func(streaminfo, &streaminfo->data[whole_chunks * PAGE_SIZE], remainder);
|
||||
}
|
||||
|
||||
return CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stream PDF data synchronously.
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::StreamPDFSync(const Napi::CallbackInfo& info) {
|
||||
if (!info[0].IsFunction()) {
|
||||
Napi::TypeError::New(env, "callback function required").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
if (backend()->getName() != "pdf") {
|
||||
Napi::TypeError::New(env, "wrong canvas type").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
|
||||
if (info[1].IsObject()) {
|
||||
setPdfMetadata(this, info[1].As<Napi::Object>());
|
||||
}
|
||||
#endif
|
||||
|
||||
cairo_surface_finish(surface());
|
||||
|
||||
PdfSvgClosure* closure = static_cast<PdfBackend*>(backend())->closure();
|
||||
Napi::Function fn = info[0].As<Napi::Function>();
|
||||
PdfStreamInfo streaminfo;
|
||||
streaminfo.fn = fn;
|
||||
streaminfo.data = &closure->vec[0];
|
||||
streaminfo.len = closure->vec.size();
|
||||
|
||||
cairo_status_t status = canvas_write_to_pdf_stream(surface(), streamPDF, &streaminfo);
|
||||
|
||||
if (!env.IsExceptionPending()) {
|
||||
if (status) {
|
||||
fn.Call(env.Global(), { CairoError(status).Value() });
|
||||
} else {
|
||||
fn.Call(env.Global(), { env.Null(), env.Null(), Napi::Number::New(env, 0) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Stream JPEG data synchronously.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
static uint32_t getSafeBufSize(Canvas* canvas) {
|
||||
// Don't allow the buffer size to exceed the size of the canvas (#674)
|
||||
// TODO not sure if this is really correct, but it fixed #674
|
||||
return (std::min)(canvas->getWidth() * canvas->getHeight() * 4, static_cast<int>(PAGE_SIZE));
|
||||
}
|
||||
|
||||
void
|
||||
Canvas::StreamJPEGSync(const Napi::CallbackInfo& info) {
|
||||
if (!info[1].IsFunction()) {
|
||||
Napi::TypeError::New(env, "callback function required").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
JpegClosure closure(this);
|
||||
parseJPEGArgs(info[0], closure);
|
||||
closure.cb = Napi::Persistent(info[1].As<Napi::Function>());
|
||||
|
||||
uint32_t bufsize = getSafeBufSize(this);
|
||||
write_to_jpeg_stream(surface(), bufsize, &closure);
|
||||
}
|
||||
#endif
|
||||
|
||||
char *
|
||||
str_value(Napi::Maybe<Napi::Value> maybe, const char *fallback, bool can_be_number) {
|
||||
Napi::Value val;
|
||||
if (maybe.UnwrapTo(&val)) {
|
||||
if (val.IsString() || (can_be_number && val.IsNumber())) {
|
||||
Napi::String strVal;
|
||||
if (val.ToString().UnwrapTo(&strVal)) return strdup(strVal.Utf8Value().c_str());
|
||||
} else if (fallback) {
|
||||
return strdup(fallback);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
Canvas::RegisterFont(const Napi::CallbackInfo& info) {
|
||||
Napi::Env env = info.Env();
|
||||
if (!info[0].IsString()) {
|
||||
Napi::Error::New(env, "Wrong argument type").ThrowAsJavaScriptException();
|
||||
return;
|
||||
} else if (!info[1].IsObject()) {
|
||||
Napi::Error::New(env, GENERIC_FACE_ERROR).ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string filePath = info[0].As<Napi::String>();
|
||||
PangoFontDescription *sys_desc = get_pango_font_description((unsigned char *)(filePath.c_str()));
|
||||
|
||||
if (!sys_desc) {
|
||||
Napi::Error::New(env, "Could not parse font file").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
PangoFontDescription *user_desc = pango_font_description_new();
|
||||
|
||||
// now check the attrs, there are many ways to be wrong
|
||||
Napi::Object js_user_desc = info[1].As<Napi::Object>();
|
||||
|
||||
// TODO: use FontParser on these values just like the FontFace API works
|
||||
char *family = str_value(js_user_desc.Get("family"), NULL, false);
|
||||
char *weight = str_value(js_user_desc.Get("weight"), "normal", true);
|
||||
char *style = str_value(js_user_desc.Get("style"), "normal", false);
|
||||
|
||||
if (family && weight && style) {
|
||||
pango_font_description_set_weight(user_desc, Canvas::GetWeightFromCSSString(weight));
|
||||
pango_font_description_set_style(user_desc, Canvas::GetStyleFromCSSString(style));
|
||||
pango_font_description_set_family(user_desc, family);
|
||||
|
||||
auto found = std::find_if(font_face_list.begin(), font_face_list.end(), [&](FontFace& f) {
|
||||
return pango_font_description_equal(f.sys_desc, sys_desc);
|
||||
});
|
||||
|
||||
if (found != font_face_list.end()) {
|
||||
pango_font_description_free(found->user_desc);
|
||||
found->user_desc = user_desc;
|
||||
} else if (register_font((unsigned char *) filePath.c_str())) {
|
||||
FontFace face;
|
||||
face.user_desc = user_desc;
|
||||
face.sys_desc = sys_desc;
|
||||
strncpy((char *)face.file_path, (char *) filePath.c_str(), 1023);
|
||||
font_face_list.push_back(face);
|
||||
} else {
|
||||
pango_font_description_free(user_desc);
|
||||
Napi::Error::New(env, "Could not load font to the system's font host").ThrowAsJavaScriptException();
|
||||
|
||||
}
|
||||
} else {
|
||||
pango_font_description_free(user_desc);
|
||||
if (!env.IsExceptionPending()) {
|
||||
Napi::Error::New(env, GENERIC_FACE_ERROR).ThrowAsJavaScriptException();
|
||||
}
|
||||
}
|
||||
|
||||
free(family);
|
||||
free(weight);
|
||||
free(style);
|
||||
fontSerial++;
|
||||
}
|
||||
|
||||
void
|
||||
Canvas::DeregisterAllFonts(const Napi::CallbackInfo& info) {
|
||||
Napi::Env env = info.Env();
|
||||
// Unload all fonts from pango to free up memory
|
||||
bool success = true;
|
||||
|
||||
std::for_each(font_face_list.begin(), font_face_list.end(), [&](FontFace& f) {
|
||||
if (!deregister_font( (unsigned char *)f.file_path )) success = false;
|
||||
pango_font_description_free(f.user_desc);
|
||||
pango_font_description_free(f.sys_desc);
|
||||
});
|
||||
|
||||
font_face_list.clear();
|
||||
fontSerial++;
|
||||
if (!success) Napi::Error::New(env, "Could not deregister one or more fonts").ThrowAsJavaScriptException();
|
||||
}
|
||||
|
||||
/*
|
||||
* Do not use! This is only exported for testing
|
||||
*/
|
||||
Napi::Value
|
||||
Canvas::ParseFont(const Napi::CallbackInfo& info) {
|
||||
Napi::Env env = info.Env();
|
||||
|
||||
if (info.Length() != 1) return env.Undefined();
|
||||
|
||||
Napi::String str;
|
||||
if (!info[0].ToString().UnwrapTo(&str)) return env.Undefined();
|
||||
|
||||
bool ok;
|
||||
auto props = FontParser::parse(str, &ok);
|
||||
if (!ok) return env.Undefined();
|
||||
|
||||
Napi::Object obj = Napi::Object::New(env);
|
||||
obj.Set("size", Napi::Number::New(env, props.fontSize));
|
||||
Napi::Array families = Napi::Array::New(env);
|
||||
obj.Set("families", families);
|
||||
|
||||
unsigned int index = 0;
|
||||
|
||||
for (auto& family : props.fontFamily) {
|
||||
families[index++] = Napi::String::New(env, family);
|
||||
}
|
||||
|
||||
obj.Set("weight", Napi::Number::New(env, props.fontWeight));
|
||||
obj.Set("variant", Napi::Number::New(env, static_cast<int>(props.fontVariant)));
|
||||
obj.Set("style", Napi::Number::New(env, static_cast<int>(props.fontStyle)));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a PangoStyle from a CSS string (like "italic")
|
||||
*/
|
||||
|
||||
PangoStyle
|
||||
Canvas::GetStyleFromCSSString(const char *style) {
|
||||
PangoStyle s = PANGO_STYLE_NORMAL;
|
||||
|
||||
if (strlen(style) > 0) {
|
||||
if (0 == strcmp("italic", style)) {
|
||||
s = PANGO_STYLE_ITALIC;
|
||||
} else if (0 == strcmp("oblique", style)) {
|
||||
s = PANGO_STYLE_OBLIQUE;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a PangoWeight from a CSS string ("bold", "100", etc)
|
||||
*/
|
||||
|
||||
PangoWeight
|
||||
Canvas::GetWeightFromCSSString(const char *weight) {
|
||||
PangoWeight w = PANGO_WEIGHT_NORMAL;
|
||||
|
||||
if (strlen(weight) > 0) {
|
||||
if (0 == strcmp("bold", weight)) {
|
||||
w = PANGO_WEIGHT_BOLD;
|
||||
} else if (0 == strcmp("100", weight)) {
|
||||
w = PANGO_WEIGHT_THIN;
|
||||
} else if (0 == strcmp("200", weight)) {
|
||||
w = PANGO_WEIGHT_ULTRALIGHT;
|
||||
} else if (0 == strcmp("300", weight)) {
|
||||
w = PANGO_WEIGHT_LIGHT;
|
||||
} else if (0 == strcmp("400", weight)) {
|
||||
w = PANGO_WEIGHT_NORMAL;
|
||||
} else if (0 == strcmp("500", weight)) {
|
||||
w = PANGO_WEIGHT_MEDIUM;
|
||||
} else if (0 == strcmp("600", weight)) {
|
||||
w = PANGO_WEIGHT_SEMIBOLD;
|
||||
} else if (0 == strcmp("700", weight)) {
|
||||
w = PANGO_WEIGHT_BOLD;
|
||||
} else if (0 == strcmp("800", weight)) {
|
||||
w = PANGO_WEIGHT_ULTRABOLD;
|
||||
} else if (0 == strcmp("900", weight)) {
|
||||
w = PANGO_WEIGHT_HEAVY;
|
||||
}
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a user description, return a description that will select the
|
||||
* font either from the system or @font-face
|
||||
*/
|
||||
|
||||
PangoFontDescription *
|
||||
Canvas::ResolveFontDescription(const PangoFontDescription *desc) {
|
||||
// One of the user-specified families could map to multiple SFNT family names
|
||||
// if someone registered two different fonts under the same family name.
|
||||
// https://drafts.csswg.org/css-fonts-3/#font-style-matching
|
||||
FontFace best;
|
||||
istringstream families(pango_font_description_get_family(desc));
|
||||
unordered_set<string> seen_families;
|
||||
string resolved_families;
|
||||
bool first = true;
|
||||
|
||||
for (string family; getline(families, family, ','); ) {
|
||||
string renamed_families;
|
||||
for (auto& ff : font_face_list) {
|
||||
string pangofamily = string(pango_font_description_get_family(ff.user_desc));
|
||||
if (streq_casein(family, pangofamily)) {
|
||||
const char* sys_desc_family_name = pango_font_description_get_family(ff.sys_desc);
|
||||
bool unseen = seen_families.find(sys_desc_family_name) == seen_families.end();
|
||||
bool better = best.user_desc == nullptr || pango_font_description_better_match(desc, best.user_desc, ff.user_desc);
|
||||
|
||||
// Avoid sending duplicate SFNT font names due to a bug in Pango for macOS:
|
||||
// https://bugzilla.gnome.org/show_bug.cgi?id=762873
|
||||
if (unseen) {
|
||||
seen_families.insert(sys_desc_family_name);
|
||||
|
||||
if (better) {
|
||||
renamed_families = string(sys_desc_family_name) + (renamed_families.size() ? "," : "") + renamed_families;
|
||||
} else {
|
||||
renamed_families = renamed_families + (renamed_families.size() ? "," : "") + sys_desc_family_name;
|
||||
}
|
||||
}
|
||||
|
||||
if (first && better) best = ff;
|
||||
}
|
||||
}
|
||||
|
||||
if (resolved_families.size()) resolved_families += ',';
|
||||
resolved_families += renamed_families.size() ? renamed_families : family;
|
||||
first = false;
|
||||
}
|
||||
|
||||
PangoFontDescription* ret = pango_font_description_copy(best.sys_desc ? best.sys_desc : desc);
|
||||
pango_font_description_set_family(ret, resolved_families.c_str());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-alloc the surface, destroying the previous.
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::resurface(Napi::Object This) {
|
||||
Napi::HandleScope scope(env);
|
||||
Napi::Value context;
|
||||
|
||||
if (This.Get("context").UnwrapTo(&context) && context.IsObject()) {
|
||||
backend()->recreateSurface();
|
||||
// Reset context
|
||||
Context2d *context2d = Context2d::Unwrap(context.As<Napi::Object>());
|
||||
cairo_t *prev = context2d->context();
|
||||
context2d->setContext(createCairoContext());
|
||||
context2d->resetState();
|
||||
cairo_destroy(prev);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around cairo_create()
|
||||
* (do not call cairo_create directly, call this instead)
|
||||
*/
|
||||
cairo_t*
|
||||
Canvas::createCairoContext() {
|
||||
cairo_t* ret = cairo_create(surface());
|
||||
cairo_set_line_width(ret, 1); // Cairo defaults to 2
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct an Error from the given cairo status.
|
||||
*/
|
||||
|
||||
Napi::Error
|
||||
Canvas::CairoError(cairo_status_t status) {
|
||||
return Napi::Error::New(env, cairo_status_to_string(status));
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
struct Closure;
|
||||
|
||||
#include "backend/Backend.h"
|
||||
#include "closure.h"
|
||||
#include <cairo.h>
|
||||
#include "dll_visibility.h"
|
||||
#include <napi.h>
|
||||
#include <pango/pangocairo.h>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
|
||||
/*
|
||||
* FontFace describes a font file in terms of one PangoFontDescription that
|
||||
* will resolve to it and one that the user describes it as (like @font-face)
|
||||
*/
|
||||
class FontFace {
|
||||
public:
|
||||
PangoFontDescription *sys_desc = nullptr;
|
||||
PangoFontDescription *user_desc = nullptr;
|
||||
unsigned char file_path[1024];
|
||||
};
|
||||
|
||||
enum text_baseline_t : uint8_t {
|
||||
TEXT_BASELINE_ALPHABETIC = 0,
|
||||
TEXT_BASELINE_TOP = 1,
|
||||
TEXT_BASELINE_BOTTOM = 2,
|
||||
TEXT_BASELINE_MIDDLE = 3,
|
||||
TEXT_BASELINE_IDEOGRAPHIC = 4,
|
||||
TEXT_BASELINE_HANGING = 5
|
||||
};
|
||||
|
||||
enum text_align_t : int8_t {
|
||||
TEXT_ALIGNMENT_LEFT = -1,
|
||||
TEXT_ALIGNMENT_CENTER = 0,
|
||||
TEXT_ALIGNMENT_RIGHT = 1,
|
||||
TEXT_ALIGNMENT_START = -2,
|
||||
TEXT_ALIGNMENT_END = 2
|
||||
};
|
||||
|
||||
enum canvas_draw_mode_t : uint8_t {
|
||||
TEXT_DRAW_PATHS,
|
||||
TEXT_DRAW_GLYPHS
|
||||
};
|
||||
|
||||
/*
|
||||
* Canvas.
|
||||
*/
|
||||
|
||||
class Canvas : public Napi::ObjectWrap<Canvas> {
|
||||
public:
|
||||
Canvas(const Napi::CallbackInfo& info);
|
||||
static void Initialize(Napi::Env& env, Napi::Object& target);
|
||||
|
||||
Napi::Value ToBuffer(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetType(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetStride(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetWidth(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetHeight(const Napi::CallbackInfo& info);
|
||||
void SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void StreamPNGSync(const Napi::CallbackInfo& info);
|
||||
void StreamPDFSync(const Napi::CallbackInfo& info);
|
||||
void StreamJPEGSync(const Napi::CallbackInfo& info);
|
||||
static void RegisterFont(const Napi::CallbackInfo& info);
|
||||
static void DeregisterAllFonts(const Napi::CallbackInfo& info);
|
||||
static Napi::Value ParseFont(const Napi::CallbackInfo& info);
|
||||
Napi::Error CairoError(cairo_status_t status);
|
||||
static void ToPngBufferAsync(Closure* closure);
|
||||
static void ToJpegBufferAsync(Closure* closure);
|
||||
static PangoWeight GetWeightFromCSSString(const char *weight);
|
||||
static PangoStyle GetStyleFromCSSString(const char *style);
|
||||
static PangoFontDescription *ResolveFontDescription(const PangoFontDescription *desc);
|
||||
|
||||
DLL_PUBLIC inline Backend* backend() { return _backend; }
|
||||
DLL_PUBLIC inline cairo_surface_t* surface(){ return backend()->getSurface(); }
|
||||
cairo_t* createCairoContext();
|
||||
|
||||
DLL_PUBLIC inline uint8_t *data(){ return cairo_image_surface_get_data(surface()); }
|
||||
DLL_PUBLIC inline int stride(){ return cairo_image_surface_get_stride(surface()); }
|
||||
DLL_PUBLIC inline std::size_t nBytes(){
|
||||
return static_cast<std::size_t>(backend()->getHeight()) * stride();
|
||||
}
|
||||
|
||||
DLL_PUBLIC inline int getWidth() { return backend()->getWidth(); }
|
||||
DLL_PUBLIC inline int getHeight() { return backend()->getHeight(); }
|
||||
|
||||
void resurface(Napi::Object This);
|
||||
|
||||
Napi::Env env;
|
||||
static int fontSerial;
|
||||
|
||||
private:
|
||||
Backend* _backend;
|
||||
Napi::ObjectReference _jsBackend;
|
||||
Napi::FunctionReference ctor;
|
||||
static std::vector<FontFace> font_face_list;
|
||||
};
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <napi.h>
|
||||
|
||||
class CanvasError {
|
||||
public:
|
||||
std::string message;
|
||||
std::string syscall;
|
||||
std::string path;
|
||||
int cerrno = 0;
|
||||
void set(const char* iMessage = NULL, const char* iSyscall = NULL, int iErrno = 0, const char* iPath = NULL) {
|
||||
if (iMessage) message.assign(iMessage);
|
||||
if (iSyscall) syscall.assign(iSyscall);
|
||||
cerrno = iErrno;
|
||||
if (iPath) path.assign(iPath);
|
||||
}
|
||||
void reset() {
|
||||
message.clear();
|
||||
syscall.clear();
|
||||
path.clear();
|
||||
cerrno = 0;
|
||||
}
|
||||
bool empty() {
|
||||
return cerrno == 0 && message.empty();
|
||||
}
|
||||
Napi::Error toError(Napi::Env env) {
|
||||
if (cerrno) {
|
||||
Napi::Error err = Napi::Error::New(env, strerror(cerrno));
|
||||
if (!syscall.empty()) err.Value().Set("syscall", syscall);
|
||||
if (!path.empty()) err.Value().Set("path", path);
|
||||
return err;
|
||||
} else {
|
||||
return Napi::Error::New(env, message);
|
||||
}
|
||||
}
|
||||
};
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#include "CanvasGradient.h"
|
||||
#include "InstanceData.h"
|
||||
|
||||
#include "Canvas.h"
|
||||
#include "color.h"
|
||||
|
||||
using namespace Napi;
|
||||
|
||||
/*
|
||||
* Initialize CanvasGradient.
|
||||
*/
|
||||
|
||||
void
|
||||
Gradient::Initialize(Napi::Env& env, Napi::Object& exports) {
|
||||
Napi::HandleScope scope(env);
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
|
||||
Napi::Function ctor = DefineClass(env, "CanvasGradient", {
|
||||
InstanceMethod<&Gradient::AddColorStop>("addColorStop", napi_default_method)
|
||||
});
|
||||
|
||||
exports.Set("CanvasGradient", ctor);
|
||||
data->CanvasGradientCtor = Napi::Persistent(ctor);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a new CanvasGradient.
|
||||
*/
|
||||
|
||||
Gradient::Gradient(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Gradient>(info), env(info.Env()) {
|
||||
// Linear
|
||||
if (
|
||||
4 == info.Length() &&
|
||||
info[0].IsNumber() &&
|
||||
info[1].IsNumber() &&
|
||||
info[2].IsNumber() &&
|
||||
info[3].IsNumber()
|
||||
) {
|
||||
double x0 = info[0].As<Napi::Number>().DoubleValue();
|
||||
double y0 = info[1].As<Napi::Number>().DoubleValue();
|
||||
double x1 = info[2].As<Napi::Number>().DoubleValue();
|
||||
double y1 = info[3].As<Napi::Number>().DoubleValue();
|
||||
_pattern = cairo_pattern_create_linear(x0, y0, x1, y1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Radial
|
||||
if (
|
||||
6 == info.Length() &&
|
||||
info[0].IsNumber() &&
|
||||
info[1].IsNumber() &&
|
||||
info[2].IsNumber() &&
|
||||
info[3].IsNumber() &&
|
||||
info[4].IsNumber() &&
|
||||
info[5].IsNumber()
|
||||
) {
|
||||
double x0 = info[0].As<Napi::Number>().DoubleValue();
|
||||
double y0 = info[1].As<Napi::Number>().DoubleValue();
|
||||
double r0 = info[2].As<Napi::Number>().DoubleValue();
|
||||
double x1 = info[3].As<Napi::Number>().DoubleValue();
|
||||
double y1 = info[4].As<Napi::Number>().DoubleValue();
|
||||
double r1 = info[5].As<Napi::Number>().DoubleValue();
|
||||
_pattern = cairo_pattern_create_radial(x0, y0, r0, x1, y1, r1);
|
||||
return;
|
||||
}
|
||||
|
||||
Napi::TypeError::New(env, "invalid arguments").ThrowAsJavaScriptException();
|
||||
}
|
||||
|
||||
/*
|
||||
* Add color stop.
|
||||
*/
|
||||
|
||||
void
|
||||
Gradient::AddColorStop(const Napi::CallbackInfo& info) {
|
||||
if (!info[0].IsNumber()) {
|
||||
Napi::TypeError::New(env, "offset required").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!info[1].IsString()) {
|
||||
Napi::TypeError::New(env, "color string required").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
short ok;
|
||||
std::string str = info[1].As<Napi::String>();
|
||||
uint32_t rgba = rgba_from_string(str.c_str(), &ok);
|
||||
|
||||
if (ok) {
|
||||
rgba_t color = rgba_create(rgba);
|
||||
cairo_pattern_add_color_stop_rgba(
|
||||
_pattern
|
||||
, info[0].As<Napi::Number>().DoubleValue()
|
||||
, color.r
|
||||
, color.g
|
||||
, color.b
|
||||
, color.a);
|
||||
} else {
|
||||
Napi::TypeError::New(env, "parse color failed").ThrowAsJavaScriptException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Destroy the pattern.
|
||||
*/
|
||||
|
||||
Gradient::~Gradient() {
|
||||
if (_pattern) cairo_pattern_destroy(_pattern);
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <napi.h>
|
||||
#include <cairo.h>
|
||||
|
||||
class Gradient : public Napi::ObjectWrap<Gradient> {
|
||||
public:
|
||||
static void Initialize(Napi::Env& env, Napi::Object& target);
|
||||
Gradient(const Napi::CallbackInfo& info);
|
||||
void AddColorStop(const Napi::CallbackInfo& info);
|
||||
inline cairo_pattern_t *pattern(){ return _pattern; }
|
||||
~Gradient();
|
||||
|
||||
Napi::Env env;
|
||||
|
||||
private:
|
||||
cairo_pattern_t *_pattern;
|
||||
};
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#include "CanvasPattern.h"
|
||||
|
||||
#include "Canvas.h"
|
||||
#include "Image.h"
|
||||
#include "InstanceData.h"
|
||||
|
||||
using namespace Napi;
|
||||
|
||||
const cairo_user_data_key_t *pattern_repeat_key;
|
||||
|
||||
/*
|
||||
* Initialize CanvasPattern.
|
||||
*/
|
||||
|
||||
void
|
||||
Pattern::Initialize(Napi::Env& env, Napi::Object& exports) {
|
||||
Napi::HandleScope scope(env);
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
|
||||
// Constructor
|
||||
Napi::Function ctor = DefineClass(env, "CanvasPattern", {
|
||||
InstanceMethod<&Pattern::setTransform>("setTransform", napi_default_method)
|
||||
});
|
||||
|
||||
// Prototype
|
||||
exports.Set("CanvasPattern", ctor);
|
||||
data->CanvasPatternCtor = Napi::Persistent(ctor);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a new CanvasPattern.
|
||||
*/
|
||||
|
||||
Pattern::Pattern(const Napi::CallbackInfo& info) : ObjectWrap<Pattern>(info), env(info.Env()) {
|
||||
if (!info[0].IsObject()) {
|
||||
Napi::TypeError::New(env, "Image or Canvas expected").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
Napi::Object obj = info[0].As<Napi::Object>();
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
cairo_surface_t *surface;
|
||||
|
||||
// Image
|
||||
if (obj.InstanceOf(data->ImageCtor.Value()).UnwrapOr(false)) {
|
||||
Image *img = Image::Unwrap(obj);
|
||||
if (!img->isComplete()) {
|
||||
Napi::Error::New(env, "Image given has not completed loading").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
surface = img->surface();
|
||||
|
||||
// Canvas
|
||||
} else if (obj.InstanceOf(data->CanvasCtor.Value()).UnwrapOr(false)) {
|
||||
Canvas *canvas = Canvas::Unwrap(obj);
|
||||
surface = canvas->surface();
|
||||
// Invalid
|
||||
} else {
|
||||
if (!env.IsExceptionPending()) {
|
||||
Napi::TypeError::New(env, "Image or Canvas expected").ThrowAsJavaScriptException();
|
||||
}
|
||||
return;
|
||||
}
|
||||
_pattern = cairo_pattern_create_for_surface(surface);
|
||||
|
||||
if (info[1].IsString()) {
|
||||
if ("no-repeat" == info[1].As<Napi::String>().Utf8Value()) {
|
||||
_repeat = NO_REPEAT;
|
||||
} else if ("repeat-x" == info[1].As<Napi::String>().Utf8Value()) {
|
||||
_repeat = REPEAT_X;
|
||||
} else if ("repeat-y" == info[1].As<Napi::String>().Utf8Value()) {
|
||||
_repeat = REPEAT_Y;
|
||||
}
|
||||
}
|
||||
|
||||
cairo_pattern_set_user_data(_pattern, pattern_repeat_key, &_repeat, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the pattern-space to user-space transform.
|
||||
*/
|
||||
void
|
||||
Pattern::setTransform(const Napi::CallbackInfo& info) {
|
||||
if (!info[0].IsObject()) {
|
||||
Napi::TypeError::New(env, "Expected DOMMatrix").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
Napi::Object mat = info[0].As<Napi::Object>();
|
||||
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
if (!mat.InstanceOf(data->DOMMatrixCtor.Value()).UnwrapOr(false)) {
|
||||
if (!env.IsExceptionPending()) {
|
||||
Napi::TypeError::New(env, "Expected DOMMatrix").ThrowAsJavaScriptException();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Napi::Value one = Napi::Number::New(env, 1);
|
||||
Napi::Value zero = Napi::Number::New(env, 0);
|
||||
|
||||
cairo_matrix_t matrix;
|
||||
cairo_matrix_init(&matrix,
|
||||
mat.Get("a").UnwrapOr(one).As<Napi::Number>().DoubleValue(),
|
||||
mat.Get("b").UnwrapOr(zero).As<Napi::Number>().DoubleValue(),
|
||||
mat.Get("c").UnwrapOr(zero).As<Napi::Number>().DoubleValue(),
|
||||
mat.Get("d").UnwrapOr(one).As<Napi::Number>().DoubleValue(),
|
||||
mat.Get("e").UnwrapOr(zero).As<Napi::Number>().DoubleValue(),
|
||||
mat.Get("f").UnwrapOr(zero).As<Napi::Number>().DoubleValue()
|
||||
);
|
||||
|
||||
cairo_matrix_invert(&matrix);
|
||||
cairo_pattern_set_matrix(_pattern, &matrix);
|
||||
}
|
||||
|
||||
repeat_type_t Pattern::get_repeat_type_for_cairo_pattern(cairo_pattern_t *pattern) {
|
||||
void *ud = cairo_pattern_get_user_data(pattern, pattern_repeat_key);
|
||||
return *reinterpret_cast<repeat_type_t*>(ud);
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy the pattern.
|
||||
*/
|
||||
|
||||
Pattern::~Pattern() {
|
||||
if (_pattern) cairo_pattern_destroy(_pattern);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2011 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cairo.h>
|
||||
#include <napi.h>
|
||||
|
||||
/*
|
||||
* Canvas types.
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
NO_REPEAT, // match CAIRO_EXTEND_NONE
|
||||
REPEAT, // match CAIRO_EXTEND_REPEAT
|
||||
REPEAT_X, // needs custom processing
|
||||
REPEAT_Y // needs custom processing
|
||||
} repeat_type_t;
|
||||
|
||||
extern const cairo_user_data_key_t *pattern_repeat_key;
|
||||
|
||||
class Pattern : public Napi::ObjectWrap<Pattern> {
|
||||
public:
|
||||
Pattern(const Napi::CallbackInfo& info);
|
||||
static void Initialize(Napi::Env& env, Napi::Object& target);
|
||||
void setTransform(const Napi::CallbackInfo& info);
|
||||
static repeat_type_t get_repeat_type_for_cairo_pattern(cairo_pattern_t *pattern);
|
||||
inline cairo_pattern_t *pattern(){ return _pattern; }
|
||||
~Pattern();
|
||||
Napi::Env env;
|
||||
private:
|
||||
cairo_pattern_t *_pattern;
|
||||
repeat_type_t _repeat = REPEAT;
|
||||
};
|
||||
+3468
File diff suppressed because it is too large.
Load diff
+232
@@ -0,0 +1,232 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cairo.h"
|
||||
#include "Canvas.h"
|
||||
#include "color.h"
|
||||
#include "napi.h"
|
||||
#include <pango/pangocairo.h>
|
||||
#include <stack>
|
||||
|
||||
/*
|
||||
* State struct.
|
||||
*
|
||||
* Used in conjunction with Save() / Restore() since
|
||||
* cairo's gstate maintains only a single source pattern at a time.
|
||||
*/
|
||||
|
||||
struct canvas_state_t {
|
||||
rgba_t fill = { 0, 0, 0, 1 };
|
||||
rgba_t stroke = { 0, 0, 0, 1 };
|
||||
rgba_t shadow = { 0, 0, 0, 0 };
|
||||
double shadowOffsetX = 0.;
|
||||
double shadowOffsetY = 0.;
|
||||
cairo_pattern_t* fillPattern = nullptr;
|
||||
cairo_pattern_t* strokePattern = nullptr;
|
||||
cairo_pattern_t* fillGradient = nullptr;
|
||||
cairo_pattern_t* strokeGradient = nullptr;
|
||||
PangoFontDescription* fontDescription = nullptr;
|
||||
std::string font = "10px sans-serif";
|
||||
cairo_filter_t patternQuality = CAIRO_FILTER_GOOD;
|
||||
float globalAlpha = 1.f;
|
||||
int shadowBlur = 0;
|
||||
text_align_t textAlignment = TEXT_ALIGNMENT_START;
|
||||
text_baseline_t textBaseline = TEXT_BASELINE_ALPHABETIC;
|
||||
canvas_draw_mode_t textDrawingMode = TEXT_DRAW_PATHS;
|
||||
bool imageSmoothingEnabled = true;
|
||||
std::string direction = "ltr";
|
||||
|
||||
canvas_state_t() {
|
||||
fontDescription = pango_font_description_from_string("sans");
|
||||
pango_font_description_set_absolute_size(fontDescription, 10 * PANGO_SCALE);
|
||||
}
|
||||
|
||||
canvas_state_t(const canvas_state_t& other) {
|
||||
fill = other.fill;
|
||||
stroke = other.stroke;
|
||||
patternQuality = other.patternQuality;
|
||||
fillPattern = other.fillPattern;
|
||||
strokePattern = other.strokePattern;
|
||||
fillGradient = other.fillGradient;
|
||||
strokeGradient = other.strokeGradient;
|
||||
globalAlpha = other.globalAlpha;
|
||||
textAlignment = other.textAlignment;
|
||||
textBaseline = other.textBaseline;
|
||||
shadow = other.shadow;
|
||||
shadowBlur = other.shadowBlur;
|
||||
shadowOffsetX = other.shadowOffsetX;
|
||||
shadowOffsetY = other.shadowOffsetY;
|
||||
textDrawingMode = other.textDrawingMode;
|
||||
fontDescription = pango_font_description_copy(other.fontDescription);
|
||||
font = other.font;
|
||||
imageSmoothingEnabled = other.imageSmoothingEnabled;
|
||||
}
|
||||
|
||||
~canvas_state_t() {
|
||||
pango_font_description_free(fontDescription);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Equivalent to a PangoRectangle but holds floats instead of ints
|
||||
* (software pixels are stored here instead of pango units)
|
||||
*
|
||||
* Should be compatible with PANGO_ASCENT, PANGO_LBEARING, etc.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
float width;
|
||||
float height;
|
||||
} float_rectangle;
|
||||
|
||||
class Context2d : public Napi::ObjectWrap<Context2d> {
|
||||
public:
|
||||
std::stack<canvas_state_t> states;
|
||||
canvas_state_t *state;
|
||||
Context2d(const Napi::CallbackInfo& info);
|
||||
static void Initialize(Napi::Env& env, Napi::Object& target);
|
||||
void DrawImage(const Napi::CallbackInfo& info);
|
||||
void PutImageData(const Napi::CallbackInfo& info);
|
||||
void Save(const Napi::CallbackInfo& info);
|
||||
void Restore(const Napi::CallbackInfo& info);
|
||||
void Rotate(const Napi::CallbackInfo& info);
|
||||
void Translate(const Napi::CallbackInfo& info);
|
||||
void Scale(const Napi::CallbackInfo& info);
|
||||
void Transform(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetTransform(const Napi::CallbackInfo& info);
|
||||
void ResetTransform(const Napi::CallbackInfo& info);
|
||||
void SetTransform(const Napi::CallbackInfo& info);
|
||||
Napi::Value IsPointInPath(const Napi::CallbackInfo& info);
|
||||
void BeginPath(const Napi::CallbackInfo& info);
|
||||
void ClosePath(const Napi::CallbackInfo& info);
|
||||
void AddPage(const Napi::CallbackInfo& info);
|
||||
void Clip(const Napi::CallbackInfo& info);
|
||||
void Fill(const Napi::CallbackInfo& info);
|
||||
void Stroke(const Napi::CallbackInfo& info);
|
||||
void FillText(const Napi::CallbackInfo& info);
|
||||
void StrokeText(const Napi::CallbackInfo& info);
|
||||
static Napi::Value SetFont(const Napi::CallbackInfo& info);
|
||||
static Napi::Value SetFillColor(const Napi::CallbackInfo& info);
|
||||
static Napi::Value SetStrokeColor(const Napi::CallbackInfo& info);
|
||||
static Napi::Value SetStrokePattern(const Napi::CallbackInfo& info);
|
||||
static Napi::Value SetTextAlignment(const Napi::CallbackInfo& info);
|
||||
void SetLineDash(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetLineDash(const Napi::CallbackInfo& info);
|
||||
Napi::Value MeasureText(const Napi::CallbackInfo& info);
|
||||
void BezierCurveTo(const Napi::CallbackInfo& info);
|
||||
void QuadraticCurveTo(const Napi::CallbackInfo& info);
|
||||
void LineTo(const Napi::CallbackInfo& info);
|
||||
void MoveTo(const Napi::CallbackInfo& info);
|
||||
void FillRect(const Napi::CallbackInfo& info);
|
||||
void StrokeRect(const Napi::CallbackInfo& info);
|
||||
void ClearRect(const Napi::CallbackInfo& info);
|
||||
void Rect(const Napi::CallbackInfo& info);
|
||||
void RoundRect(const Napi::CallbackInfo& info);
|
||||
void Arc(const Napi::CallbackInfo& info);
|
||||
void ArcTo(const Napi::CallbackInfo& info);
|
||||
void Ellipse(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetImageData(const Napi::CallbackInfo& info);
|
||||
Napi::Value CreateImageData(const Napi::CallbackInfo& info);
|
||||
static Napi::Value GetStrokeColor(const Napi::CallbackInfo& info);
|
||||
Napi::Value CreatePattern(const Napi::CallbackInfo& info);
|
||||
Napi::Value CreateLinearGradient(const Napi::CallbackInfo& info);
|
||||
Napi::Value CreateRadialGradient(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetFormat(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetPatternQuality(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetImageSmoothingEnabled(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetGlobalCompositeOperation(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetGlobalAlpha(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetShadowColor(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetMiterLimit(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetLineCap(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetLineJoin(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetLineWidth(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetLineDashOffset(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetShadowOffsetX(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetShadowOffsetY(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetShadowBlur(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetAntiAlias(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetTextDrawingMode(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetQuality(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetCurrentTransform(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetFillStyle(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetStrokeStyle(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetFont(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetTextBaseline(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetTextAlign(const Napi::CallbackInfo& info);
|
||||
void SetPatternQuality(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetImageSmoothingEnabled(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetGlobalCompositeOperation(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetGlobalAlpha(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetShadowColor(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetMiterLimit(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetLineCap(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetLineJoin(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetLineWidth(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetLineDashOffset(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetShadowOffsetX(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetShadowOffsetY(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetShadowBlur(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetAntiAlias(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetTextDrawingMode(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetQuality(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetCurrentTransform(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetFillStyle(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetStrokeStyle(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetFont(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetTextBaseline(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetTextAlign(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
|
||||
void BeginTag(const Napi::CallbackInfo& info);
|
||||
void EndTag(const Napi::CallbackInfo& info);
|
||||
#endif
|
||||
Napi::Value GetDirection(const Napi::CallbackInfo& info);
|
||||
void SetDirection(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
inline void setContext(cairo_t *ctx) { _context = ctx; }
|
||||
inline cairo_t *context(){ return _context; }
|
||||
inline Canvas *canvas(){ return _canvas; }
|
||||
inline bool hasShadow();
|
||||
void inline setSourceRGBA(rgba_t color);
|
||||
void inline setSourceRGBA(cairo_t *ctx, rgba_t color);
|
||||
void setTextPath(double x, double y);
|
||||
void blur(cairo_surface_t *surface, int radius);
|
||||
void shadow(void (fn)(cairo_t *cr));
|
||||
void shadowStart();
|
||||
void shadowApply();
|
||||
void savePath();
|
||||
void restorePath();
|
||||
void saveState();
|
||||
void restoreState();
|
||||
void inline setFillRule(Napi::Value value);
|
||||
void fill(bool preserve = false);
|
||||
void stroke(bool preserve = false);
|
||||
void save();
|
||||
void restore();
|
||||
void setFontFromState();
|
||||
void resetState();
|
||||
inline PangoLayout *layout(){ return _layout; }
|
||||
~Context2d();
|
||||
Napi::Env env;
|
||||
|
||||
private:
|
||||
void _resetPersistentHandles();
|
||||
Napi::Value _getFillColor();
|
||||
Napi::Value _getStrokeColor();
|
||||
Napi::Value get_current_transform();
|
||||
void _setFillColor(Napi::Value arg);
|
||||
void _setFillPattern(Napi::Value arg);
|
||||
void _setStrokeColor(Napi::Value arg);
|
||||
void _setStrokePattern(Napi::Value arg);
|
||||
void checkFonts();
|
||||
void paintText(const Napi::CallbackInfo&, bool);
|
||||
Napi::Reference<Napi::Value> _fillStyle;
|
||||
Napi::Reference<Napi::Value> _strokeStyle;
|
||||
Canvas *_canvas;
|
||||
cairo_t *_context = nullptr;
|
||||
cairo_path_t *_path;
|
||||
PangoLayout *_layout = nullptr;
|
||||
int fontSerial = 1;
|
||||
};
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
// This is used for classifying characters according to the definition of tokens
|
||||
// in the CSS standards, but could be extended for any other future uses
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CharData {
|
||||
static constexpr uint8_t Whitespace = 0x1;
|
||||
static constexpr uint8_t Newline = 0x2;
|
||||
static constexpr uint8_t Hex = 0x4;
|
||||
static constexpr uint8_t Nmstart = 0x8;
|
||||
static constexpr uint8_t Nmchar = 0x10;
|
||||
static constexpr uint8_t Sign = 0x20;
|
||||
static constexpr uint8_t Digit = 0x40;
|
||||
static constexpr uint8_t NumStart = 0x80;
|
||||
};
|
||||
|
||||
using namespace CharData;
|
||||
|
||||
constexpr const uint8_t charData[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-8
|
||||
Whitespace, // 9 (HT)
|
||||
Whitespace | Newline, // 10 (LF)
|
||||
0, // 11 (VT)
|
||||
Whitespace | Newline, // 12 (FF)
|
||||
Whitespace | Newline, // 13 (CR)
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 14-31
|
||||
Whitespace, // 32 (Space)
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 33-42
|
||||
Sign | NumStart, // 43 (+)
|
||||
0, // 44
|
||||
Nmchar | Sign | NumStart, // 45 (-)
|
||||
0, 0, // 46-47
|
||||
Nmchar | Digit | NumStart | Hex, // 48 (0)
|
||||
Nmchar | Digit | NumStart | Hex, // 49 (1)
|
||||
Nmchar | Digit | NumStart | Hex, // 50 (2)
|
||||
Nmchar | Digit | NumStart | Hex, // 51 (3)
|
||||
Nmchar | Digit | NumStart | Hex, // 52 (4)
|
||||
Nmchar | Digit | NumStart | Hex, // 53 (5)
|
||||
Nmchar | Digit | NumStart | Hex, // 54 (6)
|
||||
Nmchar | Digit | NumStart | Hex, // 55 (7)
|
||||
Nmchar | Digit | NumStart | Hex, // 56 (8)
|
||||
Nmchar | Digit | NumStart | Hex, // 57 (9)
|
||||
0, 0, 0, 0, 0, 0, 0, // 58-64
|
||||
Nmstart | Nmchar | Hex, // 65 (A)
|
||||
Nmstart | Nmchar | Hex, // 66 (B)
|
||||
Nmstart | Nmchar | Hex, // 67 (C)
|
||||
Nmstart | Nmchar | Hex, // 68 (D)
|
||||
Nmstart | Nmchar | Hex, // 69 (E)
|
||||
Nmstart | Nmchar | Hex, // 70 (F)
|
||||
Nmstart | Nmchar, // 71 (G)
|
||||
Nmstart | Nmchar, // 72 (H)
|
||||
Nmstart | Nmchar, // 73 (I)
|
||||
Nmstart | Nmchar, // 74 (J)
|
||||
Nmstart | Nmchar, // 75 (K)
|
||||
Nmstart | Nmchar, // 76 (L)
|
||||
Nmstart | Nmchar, // 77 (M)
|
||||
Nmstart | Nmchar, // 78 (N)
|
||||
Nmstart | Nmchar, // 79 (O)
|
||||
Nmstart | Nmchar, // 80 (P)
|
||||
Nmstart | Nmchar, // 81 (Q)
|
||||
Nmstart | Nmchar, // 82 (R)
|
||||
Nmstart | Nmchar, // 83 (S)
|
||||
Nmstart | Nmchar, // 84 (T)
|
||||
Nmstart | Nmchar, // 85 (U)
|
||||
Nmstart | Nmchar, // 86 (V)
|
||||
Nmstart | Nmchar, // 87 (W)
|
||||
Nmstart | Nmchar, // 88 (X)
|
||||
Nmstart | Nmchar, // 89 (Y)
|
||||
Nmstart | Nmchar, // 90 (Z)
|
||||
0, // 91
|
||||
Nmstart, // 92 (\)
|
||||
0, 0, // 93-94
|
||||
Nmstart | Nmchar, // 95 (_)
|
||||
0, // 96
|
||||
Nmstart | Nmchar | Hex, // 97 (a)
|
||||
Nmstart | Nmchar | Hex, // 98 (b)
|
||||
Nmstart | Nmchar | Hex, // 99 (c)
|
||||
Nmstart | Nmchar | Hex, // 100 (d)
|
||||
Nmstart | Nmchar | Hex, // 101 (e)
|
||||
Nmstart | Nmchar | Hex, // 102 (f)
|
||||
Nmstart | Nmchar, // 103 (g)
|
||||
Nmstart | Nmchar, // 104 (h)
|
||||
Nmstart | Nmchar, // 105 (i)
|
||||
Nmstart | Nmchar, // 106 (j)
|
||||
Nmstart | Nmchar, // 107 (k)
|
||||
Nmstart | Nmchar, // 108 (l)
|
||||
Nmstart | Nmchar, // 109 (m)
|
||||
Nmstart | Nmchar, // 110 (n)
|
||||
Nmstart | Nmchar, // 111 (o)
|
||||
Nmstart | Nmchar, // 112 (p)
|
||||
Nmstart | Nmchar, // 113 (q)
|
||||
Nmstart | Nmchar, // 114 (r)
|
||||
Nmstart | Nmchar, // 115 (s)
|
||||
Nmstart | Nmchar, // 116 (t)
|
||||
Nmstart | Nmchar, // 117 (u)
|
||||
Nmstart | Nmchar, // 118 (v)
|
||||
Nmstart | Nmchar, // 119 (w)
|
||||
Nmstart | Nmchar, // 120 (x)
|
||||
Nmstart | Nmchar, // 121 (y)
|
||||
Nmstart | Nmchar, // 122 (z)
|
||||
0, 0, 0, 0, 0, // 123-127
|
||||
// Non-ASCII
|
||||
Nmstart | Nmchar, // 128
|
||||
Nmstart | Nmchar, // 129
|
||||
Nmstart | Nmchar, // 130
|
||||
Nmstart | Nmchar, // 131
|
||||
Nmstart | Nmchar, // 132
|
||||
Nmstart | Nmchar, // 133
|
||||
Nmstart | Nmchar, // 134
|
||||
Nmstart | Nmchar, // 135
|
||||
Nmstart | Nmchar, // 136
|
||||
Nmstart | Nmchar, // 137
|
||||
Nmstart | Nmchar, // 138
|
||||
Nmstart | Nmchar, // 139
|
||||
Nmstart | Nmchar, // 140
|
||||
Nmstart | Nmchar, // 141
|
||||
Nmstart | Nmchar, // 142
|
||||
Nmstart | Nmchar, // 143
|
||||
Nmstart | Nmchar, // 144
|
||||
Nmstart | Nmchar, // 145
|
||||
Nmstart | Nmchar, // 146
|
||||
Nmstart | Nmchar, // 147
|
||||
Nmstart | Nmchar, // 148
|
||||
Nmstart | Nmchar, // 149
|
||||
Nmstart | Nmchar, // 150
|
||||
Nmstart | Nmchar, // 151
|
||||
Nmstart | Nmchar, // 152
|
||||
Nmstart | Nmchar, // 153
|
||||
Nmstart | Nmchar, // 154
|
||||
Nmstart | Nmchar, // 155
|
||||
Nmstart | Nmchar, // 156
|
||||
Nmstart | Nmchar, // 157
|
||||
Nmstart | Nmchar, // 158
|
||||
Nmstart | Nmchar, // 159
|
||||
Nmstart | Nmchar, // 160
|
||||
Nmstart | Nmchar, // 161
|
||||
Nmstart | Nmchar, // 162
|
||||
Nmstart | Nmchar, // 163
|
||||
Nmstart | Nmchar, // 164
|
||||
Nmstart | Nmchar, // 165
|
||||
Nmstart | Nmchar, // 166
|
||||
Nmstart | Nmchar, // 167
|
||||
Nmstart | Nmchar, // 168
|
||||
Nmstart | Nmchar, // 169
|
||||
Nmstart | Nmchar, // 170
|
||||
Nmstart | Nmchar, // 171
|
||||
Nmstart | Nmchar, // 172
|
||||
Nmstart | Nmchar, // 173
|
||||
Nmstart | Nmchar, // 174
|
||||
Nmstart | Nmchar, // 175
|
||||
Nmstart | Nmchar, // 176
|
||||
Nmstart | Nmchar, // 177
|
||||
Nmstart | Nmchar, // 178
|
||||
Nmstart | Nmchar, // 179
|
||||
Nmstart | Nmchar, // 180
|
||||
Nmstart | Nmchar, // 181
|
||||
Nmstart | Nmchar, // 182
|
||||
Nmstart | Nmchar, // 183
|
||||
Nmstart | Nmchar, // 184
|
||||
Nmstart | Nmchar, // 185
|
||||
Nmstart | Nmchar, // 186
|
||||
Nmstart | Nmchar, // 187
|
||||
Nmstart | Nmchar, // 188
|
||||
Nmstart | Nmchar, // 189
|
||||
Nmstart | Nmchar, // 190
|
||||
Nmstart | Nmchar, // 191
|
||||
Nmstart | Nmchar, // 192
|
||||
Nmstart | Nmchar, // 193
|
||||
Nmstart | Nmchar, // 194
|
||||
Nmstart | Nmchar, // 195
|
||||
Nmstart | Nmchar, // 196
|
||||
Nmstart | Nmchar, // 197
|
||||
Nmstart | Nmchar, // 198
|
||||
Nmstart | Nmchar, // 199
|
||||
Nmstart | Nmchar, // 200
|
||||
Nmstart | Nmchar, // 201
|
||||
Nmstart | Nmchar, // 202
|
||||
Nmstart | Nmchar, // 203
|
||||
Nmstart | Nmchar, // 204
|
||||
Nmstart | Nmchar, // 205
|
||||
Nmstart | Nmchar, // 206
|
||||
Nmstart | Nmchar, // 207
|
||||
Nmstart | Nmchar, // 208
|
||||
Nmstart | Nmchar, // 209
|
||||
Nmstart | Nmchar, // 210
|
||||
Nmstart | Nmchar, // 211
|
||||
Nmstart | Nmchar, // 212
|
||||
Nmstart | Nmchar, // 213
|
||||
Nmstart | Nmchar, // 214
|
||||
Nmstart | Nmchar, // 215
|
||||
Nmstart | Nmchar, // 216
|
||||
Nmstart | Nmchar, // 217
|
||||
Nmstart | Nmchar, // 218
|
||||
Nmstart | Nmchar, // 219
|
||||
Nmstart | Nmchar, // 220
|
||||
Nmstart | Nmchar, // 221
|
||||
Nmstart | Nmchar, // 222
|
||||
Nmstart | Nmchar, // 223
|
||||
Nmstart | Nmchar, // 224
|
||||
Nmstart | Nmchar, // 225
|
||||
Nmstart | Nmchar, // 226
|
||||
Nmstart | Nmchar, // 227
|
||||
Nmstart | Nmchar, // 228
|
||||
Nmstart | Nmchar, // 229
|
||||
Nmstart | Nmchar, // 230
|
||||
Nmstart | Nmchar, // 231
|
||||
Nmstart | Nmchar, // 232
|
||||
Nmstart | Nmchar, // 233
|
||||
Nmstart | Nmchar, // 234
|
||||
Nmstart | Nmchar, // 235
|
||||
Nmstart | Nmchar, // 236
|
||||
Nmstart | Nmchar, // 237
|
||||
Nmstart | Nmchar, // 238
|
||||
Nmstart | Nmchar, // 239
|
||||
Nmstart | Nmchar, // 240
|
||||
Nmstart | Nmchar, // 241
|
||||
Nmstart | Nmchar, // 242
|
||||
Nmstart | Nmchar, // 243
|
||||
Nmstart | Nmchar, // 244
|
||||
Nmstart | Nmchar, // 245
|
||||
Nmstart | Nmchar, // 246
|
||||
Nmstart | Nmchar, // 247
|
||||
Nmstart | Nmchar, // 248
|
||||
Nmstart | Nmchar, // 249
|
||||
Nmstart | Nmchar, // 250
|
||||
Nmstart | Nmchar, // 251
|
||||
Nmstart | Nmchar, // 252
|
||||
Nmstart | Nmchar, // 253
|
||||
Nmstart | Nmchar, // 254
|
||||
Nmstart | Nmchar // 255
|
||||
};
|
||||
+605
@@ -0,0 +1,605 @@
|
||||
// This is written to exactly parse the `font` shorthand in CSS2:
|
||||
// https://www.w3.org/TR/CSS22/fonts.html#font-shorthand
|
||||
// https://www.w3.org/TR/CSS22/syndata.html#tokenization
|
||||
//
|
||||
// We may want to update it for CSS 3 (e.g. font-stretch, or updated
|
||||
// tokenization) but I've only ever seen one or two issues filed in node-canvas
|
||||
// due to parsing in my 8 years on the project
|
||||
|
||||
#include "FontParser.h"
|
||||
#include "CharData.h"
|
||||
#include <cctype>
|
||||
#include <unordered_map>
|
||||
|
||||
Token::Token(Type type, std::string value) : type_(type), value_(std::move(value)) {}
|
||||
|
||||
Token::Token(Type type, double value) : type_(type), value_(value) {}
|
||||
|
||||
Token::Token(Type type) : type_(type), value_(std::string{}) {}
|
||||
|
||||
const std::string&
|
||||
Token::getString() const {
|
||||
static const std::string empty;
|
||||
auto* str = std::get_if<std::string>(&value_);
|
||||
return str ? *str : empty;
|
||||
}
|
||||
|
||||
double
|
||||
Token::getNumber() const {
|
||||
auto* num = std::get_if<double>(&value_);
|
||||
return num ? *num : 0.0f;
|
||||
}
|
||||
|
||||
Tokenizer::Tokenizer(std::string_view input) : input_(input) {}
|
||||
|
||||
std::string
|
||||
Tokenizer::utf8Encode(uint32_t codepoint) {
|
||||
std::string result;
|
||||
|
||||
if (codepoint < 0x80) {
|
||||
result += static_cast<char>(codepoint);
|
||||
} else if (codepoint < 0x800) {
|
||||
result += static_cast<char>((codepoint >> 6) | 0xc0);
|
||||
result += static_cast<char>((codepoint & 0x3f) | 0x80);
|
||||
} else if (codepoint < 0x10000) {
|
||||
result += static_cast<char>((codepoint >> 12) | 0xe0);
|
||||
result += static_cast<char>(((codepoint >> 6) & 0x3f) | 0x80);
|
||||
result += static_cast<char>((codepoint & 0x3f) | 0x80);
|
||||
} else {
|
||||
result += static_cast<char>((codepoint >> 18) | 0xf0);
|
||||
result += static_cast<char>(((codepoint >> 12) & 0x3f) | 0x80);
|
||||
result += static_cast<char>(((codepoint >> 6) & 0x3f) | 0x80);
|
||||
result += static_cast<char>((codepoint & 0x3f) | 0x80);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char
|
||||
Tokenizer::peek() const {
|
||||
return position_ < input_.length() ? input_[position_] : '\0';
|
||||
}
|
||||
|
||||
char
|
||||
Tokenizer::advance() {
|
||||
return position_ < input_.length() ? input_[position_++] : '\0';
|
||||
}
|
||||
|
||||
Token
|
||||
Tokenizer::parseNumber() {
|
||||
enum class State {
|
||||
Start,
|
||||
AfterSign,
|
||||
Digits,
|
||||
AfterDecimal,
|
||||
AfterE,
|
||||
AfterESign,
|
||||
ExponentDigits
|
||||
};
|
||||
|
||||
size_t start = position_;
|
||||
size_t ePosition = 0;
|
||||
State state = State::Start;
|
||||
bool valid = false;
|
||||
|
||||
while (position_ < input_.length()) {
|
||||
char c = peek();
|
||||
uint8_t flags = charData[static_cast<uint8_t>(c)];
|
||||
|
||||
switch (state) {
|
||||
case State::Start:
|
||||
if (flags & CharData::Sign) {
|
||||
position_++;
|
||||
state = State::AfterSign;
|
||||
} else if (flags & CharData::Digit) {
|
||||
position_++;
|
||||
state = State::Digits;
|
||||
valid = true;
|
||||
} else if (c == '.') {
|
||||
position_++;
|
||||
state = State::AfterDecimal;
|
||||
} else {
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::AfterSign:
|
||||
if (flags & CharData::Digit) {
|
||||
position_++;
|
||||
state = State::Digits;
|
||||
valid = true;
|
||||
} else if (c == '.') {
|
||||
position_++;
|
||||
state = State::AfterDecimal;
|
||||
} else {
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::Digits:
|
||||
if (flags & CharData::Digit) {
|
||||
position_++;
|
||||
} else if (c == '.') {
|
||||
position_++;
|
||||
state = State::AfterDecimal;
|
||||
} else if (c == 'e' || c == 'E') {
|
||||
ePosition = position_;
|
||||
position_++;
|
||||
state = State::AfterE;
|
||||
valid = false;
|
||||
} else {
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::AfterDecimal:
|
||||
if (flags & CharData::Digit) {
|
||||
position_++;
|
||||
valid = true;
|
||||
state = State::Digits;
|
||||
} else {
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::AfterE:
|
||||
if (flags & CharData::Sign) {
|
||||
position_++;
|
||||
state = State::AfterESign;
|
||||
} else if (flags & CharData::Digit) {
|
||||
position_++;
|
||||
valid = true;
|
||||
state = State::ExponentDigits;
|
||||
} else {
|
||||
position_ = ePosition;
|
||||
valid = true;
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::AfterESign:
|
||||
if (flags & CharData::Digit) {
|
||||
position_++;
|
||||
valid = true;
|
||||
state = State::ExponentDigits;
|
||||
} else {
|
||||
position_ = ePosition;
|
||||
valid = true;
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::ExponentDigits:
|
||||
if (flags & CharData::Digit) {
|
||||
position_++;
|
||||
} else {
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (!valid) {
|
||||
position_ = start;
|
||||
return Token(Token::Type::Invalid);
|
||||
}
|
||||
|
||||
std::string number_str(input_.substr(start, position_ - start));
|
||||
double value = std::stod(number_str);
|
||||
return Token(Token::Type::Number, value);
|
||||
}
|
||||
|
||||
// Note that identifiers are always lower-case. This helps us make easier/more
|
||||
// efficient comparisons, but means that font-families specified as identifiers
|
||||
// will be lower-cased. Since font selection isn't case sensitive, this
|
||||
// shouldn't ever be a problem.
|
||||
Token
|
||||
Tokenizer::parseIdentifier() {
|
||||
std::string identifier;
|
||||
auto flags = CharData::Nmstart;
|
||||
auto start = position_;
|
||||
|
||||
while (position_ < input_.length()) {
|
||||
char c = peek();
|
||||
|
||||
if (c == '\\') {
|
||||
advance();
|
||||
if (!parseEscape(identifier)) {
|
||||
position_ = start;
|
||||
return Token(Token::Type::Invalid);
|
||||
}
|
||||
flags = CharData::Nmchar;
|
||||
} else if (charData[static_cast<uint8_t>(c)] & flags) {
|
||||
identifier += advance() + (c >= 'A' && c <= 'Z' ? 32 : 0);
|
||||
flags = CharData::Nmchar;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Token(Token::Type::Identifier, identifier);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
Tokenizer::parseUnicode() {
|
||||
uint32_t value = 0;
|
||||
size_t count = 0;
|
||||
|
||||
while (position_ < input_.length() && count < 6) {
|
||||
char c = peek();
|
||||
uint32_t digit;
|
||||
|
||||
if (c >= '0' && c <= '9') {
|
||||
digit = c - '0';
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
digit = c - 'a' + 10;
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
digit = c - 'A' + 10;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
value = value * 16 + digit;
|
||||
advance();
|
||||
count++;
|
||||
}
|
||||
|
||||
// Optional whitespace after hex escape
|
||||
char c = peek();
|
||||
if (c == '\r') {
|
||||
advance();
|
||||
if (peek() == '\n') advance();
|
||||
} else if (isWhitespace(c)) {
|
||||
advance();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
bool
|
||||
Tokenizer::parseEscape(std::string& str) {
|
||||
char c = peek();
|
||||
auto flags = charData[static_cast<uint8_t>(c)];
|
||||
|
||||
if (flags & CharData::Hex) {
|
||||
str += utf8Encode(parseUnicode());
|
||||
return true;
|
||||
} else if (!(flags & CharData::Newline) && !(flags & CharData::Hex)) {
|
||||
str += advance();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Token
|
||||
Tokenizer::parseString(char quote) {
|
||||
advance();
|
||||
std::string value;
|
||||
auto start = position_;
|
||||
|
||||
while (position_ < input_.length()) {
|
||||
char c = peek();
|
||||
|
||||
if (c == quote) {
|
||||
advance();
|
||||
return Token(Token::Type::QuotedString, value);
|
||||
} else if (c == '\\') {
|
||||
advance();
|
||||
c = peek();
|
||||
if (c == '\r') {
|
||||
advance();
|
||||
if (peek() == '\n') advance();
|
||||
} else if (isNewline(c)) {
|
||||
advance();
|
||||
} else {
|
||||
if (!parseEscape(value)) {
|
||||
position_ = start;
|
||||
return Token(Token::Type::Invalid);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
value += advance();
|
||||
}
|
||||
}
|
||||
|
||||
position_ = start;
|
||||
return Token(Token::Type::Invalid);
|
||||
}
|
||||
|
||||
Token
|
||||
Tokenizer::nextToken() {
|
||||
if (position_ >= input_.length()) {
|
||||
return Token(Token::Type::EndOfInput);
|
||||
}
|
||||
|
||||
char c = peek();
|
||||
auto flags = charData[static_cast<uint8_t>(c)];
|
||||
|
||||
if (isWhitespace(c)) {
|
||||
std::string whitespace;
|
||||
while (position_ < input_.length() && isWhitespace(peek())) {
|
||||
whitespace += advance();
|
||||
}
|
||||
return Token(Token::Type::Whitespace, whitespace);
|
||||
}
|
||||
|
||||
if (flags & CharData::NumStart) {
|
||||
Token token = parseNumber();
|
||||
if (token.type() != Token::Type::Invalid) return token;
|
||||
}
|
||||
|
||||
if (flags & CharData::Nmstart) {
|
||||
Token token = parseIdentifier();
|
||||
if (token.type() != Token::Type::Invalid) return token;
|
||||
}
|
||||
|
||||
if (c == '"') {
|
||||
Token token = parseString('"');
|
||||
if (token.type() != Token::Type::Invalid) return token;
|
||||
}
|
||||
|
||||
if (c == '\'') {
|
||||
Token token = parseString('\'');
|
||||
if (token.type() != Token::Type::Invalid) return token;
|
||||
}
|
||||
|
||||
switch (advance()) {
|
||||
case '/': return Token(Token::Type::Slash);
|
||||
case ',': return Token(Token::Type::Comma);
|
||||
case '%': return Token(Token::Type::Percent);
|
||||
default: return Token(Token::Type::Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
FontParser::FontParser(std::string_view input)
|
||||
: tokenizer_(input)
|
||||
, currentToken_(tokenizer_.nextToken())
|
||||
, nextToken_(tokenizer_.nextToken()) {}
|
||||
|
||||
const std::unordered_map<std::string, uint16_t> FontParser::weightMap = {
|
||||
{"normal", 400},
|
||||
{"bold", 700},
|
||||
{"lighter", 100},
|
||||
{"bolder", 700}
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, double> FontParser::unitMap = {
|
||||
{"cm", 37.8f},
|
||||
{"mm", 3.78f},
|
||||
{"in", 96.0f},
|
||||
{"pt", 96.0f / 72.0f},
|
||||
{"pc", 96.0f / 6.0f},
|
||||
{"em", 16.0f},
|
||||
{"px", 1.0f}
|
||||
};
|
||||
|
||||
void
|
||||
FontParser::advance() {
|
||||
currentToken_ = nextToken_;
|
||||
nextToken_ = tokenizer_.nextToken();
|
||||
}
|
||||
|
||||
void
|
||||
FontParser::skipWs() {
|
||||
while (currentToken_.type() == Token::Type::Whitespace) advance();
|
||||
}
|
||||
|
||||
bool
|
||||
FontParser::check(Token::Type type) const {
|
||||
return currentToken_.type() == type;
|
||||
}
|
||||
|
||||
bool
|
||||
FontParser::checkWs() const {
|
||||
return nextToken_.type() == Token::Type::Whitespace
|
||||
|| nextToken_.type() == Token::Type::EndOfInput;
|
||||
}
|
||||
|
||||
bool
|
||||
FontParser::parseFontStyle(FontProperties& props) {
|
||||
if (check(Token::Type::Identifier)) {
|
||||
const auto& value = currentToken_.getString();
|
||||
if (value == "italic") {
|
||||
props.fontStyle = FontStyle::Italic;
|
||||
advance();
|
||||
return true;
|
||||
} else if (value == "oblique") {
|
||||
props.fontStyle = FontStyle::Oblique;
|
||||
advance();
|
||||
return true;
|
||||
} else if (value == "normal") {
|
||||
props.fontStyle = FontStyle::Normal;
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
FontParser::parseFontVariant(FontProperties& props) {
|
||||
if (check(Token::Type::Identifier)) {
|
||||
const auto& value = currentToken_.getString();
|
||||
if (value == "small-caps") {
|
||||
props.fontVariant = FontVariant::SmallCaps;
|
||||
advance();
|
||||
return true;
|
||||
} else if (value == "normal") {
|
||||
props.fontVariant = FontVariant::Normal;
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
FontParser::parseFontWeight(FontProperties& props) {
|
||||
if (check(Token::Type::Number)) {
|
||||
double weightFloat = currentToken_.getNumber();
|
||||
int weight = static_cast<int>(weightFloat);
|
||||
if (weight < 1 || weight > 1000) return false;
|
||||
props.fontWeight = static_cast<uint16_t>(weight);
|
||||
advance();
|
||||
return true;
|
||||
} else if (check(Token::Type::Identifier)) {
|
||||
const auto& value = currentToken_.getString();
|
||||
|
||||
if (auto it = weightMap.find(value); it != weightMap.end()) {
|
||||
props.fontWeight = it->second;
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
FontParser::parseFontSize(FontProperties& props) {
|
||||
if (!check(Token::Type::Number)) return false;
|
||||
|
||||
props.fontSize = currentToken_.getNumber();
|
||||
advance();
|
||||
|
||||
double multiplier = 1.0f;
|
||||
if (check(Token::Type::Identifier)) {
|
||||
const auto& unit = currentToken_.getString();
|
||||
|
||||
if (auto it = unitMap.find(unit); it != unitMap.end()) {
|
||||
multiplier = it->second;
|
||||
advance();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (check(Token::Type::Percent)) {
|
||||
multiplier = 16.0f / 100.0f;
|
||||
advance();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Technically if we consumed some tokens but couldn't parse the font-size,
|
||||
// we should rewind the tokenizer, but I don't think the grammar allows for
|
||||
// any valid alternates in this specific case
|
||||
|
||||
props.fontSize *= multiplier;
|
||||
return true;
|
||||
}
|
||||
|
||||
// line-height is not used by canvas ever, but should still parse
|
||||
bool
|
||||
FontParser::parseLineHeight(FontProperties& props) {
|
||||
if (check(Token::Type::Slash)) {
|
||||
advance();
|
||||
skipWs();
|
||||
if (check(Token::Type::Number)) {
|
||||
advance();
|
||||
if (check(Token::Type::Percent)) {
|
||||
advance();
|
||||
} else if (check(Token::Type::Identifier)) {
|
||||
auto identifier = currentToken_.getString();
|
||||
if (auto it = unitMap.find(identifier); it != unitMap.end()) {
|
||||
advance();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (check(Token::Type::Identifier) && currentToken_.getString() == "normal") {
|
||||
advance();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
FontParser::parseFontFamily(FontProperties& props) {
|
||||
while (!check(Token::Type::EndOfInput)) {
|
||||
std::string family = "";
|
||||
std::string trailingWs = "";
|
||||
bool found = false;
|
||||
|
||||
while (
|
||||
check(Token::Type::QuotedString) ||
|
||||
check(Token::Type::Identifier) ||
|
||||
check(Token::Type::Whitespace)
|
||||
) {
|
||||
if (check(Token::Type::Whitespace)) {
|
||||
if (found) trailingWs += currentToken_.getString();
|
||||
} else { // Identifier, QuotedString
|
||||
if (found) {
|
||||
family += trailingWs;
|
||||
trailingWs.clear();
|
||||
}
|
||||
|
||||
family += currentToken_.getString();
|
||||
found = true;
|
||||
}
|
||||
|
||||
advance();
|
||||
}
|
||||
|
||||
if (!found) return false; // only whitespace or non-id/string found
|
||||
|
||||
props.fontFamily.push_back(family);
|
||||
|
||||
if (check(Token::Type::Comma)) advance();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FontProperties
|
||||
FontParser::parse(const std::string& fontString, bool* success) {
|
||||
FontParser parser(fontString);
|
||||
auto result = parser.parseFont();
|
||||
if (success) *success = !parser.hasError_;
|
||||
return result;
|
||||
}
|
||||
|
||||
FontProperties
|
||||
FontParser::parseFont() {
|
||||
FontProperties props;
|
||||
uint8_t state = 0b111;
|
||||
|
||||
skipWs();
|
||||
|
||||
for (size_t i = 0; i < 3 && checkWs(); i++) {
|
||||
if ((state & 0b001) && parseFontStyle(props)) {
|
||||
state &= 0b110;
|
||||
goto match;
|
||||
}
|
||||
|
||||
if ((state & 0b010) && parseFontVariant(props)) {
|
||||
state &= 0b101;
|
||||
goto match;
|
||||
}
|
||||
|
||||
if ((state & 0b100) && parseFontWeight(props)) {
|
||||
state &= 0b011;
|
||||
goto match;
|
||||
}
|
||||
|
||||
break; // all attempts exhausted
|
||||
match: skipWs(); // success: move to the next non-ws token
|
||||
}
|
||||
|
||||
if (parseFontSize(props)) {
|
||||
skipWs();
|
||||
if (parseLineHeight(props) && parseFontFamily(props)) {
|
||||
return props;
|
||||
}
|
||||
}
|
||||
|
||||
hasError_ = true;
|
||||
return props;
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
#include <unordered_map>
|
||||
#include "CharData.h"
|
||||
|
||||
enum class FontStyle {
|
||||
Normal,
|
||||
Italic,
|
||||
Oblique
|
||||
};
|
||||
|
||||
enum class FontVariant {
|
||||
Normal,
|
||||
SmallCaps
|
||||
};
|
||||
|
||||
struct FontProperties {
|
||||
double fontSize{16.0f};
|
||||
std::vector<std::string> fontFamily;
|
||||
uint16_t fontWeight{400};
|
||||
FontVariant fontVariant{FontVariant::Normal};
|
||||
FontStyle fontStyle{FontStyle::Normal};
|
||||
};
|
||||
|
||||
class Token {
|
||||
public:
|
||||
enum class Type {
|
||||
Invalid,
|
||||
Number,
|
||||
Percent,
|
||||
Identifier,
|
||||
Slash,
|
||||
Comma,
|
||||
QuotedString,
|
||||
Whitespace,
|
||||
EndOfInput
|
||||
};
|
||||
|
||||
Token(Type type, std::string value);
|
||||
Token(Type type, double value);
|
||||
Token(Type type);
|
||||
|
||||
Type type() const { return type_; }
|
||||
|
||||
const std::string& getString() const;
|
||||
double getNumber() const;
|
||||
|
||||
private:
|
||||
Type type_;
|
||||
std::variant<std::string, double> value_;
|
||||
};
|
||||
|
||||
class Tokenizer {
|
||||
public:
|
||||
Tokenizer(std::string_view input);
|
||||
Token nextToken();
|
||||
|
||||
private:
|
||||
std::string_view input_;
|
||||
size_t position_{0};
|
||||
|
||||
// Util
|
||||
std::string utf8Encode(uint32_t codepoint);
|
||||
inline bool isWhitespace(char c) const {
|
||||
return charData[static_cast<uint8_t>(c)] & CharData::Whitespace;
|
||||
}
|
||||
inline bool isNewline(char c) const {
|
||||
return charData[static_cast<uint8_t>(c)] & CharData::Newline;
|
||||
}
|
||||
|
||||
// Moving through the string
|
||||
char peek() const;
|
||||
char advance();
|
||||
|
||||
// Tokenize
|
||||
Token parseNumber();
|
||||
Token parseIdentifier();
|
||||
uint32_t parseUnicode();
|
||||
bool parseEscape(std::string& str);
|
||||
Token parseString(char quote);
|
||||
};
|
||||
|
||||
class FontParser {
|
||||
public:
|
||||
static FontProperties parse(const std::string& fontString, bool* success = nullptr);
|
||||
|
||||
private:
|
||||
static const std::unordered_map<std::string, uint16_t> weightMap;
|
||||
static const std::unordered_map<std::string, double> unitMap;
|
||||
|
||||
FontParser(std::string_view input);
|
||||
|
||||
void advance();
|
||||
void skipWs();
|
||||
bool check(Token::Type type) const;
|
||||
bool checkWs() const;
|
||||
|
||||
bool parseFontStyle(FontProperties& props);
|
||||
bool parseFontVariant(FontProperties& props);
|
||||
bool parseFontWeight(FontProperties& props);
|
||||
bool parseFontSize(FontProperties& props);
|
||||
bool parseLineHeight(FontProperties& props);
|
||||
bool parseFontFamily(FontProperties& props);
|
||||
FontProperties parseFont();
|
||||
|
||||
Tokenizer tokenizer_;
|
||||
Token currentToken_;
|
||||
Token nextToken_;
|
||||
bool hasError_{false};
|
||||
};
|
||||
+1727
File diff suppressed because it is too large.
Load diff
+146
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cairo.h>
|
||||
#include "CanvasError.h"
|
||||
#include <functional>
|
||||
#include <napi.h>
|
||||
#include <stdint.h> // node < 7 uses libstdc++ on macOS which lacks complete c++11
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
#include <jpeglib.h>
|
||||
#include <jerror.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GIF
|
||||
#include <gif_lib.h>
|
||||
|
||||
#if GIFLIB_MAJOR > 5 || GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1
|
||||
#define GIF_CLOSE_FILE(gif) DGifCloseFile(gif, NULL)
|
||||
#else
|
||||
#define GIF_CLOSE_FILE(gif) DGifCloseFile(gif)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_RSVG
|
||||
#include <librsvg/rsvg.h>
|
||||
// librsvg <= 2.36.1, identified by undefined macro, needs an extra include
|
||||
#ifndef LIBRSVG_CHECK_VERSION
|
||||
#include <librsvg/rsvg-cairo.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
using JPEGDecodeL = std::function<uint32_t (uint8_t* const src)>;
|
||||
|
||||
class Image : public Napi::ObjectWrap<Image> {
|
||||
public:
|
||||
char *filename;
|
||||
int width, height;
|
||||
int naturalWidth, naturalHeight;
|
||||
Napi::Env env;
|
||||
static Napi::FunctionReference constructor;
|
||||
static void Initialize(Napi::Env& env, Napi::Object& target);
|
||||
Image(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetComplete(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetWidth(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetHeight(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetNaturalWidth(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetNaturalHeight(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetDataMode(const Napi::CallbackInfo& info);
|
||||
void SetDataMode(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
static Napi::Value GetSource(const Napi::CallbackInfo& info);
|
||||
static void SetSource(const Napi::CallbackInfo& info);
|
||||
inline uint8_t *data(){ return cairo_image_surface_get_data(_surface); }
|
||||
inline int stride(){ return cairo_image_surface_get_stride(_surface); }
|
||||
static int isPNG(uint8_t *data);
|
||||
static int isJPEG(uint8_t *data);
|
||||
static int isGIF(uint8_t *data);
|
||||
static int isSVG(uint8_t *data, unsigned len);
|
||||
static int isBMP(uint8_t *data, unsigned len);
|
||||
static cairo_status_t readPNG(void *closure, unsigned char *data, unsigned len);
|
||||
inline int isComplete(){ return COMPLETE == state; }
|
||||
cairo_surface_t *surface();
|
||||
cairo_status_t loadSurface();
|
||||
cairo_status_t loadFromBuffer(uint8_t *buf, unsigned len);
|
||||
cairo_status_t loadPNGFromBuffer(uint8_t *buf);
|
||||
cairo_status_t loadPNG();
|
||||
void clearData();
|
||||
#ifdef HAVE_RSVG
|
||||
cairo_status_t loadSVGFromBuffer(uint8_t *buf, unsigned len);
|
||||
cairo_status_t loadSVG(FILE *stream);
|
||||
cairo_status_t renderSVGToSurface();
|
||||
#endif
|
||||
#ifdef HAVE_GIF
|
||||
cairo_status_t loadGIFFromBuffer(uint8_t *buf, unsigned len);
|
||||
cairo_status_t loadGIF(FILE *stream);
|
||||
#endif
|
||||
#ifdef HAVE_JPEG
|
||||
enum Orientation {
|
||||
NORMAL,
|
||||
MIRROR_HORIZ,
|
||||
MIRROR_VERT,
|
||||
ROTATE_180,
|
||||
ROTATE_90_CW,
|
||||
ROTATE_270_CW,
|
||||
MIRROR_HORIZ_AND_ROTATE_90_CW,
|
||||
MIRROR_HORIZ_AND_ROTATE_270_CW
|
||||
};
|
||||
cairo_status_t loadJPEGFromBuffer(uint8_t *buf, unsigned len);
|
||||
cairo_status_t loadJPEG(FILE *stream);
|
||||
void jpegToARGB(jpeg_decompress_struct* args, uint8_t* data, uint8_t* src, JPEGDecodeL decode);
|
||||
cairo_status_t decodeJPEGIntoSurface(jpeg_decompress_struct *info, Orientation orientation);
|
||||
cairo_status_t decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len);
|
||||
cairo_status_t assignDataAsMime(uint8_t *data, int len, const char *mime_type);
|
||||
|
||||
class Reader {
|
||||
public:
|
||||
virtual bool hasBytes(unsigned n) const = 0;
|
||||
virtual uint8_t getNext() = 0;
|
||||
virtual void skipBytes(unsigned n) = 0;
|
||||
};
|
||||
Orientation getExifOrientation(Reader& jpeg);
|
||||
void updateDimensionsForOrientation(Orientation orientation);
|
||||
void rotatePixels(uint8_t* pixels, int width, int height, int channels, Orientation orientation);
|
||||
#endif
|
||||
cairo_status_t loadBMPFromBuffer(uint8_t *buf, unsigned len);
|
||||
cairo_status_t loadBMP(FILE *stream);
|
||||
CanvasError errorInfo;
|
||||
void loaded();
|
||||
cairo_status_t load();
|
||||
~Image();
|
||||
|
||||
enum {
|
||||
DEFAULT
|
||||
, LOADING
|
||||
, COMPLETE
|
||||
} state;
|
||||
|
||||
enum data_mode_t {
|
||||
DATA_IMAGE = 1
|
||||
, DATA_MIME = 2
|
||||
} data_mode;
|
||||
|
||||
typedef enum {
|
||||
UNKNOWN
|
||||
, GIF
|
||||
, JPEG
|
||||
, PNG
|
||||
, SVG
|
||||
} type;
|
||||
|
||||
static type extension(const char *filename);
|
||||
|
||||
private:
|
||||
cairo_surface_t *_surface;
|
||||
uint8_t *_data = nullptr;
|
||||
int _data_len;
|
||||
#ifdef HAVE_RSVG
|
||||
RsvgHandle *_rsvg;
|
||||
bool _is_svg;
|
||||
int _svg_last_width;
|
||||
int _svg_last_height;
|
||||
#endif
|
||||
};
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#include "ImageData.h"
|
||||
#include "InstanceData.h"
|
||||
|
||||
/*
|
||||
* Initialize ImageData.
|
||||
*/
|
||||
|
||||
void
|
||||
ImageData::Initialize(Napi::Env& env, Napi::Object& exports) {
|
||||
Napi::HandleScope scope(env);
|
||||
|
||||
InstanceData *data = env.GetInstanceData<InstanceData>();
|
||||
|
||||
Napi::Function ctor = DefineClass(env, "ImageData", {
|
||||
InstanceAccessor<&ImageData::GetWidth>("width", napi_default_jsproperty),
|
||||
InstanceAccessor<&ImageData::GetHeight>("height", napi_default_jsproperty)
|
||||
});
|
||||
|
||||
exports.Set("ImageData", ctor);
|
||||
data->ImageDataCtor = Napi::Persistent(ctor);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a new ImageData object.
|
||||
*/
|
||||
|
||||
ImageData::ImageData(const Napi::CallbackInfo& info) : Napi::ObjectWrap<ImageData>(info), env(info.Env()) {
|
||||
Napi::TypedArray dataArray;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
int length;
|
||||
|
||||
if (info[0].IsNumber() && info[1].IsNumber()) {
|
||||
width = info[0].As<Napi::Number>().Uint32Value();
|
||||
if (width == 0) {
|
||||
Napi::RangeError::New(env, "The source width is zero.").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
height = info[1].As<Napi::Number>().Uint32Value();
|
||||
if (height == 0) {
|
||||
Napi::RangeError::New(env, "The source height is zero.").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
length = width * height * 4; // ImageData(w, h) constructor assumes 4 BPP; documented.
|
||||
|
||||
dataArray = Napi::Uint8Array::New(env, length, napi_uint8_clamped_array);
|
||||
} else if (
|
||||
info[0].IsTypedArray() &&
|
||||
info[0].As<Napi::TypedArray>().TypedArrayType() == napi_uint8_clamped_array &&
|
||||
info[1].IsNumber()
|
||||
) {
|
||||
dataArray = info[0].As<Napi::Uint8Array>();
|
||||
|
||||
length = dataArray.ElementLength();
|
||||
if (length == 0) {
|
||||
Napi::RangeError::New(env, "The input data has a zero byte length.").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't assert that the ImageData length is a multiple of four because some
|
||||
// data formats are not 4 BPP.
|
||||
|
||||
width = info[1].As<Napi::Number>().Uint32Value();
|
||||
if (width == 0) {
|
||||
Napi::RangeError::New(env, "The source width is zero.").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't assert that the byte length is a multiple of 4 * width, ditto.
|
||||
|
||||
if (info[2].IsNumber()) { // Explicit height given
|
||||
height = info[2].As<Napi::Number>().Uint32Value();
|
||||
} else { // Calculate height assuming 4 BPP
|
||||
int size = length / 4;
|
||||
height = size / width;
|
||||
}
|
||||
} else if (
|
||||
info[0].IsTypedArray() &&
|
||||
info[0].As<Napi::TypedArray>().TypedArrayType() == napi_uint16_array &&
|
||||
info[1].IsNumber()
|
||||
) { // Intended for RGB16_565 format
|
||||
dataArray = info[0].As<Napi::TypedArray>();
|
||||
|
||||
length = dataArray.ElementLength();
|
||||
if (length == 0) {
|
||||
Napi::RangeError::New(env, "The input data has a zero byte length.").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
width = info[1].As<Napi::Number>().Uint32Value();
|
||||
if (width == 0) {
|
||||
Napi::RangeError::New(env, "The source width is zero.").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
if (info[2].IsNumber()) { // Explicit height given
|
||||
height = info[2].As<Napi::Number>().Uint32Value();
|
||||
} else { // Calculate height assuming 2 BPP
|
||||
int size = length / 2;
|
||||
height = size / width;
|
||||
}
|
||||
} else {
|
||||
Napi::TypeError::New(env, "Expected (Uint8ClampedArray, width[, height]), (Uint16Array, width[, height]) or (width, height)").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
_width = width;
|
||||
_height = height;
|
||||
_data = dataArray.As<Napi::Uint8Array>().Data();
|
||||
|
||||
info.This().As<Napi::Object>().Set("data", dataArray);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get width.
|
||||
*/
|
||||
|
||||
Napi::Value
|
||||
ImageData::GetWidth(const Napi::CallbackInfo& info) {
|
||||
return Napi::Number::New(env, width());
|
||||
}
|
||||
|
||||
/*
|
||||
* Get height.
|
||||
*/
|
||||
|
||||
Napi::Value
|
||||
ImageData::GetHeight(const Napi::CallbackInfo& info) {
|
||||
return Napi::Number::New(env, height());
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <napi.h>
|
||||
#include <stdint.h> // node < 7 uses libstdc++ on macOS which lacks complete c++11
|
||||
|
||||
class ImageData : public Napi::ObjectWrap<ImageData> {
|
||||
public:
|
||||
static void Initialize(Napi::Env& env, Napi::Object& exports);
|
||||
ImageData(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetWidth(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetHeight(const Napi::CallbackInfo& info);
|
||||
|
||||
inline int width() { return _width; }
|
||||
inline int height() { return _height; }
|
||||
inline uint8_t *data() { return _data; }
|
||||
|
||||
Napi::Env env;
|
||||
|
||||
private:
|
||||
int _width;
|
||||
int _height;
|
||||
uint8_t *_data;
|
||||
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#include <napi.h>
|
||||
|
||||
struct InstanceData {
|
||||
Napi::FunctionReference ImageBackendCtor;
|
||||
Napi::FunctionReference PdfBackendCtor;
|
||||
Napi::FunctionReference SvgBackendCtor;
|
||||
Napi::FunctionReference CanvasCtor;
|
||||
Napi::FunctionReference CanvasGradientCtor;
|
||||
Napi::FunctionReference DOMMatrixCtor;
|
||||
Napi::FunctionReference ImageCtor;
|
||||
Napi::FunctionReference parseFont;
|
||||
Napi::FunctionReference Context2dCtor;
|
||||
Napi::FunctionReference ImageDataCtor;
|
||||
Napi::FunctionReference CanvasPatternCtor;
|
||||
};
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
#pragma once
|
||||
|
||||
#include "closure.h"
|
||||
#include <jpeglib.h>
|
||||
#include <jerror.h>
|
||||
|
||||
/*
|
||||
* Expanded data destination object for closure output,
|
||||
* inspired by IJG's jdatadst.c
|
||||
*/
|
||||
|
||||
struct closure_destination_mgr {
|
||||
jpeg_destination_mgr pub;
|
||||
JpegClosure* closure;
|
||||
JOCTET *buffer;
|
||||
int bufsize;
|
||||
};
|
||||
|
||||
void
|
||||
init_closure_destination(j_compress_ptr cinfo){
|
||||
// we really don't have to do anything here
|
||||
}
|
||||
|
||||
boolean
|
||||
empty_closure_output_buffer(j_compress_ptr cinfo){
|
||||
closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest;
|
||||
Napi::Env env = dest->closure->canvas->Env();
|
||||
Napi::HandleScope scope(env);
|
||||
Napi::AsyncContext async(env, "canvas:empty_closure_output_buffer");
|
||||
|
||||
Napi::Object buf = Napi::Buffer<char>::New(env, (char *)dest->buffer, dest->bufsize);
|
||||
|
||||
// emit "data"
|
||||
dest->closure->cb.MakeCallback(env.Global(), {env.Null(), buf}, async);
|
||||
|
||||
dest->buffer = (JOCTET *)malloc(dest->bufsize);
|
||||
cinfo->dest->next_output_byte = dest->buffer;
|
||||
cinfo->dest->free_in_buffer = dest->bufsize;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
term_closure_destination(j_compress_ptr cinfo){
|
||||
closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest;
|
||||
Napi::Env env = dest->closure->canvas->Env();
|
||||
Napi::HandleScope scope(env);
|
||||
Napi::AsyncContext async(env, "canvas:term_closure_destination");
|
||||
|
||||
/* emit remaining data */
|
||||
Napi::Object buf = Napi::Buffer<char>::New(env, (char *)dest->buffer, dest->bufsize - dest->pub.free_in_buffer);
|
||||
|
||||
dest->closure->cb.MakeCallback(env.Global(), {env.Null(), buf}, async);
|
||||
|
||||
// emit "end"
|
||||
dest->closure->cb.MakeCallback(env.Global(), {env.Null(), env.Null()}, async);
|
||||
}
|
||||
|
||||
void
|
||||
jpeg_closure_dest(j_compress_ptr cinfo, JpegClosure* closure, int bufsize){
|
||||
closure_destination_mgr * dest;
|
||||
|
||||
/* The destination object is made permanent so that multiple JPEG images
|
||||
* can be written to the same buffer without re-executing jpeg_mem_dest.
|
||||
*/
|
||||
if (cinfo->dest == NULL) { /* first time for this JPEG object? */
|
||||
cinfo->dest = (struct jpeg_destination_mgr *)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
|
||||
sizeof(closure_destination_mgr));
|
||||
}
|
||||
|
||||
dest = (closure_destination_mgr *) cinfo->dest;
|
||||
|
||||
cinfo->dest->init_destination = &init_closure_destination;
|
||||
cinfo->dest->empty_output_buffer = &empty_closure_output_buffer;
|
||||
cinfo->dest->term_destination = &term_closure_destination;
|
||||
|
||||
dest->closure = closure;
|
||||
dest->bufsize = bufsize;
|
||||
dest->buffer = (JOCTET *)malloc(bufsize);
|
||||
|
||||
cinfo->dest->next_output_byte = dest->buffer;
|
||||
cinfo->dest->free_in_buffer = dest->bufsize;
|
||||
}
|
||||
|
||||
void encode_jpeg(jpeg_compress_struct cinfo, cairo_surface_t *surface, int quality, bool progressive, int chromaHSampFactor, int chromaVSampFactor) {
|
||||
int w = cairo_image_surface_get_width(surface);
|
||||
int h = cairo_image_surface_get_height(surface);
|
||||
|
||||
cinfo.in_color_space = JCS_RGB;
|
||||
cinfo.input_components = 3;
|
||||
cinfo.image_width = w;
|
||||
cinfo.image_height = h;
|
||||
jpeg_set_defaults(&cinfo);
|
||||
if (progressive)
|
||||
jpeg_simple_progression(&cinfo);
|
||||
jpeg_set_quality(&cinfo, quality, (quality < 25) ? 0 : 1);
|
||||
cinfo.comp_info[0].h_samp_factor = chromaHSampFactor;
|
||||
cinfo.comp_info[0].v_samp_factor = chromaVSampFactor;
|
||||
|
||||
JSAMPROW slr;
|
||||
jpeg_start_compress(&cinfo, TRUE);
|
||||
unsigned char *dst;
|
||||
unsigned int *src = (unsigned int *)cairo_image_surface_get_data(surface);
|
||||
int sl = 0;
|
||||
dst = (unsigned char *)malloc(w * 3);
|
||||
while (sl < h) {
|
||||
unsigned char *dp = dst;
|
||||
int x = 0;
|
||||
while (x < w) {
|
||||
dp[0] = (*src >> 16) & 255;
|
||||
dp[1] = (*src >> 8) & 255;
|
||||
dp[2] = *src & 255;
|
||||
src++;
|
||||
dp += 3;
|
||||
x++;
|
||||
}
|
||||
slr = dst;
|
||||
jpeg_write_scanlines(&cinfo, &slr, 1);
|
||||
sl++;
|
||||
}
|
||||
free(dst);
|
||||
jpeg_finish_compress(&cinfo);
|
||||
jpeg_destroy_compress(&cinfo);
|
||||
}
|
||||
|
||||
void
|
||||
write_to_jpeg_stream(cairo_surface_t *surface, int bufsize, JpegClosure* closure) {
|
||||
jpeg_compress_struct cinfo;
|
||||
jpeg_error_mgr jerr;
|
||||
cinfo.err = jpeg_std_error(&jerr);
|
||||
jpeg_create_compress(&cinfo);
|
||||
jpeg_closure_dest(&cinfo, closure, bufsize);
|
||||
encode_jpeg(
|
||||
cinfo,
|
||||
surface,
|
||||
closure->quality,
|
||||
closure->progressive,
|
||||
closure->chromaSubsampling,
|
||||
closure->chromaSubsampling);
|
||||
}
|
||||
|
||||
void
|
||||
write_to_jpeg_buffer(cairo_surface_t* surface, JpegClosure* closure) {
|
||||
jpeg_compress_struct cinfo;
|
||||
jpeg_error_mgr jerr;
|
||||
cinfo.err = jpeg_std_error(&jerr);
|
||||
jpeg_create_compress(&cinfo);
|
||||
cinfo.client_data = closure;
|
||||
cinfo.dest = closure->jpeg_dest_mgr;
|
||||
encode_jpeg(
|
||||
cinfo,
|
||||
surface,
|
||||
closure->quality,
|
||||
closure->progressive,
|
||||
closure->chromaSubsampling,
|
||||
closure->chromaSubsampling);
|
||||
}
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
#pragma once
|
||||
|
||||
#include <cairo.h>
|
||||
#include "closure.h"
|
||||
#include <cmath> // round
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <png.h>
|
||||
#include <pngconf.h>
|
||||
|
||||
#if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__)
|
||||
#define likely(expr) (__builtin_expect (!!(expr), 1))
|
||||
#define unlikely(expr) (__builtin_expect (!!(expr), 0))
|
||||
#else
|
||||
#define likely(expr) (expr)
|
||||
#define unlikely(expr) (expr)
|
||||
#endif
|
||||
|
||||
static void canvas_png_flush(png_structp png_ptr) {
|
||||
/* Do nothing; fflush() is said to be just a waste of energy. */
|
||||
(void) png_ptr; /* Stifle compiler warning */
|
||||
}
|
||||
|
||||
/* Converts native endian xRGB => RGBx bytes */
|
||||
static void canvas_convert_data_to_bytes(png_structp png, png_row_infop row_info, png_bytep data) {
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < row_info->rowbytes; i += 4) {
|
||||
uint8_t *b = &data[i];
|
||||
uint32_t pixel;
|
||||
|
||||
memcpy(&pixel, b, sizeof (uint32_t));
|
||||
|
||||
b[0] = (pixel & 0xff0000) >> 16;
|
||||
b[1] = (pixel & 0x00ff00) >> 8;
|
||||
b[2] = (pixel & 0x0000ff) >> 0;
|
||||
b[3] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Unpremultiplies data and converts native endian ARGB => RGBA bytes */
|
||||
static void canvas_unpremultiply_data(png_structp png, png_row_infop row_info, png_bytep data) {
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < row_info->rowbytes; i += 4) {
|
||||
uint8_t *b = &data[i];
|
||||
uint32_t pixel;
|
||||
uint8_t alpha;
|
||||
|
||||
memcpy(&pixel, b, sizeof (uint32_t));
|
||||
alpha = (pixel & 0xff000000) >> 24;
|
||||
if (alpha == 0) {
|
||||
b[0] = b[1] = b[2] = b[3] = 0;
|
||||
} else {
|
||||
b[0] = (((pixel & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
|
||||
b[1] = (((pixel & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha;
|
||||
b[2] = (((pixel & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha;
|
||||
b[3] = alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Converts RGB16_565 format data to RGBA32 */
|
||||
static void canvas_convert_565_to_888(png_structp png, png_row_infop row_info, png_bytep data) {
|
||||
// Loop in reverse to unpack in-place.
|
||||
for (ptrdiff_t col = row_info->width - 1; col >= 0; col--) {
|
||||
uint8_t* src = &data[col * sizeof(uint16_t)];
|
||||
uint8_t* dst = &data[col * 3];
|
||||
uint16_t pixel;
|
||||
|
||||
memcpy(&pixel, src, sizeof(uint16_t));
|
||||
|
||||
// Convert and rescale to the full 0-255 range
|
||||
// See http://stackoverflow.com/a/29326693
|
||||
const uint8_t red5 = (pixel & 0xF800) >> 11;
|
||||
const uint8_t green6 = (pixel & 0x7E0) >> 5;
|
||||
const uint8_t blue5 = (pixel & 0x001F);
|
||||
|
||||
dst[0] = ((red5 * 255 + 15) / 31);
|
||||
dst[1] = ((green6 * 255 + 31) / 63);
|
||||
dst[2] = ((blue5 * 255 + 15) / 31);
|
||||
}
|
||||
}
|
||||
|
||||
struct canvas_png_write_closure_t {
|
||||
cairo_write_func_t write_func;
|
||||
PngClosure* closure;
|
||||
};
|
||||
|
||||
#ifdef PNG_SETJMP_SUPPORTED
|
||||
bool setjmp_wrapper(png_structp png) {
|
||||
return setjmp(png_jmpbuf(png));
|
||||
}
|
||||
#endif
|
||||
|
||||
static cairo_status_t canvas_write_png(cairo_surface_t *surface, png_rw_ptr write_func, canvas_png_write_closure_t *closure) {
|
||||
unsigned int i;
|
||||
cairo_status_t status = CAIRO_STATUS_SUCCESS;
|
||||
uint8_t *data;
|
||||
png_structp png;
|
||||
png_infop info;
|
||||
png_bytep *volatile rows = NULL;
|
||||
png_color_16 white;
|
||||
int png_color_type;
|
||||
int bpc;
|
||||
unsigned int width = cairo_image_surface_get_width(surface);
|
||||
unsigned int height = cairo_image_surface_get_height(surface);
|
||||
|
||||
data = cairo_image_surface_get_data(surface);
|
||||
if (data == NULL) {
|
||||
status = CAIRO_STATUS_SURFACE_TYPE_MISMATCH;
|
||||
return status;
|
||||
}
|
||||
cairo_surface_flush(surface);
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
status = CAIRO_STATUS_WRITE_ERROR;
|
||||
return status;
|
||||
}
|
||||
|
||||
rows = (png_bytep *) malloc(height * sizeof (png_byte*));
|
||||
if (unlikely(rows == NULL)) {
|
||||
status = CAIRO_STATUS_NO_MEMORY;
|
||||
return status;
|
||||
}
|
||||
|
||||
int stride = cairo_image_surface_get_stride(surface);
|
||||
for (i = 0; i < height; i++) {
|
||||
rows[i] = (png_byte *) data + i * stride;
|
||||
}
|
||||
|
||||
#ifdef PNG_USER_MEM_SUPPORTED
|
||||
png = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
#else
|
||||
png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
#endif
|
||||
|
||||
if (unlikely(png == NULL)) {
|
||||
status = CAIRO_STATUS_NO_MEMORY;
|
||||
free(rows);
|
||||
return status;
|
||||
}
|
||||
|
||||
info = png_create_info_struct (png);
|
||||
if (unlikely(info == NULL)) {
|
||||
status = CAIRO_STATUS_NO_MEMORY;
|
||||
png_destroy_write_struct(&png, &info);
|
||||
free(rows);
|
||||
return status;
|
||||
|
||||
}
|
||||
|
||||
#ifdef PNG_SETJMP_SUPPORTED
|
||||
if (setjmp_wrapper(png)) {
|
||||
png_destroy_write_struct(&png, &info);
|
||||
free(rows);
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
|
||||
png_set_write_fn(png, closure, write_func, canvas_png_flush);
|
||||
png_set_compression_level(png, closure->closure->compressionLevel);
|
||||
png_set_filter(png, 0, closure->closure->filters);
|
||||
if (closure->closure->resolution != 0) {
|
||||
uint32_t res = static_cast<uint32_t>(round(static_cast<double>(closure->closure->resolution) * 39.3701));
|
||||
png_set_pHYs(png, info, res, res, PNG_RESOLUTION_METER);
|
||||
}
|
||||
|
||||
cairo_format_t format = cairo_image_surface_get_format(surface);
|
||||
|
||||
switch (format) {
|
||||
case CAIRO_FORMAT_ARGB32:
|
||||
bpc = 8;
|
||||
png_color_type = PNG_COLOR_TYPE_RGB_ALPHA;
|
||||
break;
|
||||
#ifdef CAIRO_FORMAT_RGB30
|
||||
case CAIRO_FORMAT_RGB30:
|
||||
bpc = 10;
|
||||
png_color_type = PNG_COLOR_TYPE_RGB;
|
||||
break;
|
||||
#endif
|
||||
case CAIRO_FORMAT_RGB24:
|
||||
bpc = 8;
|
||||
png_color_type = PNG_COLOR_TYPE_RGB;
|
||||
break;
|
||||
case CAIRO_FORMAT_A8:
|
||||
bpc = 8;
|
||||
png_color_type = PNG_COLOR_TYPE_GRAY;
|
||||
break;
|
||||
case CAIRO_FORMAT_A1:
|
||||
bpc = 1;
|
||||
png_color_type = PNG_COLOR_TYPE_GRAY;
|
||||
#ifndef WORDS_BIGENDIAN
|
||||
png_set_packswap(png);
|
||||
#endif
|
||||
break;
|
||||
case CAIRO_FORMAT_RGB16_565:
|
||||
bpc = 8; // 565 gets upconverted to 888
|
||||
png_color_type = PNG_COLOR_TYPE_RGB;
|
||||
break;
|
||||
case CAIRO_FORMAT_INVALID:
|
||||
default:
|
||||
status = CAIRO_STATUS_INVALID_FORMAT;
|
||||
png_destroy_write_struct(&png, &info);
|
||||
free(rows);
|
||||
return status;
|
||||
}
|
||||
|
||||
if ((format == CAIRO_FORMAT_A8 || format == CAIRO_FORMAT_A1) &&
|
||||
closure->closure->palette != NULL) {
|
||||
png_color_type = PNG_COLOR_TYPE_PALETTE;
|
||||
}
|
||||
|
||||
png_set_IHDR(png, info, width, height, bpc, png_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
|
||||
if (png_color_type == PNG_COLOR_TYPE_PALETTE) {
|
||||
size_t nColors = closure->closure->nPaletteColors;
|
||||
uint8_t* colors = closure->closure->palette;
|
||||
uint8_t backgroundIndex = closure->closure->backgroundIndex;
|
||||
png_colorp pngPalette = (png_colorp)png_malloc(png, nColors * sizeof(png_colorp));
|
||||
png_bytep transparency = (png_bytep)png_malloc(png, nColors * sizeof(png_bytep));
|
||||
for (i = 0; i < nColors; i++) {
|
||||
pngPalette[i].red = colors[4 * i];
|
||||
pngPalette[i].green = colors[4 * i + 1];
|
||||
pngPalette[i].blue = colors[4 * i + 2];
|
||||
transparency[i] = colors[4 * i + 3];
|
||||
}
|
||||
png_set_PLTE(png, info, pngPalette, nColors);
|
||||
png_set_tRNS(png, info, transparency, nColors, NULL);
|
||||
png_set_packing(png); // pack pixels
|
||||
// have libpng free palette and trans:
|
||||
png_data_freer(png, info, PNG_DESTROY_WILL_FREE_DATA, PNG_FREE_PLTE | PNG_FREE_TRNS);
|
||||
png_color_16 bkg;
|
||||
bkg.index = backgroundIndex;
|
||||
png_set_bKGD(png, info, &bkg);
|
||||
}
|
||||
|
||||
if (png_color_type != PNG_COLOR_TYPE_PALETTE) {
|
||||
white.gray = (1 << bpc) - 1;
|
||||
white.red = white.blue = white.green = white.gray;
|
||||
png_set_bKGD(png, info, &white);
|
||||
}
|
||||
|
||||
/* We have to call png_write_info() before setting up the write
|
||||
* transformation, since it stores data internally in 'png'
|
||||
* that is needed for the write transformation functions to work.
|
||||
*/
|
||||
png_write_info(png, info);
|
||||
if (png_color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
|
||||
png_set_write_user_transform_fn(png, canvas_unpremultiply_data);
|
||||
} else if (format == CAIRO_FORMAT_RGB16_565) {
|
||||
png_set_write_user_transform_fn(png, canvas_convert_565_to_888);
|
||||
} else if (png_color_type == PNG_COLOR_TYPE_RGB) {
|
||||
png_set_write_user_transform_fn(png, canvas_convert_data_to_bytes);
|
||||
png_set_filler(png, 0, PNG_FILLER_AFTER);
|
||||
}
|
||||
|
||||
png_write_image(png, rows);
|
||||
png_write_end(png, info);
|
||||
|
||||
png_destroy_write_struct(&png, &info);
|
||||
free(rows);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void canvas_stream_write_func(png_structp png, png_bytep data, png_size_t size) {
|
||||
cairo_status_t status;
|
||||
struct canvas_png_write_closure_t *png_closure;
|
||||
|
||||
png_closure = (struct canvas_png_write_closure_t *) png_get_io_ptr(png);
|
||||
status = png_closure->write_func(png_closure->closure, data, size);
|
||||
if (unlikely(status)) {
|
||||
cairo_status_t *error = (cairo_status_t *) png_get_error_ptr(png);
|
||||
if (*error == CAIRO_STATUS_SUCCESS) {
|
||||
*error = status;
|
||||
}
|
||||
png_error(png, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static cairo_status_t canvas_write_to_png_stream(cairo_surface_t *surface, cairo_write_func_t write_func, PngClosure* closure) {
|
||||
struct canvas_png_write_closure_t png_closure;
|
||||
|
||||
if (cairo_surface_status(surface)) {
|
||||
return cairo_surface_status(surface);
|
||||
}
|
||||
|
||||
png_closure.write_func = write_func;
|
||||
png_closure.closure = closure;
|
||||
|
||||
return canvas_write_png(surface, canvas_stream_write_func, &png_closure);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
#pragma once
|
||||
|
||||
template <typename T>
|
||||
class Point {
|
||||
public:
|
||||
T x, y;
|
||||
Point(T x=0, T y=0): x(x), y(y) {}
|
||||
Point(const Point&) = default;
|
||||
Point& operator=(const Point&) = default;
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <cctype>
|
||||
|
||||
inline bool streq_casein(std::string& str1, std::string& str2) {
|
||||
return str1.size() == str2.size() && std::equal(str1.begin(), str1.end(), str2.begin(), [](char& c1, char& c2) {
|
||||
return c1 == c2 || std::toupper(c1) == std::toupper(c2);
|
||||
});
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
#include "Backend.h"
|
||||
#include <string>
|
||||
#include <napi.h>
|
||||
|
||||
Backend::Backend(std::string name, Napi::CallbackInfo& info) : name(name), env(info.Env()) {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
if (info[0].IsNumber()) width = info[0].As<Napi::Number>().Int32Value();
|
||||
if (info[1].IsNumber()) height = info[1].As<Napi::Number>().Int32Value();
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
}
|
||||
|
||||
Backend::~Backend()
|
||||
{
|
||||
Backend::destroySurface();
|
||||
}
|
||||
|
||||
void Backend::setCanvas(Canvas* _canvas)
|
||||
{
|
||||
this->canvas = _canvas;
|
||||
}
|
||||
|
||||
|
||||
cairo_surface_t* Backend::recreateSurface()
|
||||
{
|
||||
this->destroySurface();
|
||||
|
||||
return this->createSurface();
|
||||
}
|
||||
|
||||
DLL_PUBLIC cairo_surface_t* Backend::getSurface() {
|
||||
if (!surface) createSurface();
|
||||
return surface;
|
||||
}
|
||||
|
||||
void Backend::destroySurface()
|
||||
{
|
||||
if(this->surface)
|
||||
{
|
||||
cairo_surface_destroy(this->surface);
|
||||
this->surface = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string Backend::getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
int Backend::getWidth()
|
||||
{
|
||||
return this->width;
|
||||
}
|
||||
void Backend::setWidth(int width_)
|
||||
{
|
||||
this->width = width_;
|
||||
this->recreateSurface();
|
||||
}
|
||||
|
||||
int Backend::getHeight()
|
||||
{
|
||||
return this->height;
|
||||
}
|
||||
void Backend::setHeight(int height_)
|
||||
{
|
||||
this->height = height_;
|
||||
this->recreateSurface();
|
||||
}
|
||||
|
||||
bool Backend::isSurfaceValid(){
|
||||
bool hadSurface = surface != NULL;
|
||||
bool isValid = true;
|
||||
|
||||
cairo_status_t status = cairo_surface_status(getSurface());
|
||||
|
||||
if (status != CAIRO_STATUS_SUCCESS) {
|
||||
error = cairo_status_to_string(status);
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (!hadSurface)
|
||||
destroySurface();
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
|
||||
BackendOperationNotAvailable::BackendOperationNotAvailable(Backend* backend,
|
||||
std::string operation_name)
|
||||
: operation_name(operation_name)
|
||||
{
|
||||
msg = "operation " + operation_name +
|
||||
" not supported by backend " + backend->getName();
|
||||
};
|
||||
|
||||
BackendOperationNotAvailable::~BackendOperationNotAvailable() throw() {};
|
||||
|
||||
const char* BackendOperationNotAvailable::what() const throw()
|
||||
{
|
||||
return msg.c_str();
|
||||
};
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <cairo.h>
|
||||
#include "../dll_visibility.h"
|
||||
#include <exception>
|
||||
#include <napi.h>
|
||||
#include <string>
|
||||
|
||||
class Canvas;
|
||||
|
||||
class Backend
|
||||
{
|
||||
private:
|
||||
const std::string name;
|
||||
const char* error = NULL;
|
||||
|
||||
protected:
|
||||
int width;
|
||||
int height;
|
||||
cairo_surface_t* surface = nullptr;
|
||||
Canvas* canvas = nullptr;
|
||||
|
||||
Backend(std::string name, Napi::CallbackInfo& info);
|
||||
|
||||
public:
|
||||
Napi::Env env;
|
||||
|
||||
virtual ~Backend();
|
||||
|
||||
void setCanvas(Canvas* canvas);
|
||||
|
||||
virtual cairo_surface_t* createSurface() = 0;
|
||||
virtual cairo_surface_t* recreateSurface();
|
||||
|
||||
DLL_PUBLIC cairo_surface_t* getSurface();
|
||||
virtual void destroySurface();
|
||||
|
||||
DLL_PUBLIC std::string getName();
|
||||
|
||||
DLL_PUBLIC int getWidth();
|
||||
virtual void setWidth(int width);
|
||||
|
||||
DLL_PUBLIC int getHeight();
|
||||
virtual void setHeight(int height);
|
||||
|
||||
// Overridden by ImageBackend. SVG and PDF thus always return INVALID.
|
||||
virtual cairo_format_t getFormat() {
|
||||
return CAIRO_FORMAT_INVALID;
|
||||
}
|
||||
|
||||
bool isSurfaceValid();
|
||||
inline const char* getError(){ return error; }
|
||||
};
|
||||
|
||||
|
||||
class BackendOperationNotAvailable: public std::exception
|
||||
{
|
||||
private:
|
||||
std::string operation_name;
|
||||
std::string msg;
|
||||
|
||||
public:
|
||||
BackendOperationNotAvailable(Backend* backend, std::string operation_name);
|
||||
~BackendOperationNotAvailable() throw();
|
||||
|
||||
const char* what() const throw();
|
||||
};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
#include "ImageBackend.h"
|
||||
#include "../InstanceData.h"
|
||||
#include <napi.h>
|
||||
#include <cassert>
|
||||
|
||||
ImageBackend::ImageBackend(Napi::CallbackInfo& info) : Napi::ObjectWrap<ImageBackend>(info), Backend("image", info)
|
||||
{
|
||||
}
|
||||
|
||||
// This returns an approximate value only, suitable for
|
||||
// Napi::MemoryManagement:: AdjustExternalMemory.
|
||||
// The formats that don't map to intrinsic types (RGB30, A1) round up.
|
||||
int32_t ImageBackend::approxBytesPerPixel() {
|
||||
switch (format) {
|
||||
case CAIRO_FORMAT_ARGB32:
|
||||
case CAIRO_FORMAT_RGB24:
|
||||
return 4;
|
||||
#ifdef CAIRO_FORMAT_RGB30
|
||||
case CAIRO_FORMAT_RGB30:
|
||||
return 3;
|
||||
#endif
|
||||
case CAIRO_FORMAT_RGB16_565:
|
||||
return 2;
|
||||
case CAIRO_FORMAT_A8:
|
||||
case CAIRO_FORMAT_A1:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
cairo_surface_t* ImageBackend::createSurface() {
|
||||
assert(!surface);
|
||||
surface = cairo_image_surface_create(format, width, height);
|
||||
assert(surface);
|
||||
Napi::MemoryManagement::AdjustExternalMemory(env, approxBytesPerPixel() * width * height);
|
||||
return surface;
|
||||
}
|
||||
|
||||
void ImageBackend::destroySurface() {
|
||||
if (surface) {
|
||||
cairo_surface_destroy(surface);
|
||||
surface = nullptr;
|
||||
Napi::MemoryManagement::AdjustExternalMemory(env, -approxBytesPerPixel() * width * height);
|
||||
}
|
||||
}
|
||||
|
||||
cairo_format_t ImageBackend::getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
void ImageBackend::setFormat(cairo_format_t _format) {
|
||||
this->format = _format;
|
||||
}
|
||||
|
||||
Napi::FunctionReference ImageBackend::constructor;
|
||||
|
||||
void ImageBackend::Initialize(Napi::Object target) {
|
||||
Napi::Env env = target.Env();
|
||||
Napi::Function ctor = DefineClass(env, "ImageBackend", {});
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
data->ImageBackendCtor = Napi::Persistent(ctor);
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "Backend.h"
|
||||
#include <napi.h>
|
||||
|
||||
class ImageBackend : public Napi::ObjectWrap<ImageBackend>, public Backend
|
||||
{
|
||||
private:
|
||||
cairo_surface_t* createSurface();
|
||||
void destroySurface();
|
||||
cairo_format_t format = DEFAULT_FORMAT;
|
||||
|
||||
public:
|
||||
ImageBackend(Napi::CallbackInfo& info);
|
||||
|
||||
cairo_format_t getFormat();
|
||||
void setFormat(cairo_format_t format);
|
||||
|
||||
int32_t approxBytesPerPixel();
|
||||
|
||||
static Napi::FunctionReference constructor;
|
||||
static void Initialize(Napi::Object target);
|
||||
const static cairo_format_t DEFAULT_FORMAT = CAIRO_FORMAT_ARGB32;
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#include "PdfBackend.h"
|
||||
|
||||
#include <cairo-pdf.h>
|
||||
#include "../InstanceData.h"
|
||||
#include "../Canvas.h"
|
||||
#include "../closure.h"
|
||||
|
||||
PdfBackend::PdfBackend(Napi::CallbackInfo& info) : Napi::ObjectWrap<PdfBackend>(info), Backend("pdf", info) {
|
||||
PdfBackend::createSurface();
|
||||
}
|
||||
|
||||
PdfBackend::~PdfBackend() {
|
||||
cairo_surface_finish(surface);
|
||||
if (_closure) delete _closure;
|
||||
destroySurface();
|
||||
}
|
||||
|
||||
cairo_surface_t* PdfBackend::createSurface() {
|
||||
if (!_closure) _closure = new PdfSvgClosure(canvas);
|
||||
surface = cairo_pdf_surface_create_for_stream(PdfSvgClosure::writeVec, _closure, width, height);
|
||||
return surface;
|
||||
}
|
||||
|
||||
cairo_surface_t* PdfBackend::recreateSurface() {
|
||||
cairo_pdf_surface_set_size(surface, width, height);
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
void
|
||||
PdfBackend::Initialize(Napi::Object target) {
|
||||
Napi::Env env = target.Env();
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
Napi::Function ctor = DefineClass(env, "PdfBackend", {});
|
||||
data->PdfBackendCtor = Napi::Persistent(ctor);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "Backend.h"
|
||||
#include "../closure.h"
|
||||
#include <napi.h>
|
||||
|
||||
class PdfBackend : public Napi::ObjectWrap<PdfBackend>, public Backend
|
||||
{
|
||||
private:
|
||||
cairo_surface_t* createSurface();
|
||||
cairo_surface_t* recreateSurface();
|
||||
|
||||
public:
|
||||
PdfSvgClosure* _closure = NULL;
|
||||
inline PdfSvgClosure* closure() { return _closure; }
|
||||
|
||||
PdfBackend(Napi::CallbackInfo& info);
|
||||
~PdfBackend();
|
||||
|
||||
static Napi::FunctionReference constructor;
|
||||
static void Initialize(Napi::Object target);
|
||||
static Napi::Value New(const Napi::CallbackInfo& info);
|
||||
};
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
#include "SvgBackend.h"
|
||||
|
||||
#include <cairo-svg.h>
|
||||
#include <napi.h>
|
||||
#include "../Canvas.h"
|
||||
#include "../closure.h"
|
||||
#include "../InstanceData.h"
|
||||
#include <cassert>
|
||||
|
||||
using namespace Napi;
|
||||
|
||||
SvgBackend::SvgBackend(Napi::CallbackInfo& info) : Napi::ObjectWrap<SvgBackend>(info), Backend("svg", info) {
|
||||
SvgBackend::createSurface();
|
||||
}
|
||||
|
||||
SvgBackend::~SvgBackend() {
|
||||
cairo_surface_finish(surface);
|
||||
if (_closure) {
|
||||
delete _closure;
|
||||
_closure = nullptr;
|
||||
}
|
||||
destroySurface();
|
||||
}
|
||||
|
||||
cairo_surface_t* SvgBackend::createSurface() {
|
||||
assert(!_closure);
|
||||
_closure = new PdfSvgClosure(canvas);
|
||||
surface = cairo_svg_surface_create_for_stream(PdfSvgClosure::writeVec, _closure, width, height);
|
||||
return surface;
|
||||
}
|
||||
|
||||
cairo_surface_t* SvgBackend::recreateSurface() {
|
||||
cairo_surface_finish(surface);
|
||||
delete _closure;
|
||||
_closure = nullptr;
|
||||
cairo_surface_destroy(surface);
|
||||
|
||||
return createSurface();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SvgBackend::Initialize(Napi::Object target) {
|
||||
Napi::Env env = target.Env();
|
||||
Napi::Function ctor = DefineClass(env, "SvgBackend", {});
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
data->SvgBackendCtor = Napi::Persistent(ctor);
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "Backend.h"
|
||||
#include "../closure.h"
|
||||
#include <napi.h>
|
||||
|
||||
class SvgBackend : public Napi::ObjectWrap<SvgBackend>, public Backend
|
||||
{
|
||||
private:
|
||||
cairo_surface_t* createSurface();
|
||||
cairo_surface_t* recreateSurface();
|
||||
|
||||
public:
|
||||
PdfSvgClosure* _closure = NULL;
|
||||
inline PdfSvgClosure* closure() { return _closure; }
|
||||
|
||||
SvgBackend(Napi::CallbackInfo& info);
|
||||
~SvgBackend();
|
||||
|
||||
static void Initialize(Napi::Object target);
|
||||
};
|
||||
Loaded 100 of 5445 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user