diff --git a/docs/api-composite.md b/docs/api-composite.md
index 12cc100b..ebc38f3a 100644
--- a/docs/api-composite.md
+++ b/docs/api-composite.md
@@ -29,6 +29,18 @@ and [https://www.cairographics.org/operators/][2]
* `images[].input.create.height` **[Number][7]?**
* `images[].input.create.channels` **[Number][7]?** 3-4
* `images[].input.create.background` **([String][6] | [Object][4])?** parsed by the [color][8] module to extract values for red, green, blue and alpha.
+ * `images[].input.text` **[Object][4]?** describes a new text image to be created.
+
+ * `images[].input.text.text` **[string][6]?** text to render as a UTF-8 string. It can contain Pango markup, for example `LeMonde`.
+ * `images[].input.text.font` **[string][6]?** font name to render with.
+ * `images[].input.text.fontfile` **[string][6]?** absolute filesystem path to a font file that can be used by `font`.
+ * `images[].input.text.width` **[number][7]** integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries. (optional, default `0`)
+ * `images[].input.text.height` **[number][7]** integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0. (optional, default `0`)
+ * `images[].input.text.align` **[string][6]** text alignment (`'left'`, `'centre'`, `'center'`, `'right'`). (optional, default `'left'`)
+ * `images[].input.text.justify` **[boolean][9]** set this to true to apply justification to the text. (optional, default `false`)
+ * `images[].input.text.dpi` **[number][7]** the resolution (size) at which to render the text. Does not take effect if `height` is specified. (optional, default `72`)
+ * `images[].input.text.rgba` **[boolean][9]** set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `Red!`. (optional, default `false`)
+ * `images[].input.text.spacing` **[number][7]** text line height in points. Will use the font line height if none is specified. (optional, default `0`)
* `images[].blend` **[String][6]** how to blend this image with the image below. (optional, default `'over'`)
* `images[].gravity` **[String][6]** gravity at which to place the overlay. (optional, default `'centre'`)
* `images[].top` **[Number][7]?** the pixel offset from the top edge.
diff --git a/docs/api-constructor.md b/docs/api-constructor.md
index b690c539..31bce0fd 100644
--- a/docs/api-constructor.md
+++ b/docs/api-constructor.md
@@ -51,6 +51,18 @@ Implements the [stream.Duplex][1] class.
* `options.create.noise.type` **[string][12]?** type of generated noise, currently only `gaussian` is supported.
* `options.create.noise.mean` **[number][14]?** mean of pixels in generated noise.
* `options.create.noise.sigma` **[number][14]?** standard deviation of pixels in generated noise.
+ * `options.text` **[Object][13]?** describes a new text image to be created.
+
+ * `options.text.text` **[string][12]?** text to render as a UTF-8 string. It can contain Pango markup, for example `LeMonde`.
+ * `options.text.font` **[string][12]?** font name to render with.
+ * `options.text.fontfile` **[string][12]?** absolute filesystem path to a font file that can be used by `font`.
+ * `options.text.width` **[number][14]** integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries. (optional, default `0`)
+ * `options.text.height` **[number][14]** integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0. (optional, default `0`)
+ * `options.text.align` **[string][12]** text alignment (`'left'`, `'centre'`, `'center'`, `'right'`). (optional, default `'left'`)
+ * `options.text.justify` **[boolean][15]** set this to true to apply justification to the text. (optional, default `false`)
+ * `options.text.dpi` **[number][14]** the resolution (size) at which to render the text. Does not take effect if `height` is specified. (optional, default `72`)
+ * `options.text.rgba` **[boolean][15]** set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `Red!`. (optional, default `false`)
+ * `options.text.spacing` **[number][14]** text line height in points. Will use the font line height if none is specified. (optional, default `0`)
### Examples
@@ -127,6 +139,29 @@ await sharp({
}).toFile('noise.png');
```
+```javascript
+// Generate an image from text
+await sharp({
+ text: {
+ text: 'Hello, world!',
+ width: 400, // max width
+ height: 300 // max height
+ }
+}).toFile('text_bw.png');
+```
+
+```javascript
+// Generate an rgba image from text using pango markup and font
+await sharp({
+ text: {
+ text: 'Red!blue',
+ font: 'sans',
+ rgba: true,
+ dpi: 300
+ }
+}).toFile('text_rgba.png');
+```
+
* Throws **[Error][17]** Invalid parameters
Returns **[Sharp][18]**
diff --git a/docs/api-output.md b/docs/api-output.md
index f45c95d6..7d131dbd 100644
--- a/docs/api-output.md
+++ b/docs/api-output.md
@@ -22,6 +22,7 @@ A `Promise` is returned when `callback` is not provided.
`info` contains the output image `format`, `size` (bytes), `width`, `height`,
`channels` and `premultiplied` (indicating if premultiplication was used).
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
+ May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
### Examples
@@ -61,6 +62,7 @@ See [withMetadata][1] for control over this.
`channels` and `premultiplied` (indicating if premultiplication was used).
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
+May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
A `Promise` is returned when `callback` is not provided.
diff --git a/lib/composite.js b/lib/composite.js
index b6b576bc..8373767e 100644
--- a/lib/composite.js
+++ b/lib/composite.js
@@ -93,6 +93,17 @@ const blend = {
* @param {Number} [images[].input.create.height]
* @param {Number} [images[].input.create.channels] - 3-4
* @param {String|Object} [images[].input.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
+ * @param {Object} [images[].input.text] - describes a new text image to be created.
+ * @param {string} [images[].input.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `LeMonde`.
+ * @param {string} [images[].input.text.font] - font name to render with.
+ * @param {string} [images[].input.text.fontfile] - absolute filesystem path to a font file that can be used by `font`.
+ * @param {number} [images[].input.text.width=0] - integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries.
+ * @param {number} [images[].input.text.height=0] - integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0.
+ * @param {string} [images[].input.text.align='left'] - text alignment (`'left'`, `'centre'`, `'center'`, `'right'`).
+ * @param {boolean} [images[].input.text.justify=false] - set this to true to apply justification to the text.
+ * @param {number} [images[].input.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
+ * @param {boolean} [images[].input.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `Red!`.
+ * @param {number} [images[].input.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
* @param {String} [images[].blend='over'] - how to blend this image with the image below.
* @param {String} [images[].gravity='centre'] - gravity at which to place the overlay.
* @param {Number} [images[].top] - the pixel offset from the top edge.
diff --git a/lib/constructor.js b/lib/constructor.js
index 7ae48c3e..33e6ffe1 100644
--- a/lib/constructor.js
+++ b/lib/constructor.js
@@ -92,6 +92,27 @@ const debuglog = util.debuglog('sharp');
* }
* }).toFile('noise.png');
*
+ * @example
+ * // Generate an image from text
+ * await sharp({
+ * text: {
+ * text: 'Hello, world!',
+ * width: 400, // max width
+ * height: 300 // max height
+ * }
+ * }).toFile('text_bw.png');
+ *
+ * @example
+ * // Generate an rgba image from text using pango markup and font
+ * await sharp({
+ * text: {
+ * text: 'Red!blue',
+ * font: 'sans',
+ * rgba: true,
+ * dpi: 300
+ * }
+ * }).toFile('text_rgba.png');
+ *
* @param {(Buffer|Uint8Array|Uint8ClampedArray|Int8Array|Uint16Array|Int16Array|Uint32Array|Int32Array|Float32Array|Float64Array|string)} [input] - if present, can be
* a Buffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image data, or
* a TypedArray containing raw pixel image data, or
@@ -126,6 +147,17 @@ const debuglog = util.debuglog('sharp');
* @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported.
* @param {number} [options.create.noise.mean] - mean of pixels in generated noise.
* @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise.
+ * @param {Object} [options.text] - describes a new text image to be created.
+ * @param {string} [options.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `LeMonde`.
+ * @param {string} [options.text.font] - font name to render with.
+ * @param {string} [options.text.fontfile] - absolute filesystem path to a font file that can be used by `font`.
+ * @param {number} [options.text.width=0] - integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries.
+ * @param {number} [options.text.height=0] - integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0.
+ * @param {string} [options.text.align='left'] - text alignment (`'left'`, `'centre'`, `'center'`, `'right'`).
+ * @param {boolean} [options.text.justify=false] - set this to true to apply justification to the text.
+ * @param {number} [options.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
+ * @param {boolean} [options.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `Red!`.
+ * @param {number} [options.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
diff --git a/lib/input.js b/lib/input.js
index c90f7a63..7520859a 100644
--- a/lib/input.js
+++ b/lib/input.js
@@ -4,6 +4,18 @@ const color = require('color');
const is = require('./is');
const sharp = require('./sharp');
+/**
+ * Justication alignment
+ * @member
+ * @private
+ */
+const align = {
+ left: 'low',
+ center: 'centre',
+ centre: 'centre',
+ right: 'high'
+};
+
/**
* Extract input options, if any, from an object.
* @private
@@ -245,6 +257,81 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
throw new Error('Expected valid width, height and channels to create a new input image');
}
}
+ // Create a new image with text
+ if (is.defined(inputOptions.text)) {
+ if (is.object(inputOptions.text) && is.string(inputOptions.text.text)) {
+ inputDescriptor.textValue = inputOptions.text.text;
+ if (is.defined(inputOptions.text.height) && is.defined(inputOptions.text.dpi)) {
+ throw new Error('Expected only one of dpi or height');
+ }
+ if (is.defined(inputOptions.text.font)) {
+ if (is.string(inputOptions.text.font)) {
+ inputDescriptor.textFont = inputOptions.text.font;
+ } else {
+ throw is.invalidParameterError('text.font', 'string', inputOptions.text.font);
+ }
+ }
+ if (is.defined(inputOptions.text.fontfile)) {
+ if (is.string(inputOptions.text.fontfile)) {
+ inputDescriptor.textFontfile = inputOptions.text.fontfile;
+ } else {
+ throw is.invalidParameterError('text.fontfile', 'string', inputOptions.text.fontfile);
+ }
+ }
+ if (is.defined(inputOptions.text.width)) {
+ if (is.number(inputOptions.text.width)) {
+ inputDescriptor.textWidth = inputOptions.text.width;
+ } else {
+ throw is.invalidParameterError('text.textWidth', 'number', inputOptions.text.width);
+ }
+ }
+ if (is.defined(inputOptions.text.height)) {
+ if (is.number(inputOptions.text.height)) {
+ inputDescriptor.textHeight = inputOptions.text.height;
+ } else {
+ throw is.invalidParameterError('text.height', 'number', inputOptions.text.height);
+ }
+ }
+ if (is.defined(inputOptions.text.align)) {
+ if (is.string(inputOptions.text.align) && is.string(this.constructor.align[inputOptions.text.align])) {
+ inputDescriptor.textAlign = this.constructor.align[inputOptions.text.align];
+ } else {
+ throw is.invalidParameterError('text.align', 'valid alignment', inputOptions.text.align);
+ }
+ }
+ if (is.defined(inputOptions.text.justify)) {
+ if (is.bool(inputOptions.text.justify)) {
+ inputDescriptor.textJustify = inputOptions.text.justify;
+ } else {
+ throw is.invalidParameterError('text.justify', 'boolean', inputOptions.text.justify);
+ }
+ }
+ if (is.defined(inputOptions.text.dpi)) {
+ if (is.number(inputOptions.text.dpi) && is.inRange(inputOptions.text.dpi, 1, 100000)) {
+ inputDescriptor.textDpi = inputOptions.text.dpi;
+ } else {
+ throw is.invalidParameterError('text.dpi', 'number between 1 and 100000', inputOptions.text.dpi);
+ }
+ }
+ if (is.defined(inputOptions.text.rgba)) {
+ if (is.bool(inputOptions.text.rgba)) {
+ inputDescriptor.textRgba = inputOptions.text.rgba;
+ } else {
+ throw is.invalidParameterError('text.rgba', 'bool', inputOptions.text.rgba);
+ }
+ }
+ if (is.defined(inputOptions.text.spacing)) {
+ if (is.number(inputOptions.text.spacing)) {
+ inputDescriptor.textSpacing = inputOptions.text.spacing;
+ } else {
+ throw is.invalidParameterError('text.spacing', 'number', inputOptions.text.spacing);
+ }
+ }
+ delete inputDescriptor.buffer;
+ } else {
+ throw new Error('Expected a valid string to create an image with text.');
+ }
+ }
} else if (is.defined(inputOptions)) {
throw new Error('Invalid input options ' + inputOptions);
}
@@ -504,4 +591,6 @@ module.exports = function (Sharp) {
metadata,
stats
});
+ // Class attributes
+ Sharp.align = align;
};
diff --git a/lib/output.js b/lib/output.js
index 3ba4a99e..dcdfbb02 100644
--- a/lib/output.js
+++ b/lib/output.js
@@ -58,6 +58,7 @@ const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math
* `info` contains the output image `format`, `size` (bytes), `width`, `height`,
* `channels` and `premultiplied` (indicating if premultiplication was used).
* When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
+ * May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
* @returns {Promise