mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Expose vips_text to create an image containing rendered text (#3252)
This commit is contained in:
parent
76c4c51e2a
commit
ea7cf2a2ef
@ -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 `<i>Le</i>Monde`.
|
||||
* `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 `<span foreground="red">Red!</span>`. (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.
|
||||
|
@ -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 `<i>Le</i>Monde`.
|
||||
* `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 `<span foreground="red">Red!</span>`. (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: '<span foreground="red">Red!</span><span background="cyan">blue</span>',
|
||||
font: 'sans',
|
||||
rgba: true,
|
||||
dpi: 300
|
||||
}
|
||||
}).toFile('text_rgba.png');
|
||||
```
|
||||
|
||||
* Throws **[Error][17]** Invalid parameters
|
||||
|
||||
Returns **[Sharp][18]**
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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 `<i>Le</i>Monde`.
|
||||
* @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 `<span foreground="red">Red!</span>`.
|
||||
* @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.
|
||||
|
@ -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: '<span foreground="red">Red!</span><span background="cyan">blue</span>',
|
||||
* 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 `<i>Le</i>Monde`.
|
||||
* @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 `<span foreground="red">Red!</span>`.
|
||||
* @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
|
||||
*/
|
||||
|
89
lib/input.js
89
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;
|
||||
};
|
||||
|
@ -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<Object>} - when no callback is provided
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
@ -98,6 +99,7 @@ function toFile (fileOut, callback) {
|
||||
* - `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.
|
||||
*
|
||||
* A `Promise` is returned when `callback` is not provided.
|
||||
*
|
||||
|
@ -83,7 +83,8 @@
|
||||
"Chris Banks <christopher.bradley.banks@gmail.com>",
|
||||
"Ompal Singh <ompal.hitm09@gmail.com>",
|
||||
"Brodan <christopher.hranj@gmail.com",
|
||||
"Ankur Parihar <ankur.github@gmail.com>"
|
||||
"Ankur Parihar <ankur.github@gmail.com>",
|
||||
"Brahim Ait elhaj <brahima@gmail.com>"
|
||||
],
|
||||
"scripts": {
|
||||
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)",
|
||||
|
@ -133,6 +133,39 @@ namespace sharp {
|
||||
descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
|
||||
}
|
||||
}
|
||||
// Create new image with text
|
||||
if (HasAttr(input, "textValue")) {
|
||||
descriptor->textValue = AttrAsStr(input, "textValue");
|
||||
if (HasAttr(input, "textFont")) {
|
||||
descriptor->textFont = AttrAsStr(input, "textFont");
|
||||
}
|
||||
if (HasAttr(input, "textFontfile")) {
|
||||
descriptor->textFontfile = AttrAsStr(input, "textFontfile");
|
||||
}
|
||||
if (HasAttr(input, "textWidth")) {
|
||||
descriptor->textWidth = AttrAsUint32(input, "textWidth");
|
||||
}
|
||||
if (HasAttr(input, "textHeight")) {
|
||||
descriptor->textHeight = AttrAsUint32(input, "textHeight");
|
||||
}
|
||||
if (HasAttr(input, "textAlign")) {
|
||||
descriptor->textAlign = static_cast<VipsAlign>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_ALIGN,
|
||||
AttrAsStr(input, "textAlign").data()));
|
||||
}
|
||||
if (HasAttr(input, "textJustify")) {
|
||||
descriptor->textJustify = AttrAsBool(input, "textJustify");
|
||||
}
|
||||
if (HasAttr(input, "textDpi")) {
|
||||
descriptor->textDpi = AttrAsUint32(input, "textDpi");
|
||||
}
|
||||
if (HasAttr(input, "textRgba")) {
|
||||
descriptor->textRgba = AttrAsBool(input, "textRgba");
|
||||
}
|
||||
if (HasAttr(input, "textSpacing")) {
|
||||
descriptor->textSpacing = AttrAsUint32(input, "textSpacing");
|
||||
}
|
||||
}
|
||||
// Limit input images to a given number of pixels, where pixels = width * height
|
||||
descriptor->limitInputPixels = static_cast<uint64_t>(AttrAsInt64(input, "limitInputPixels"));
|
||||
// Allow switch from random to sequential access
|
||||
@ -395,6 +428,34 @@ namespace sharp {
|
||||
}
|
||||
image = image.cast(VIPS_FORMAT_UCHAR);
|
||||
imageType = ImageType::RAW;
|
||||
} else if (descriptor->textValue.length() > 0) {
|
||||
// Create a new image with text
|
||||
vips::VOption *textOptions = VImage::option()
|
||||
->set("align", descriptor->textAlign)
|
||||
->set("justify", descriptor->textJustify)
|
||||
->set("rgba", descriptor->textRgba)
|
||||
->set("spacing", descriptor->textSpacing)
|
||||
->set("autofit_dpi", &descriptor->textAutofitDpi);
|
||||
if (descriptor->textWidth > 0) {
|
||||
textOptions->set("width", descriptor->textWidth);
|
||||
}
|
||||
// Ignore dpi if height is set
|
||||
if (descriptor->textWidth > 0 && descriptor->textHeight > 0) {
|
||||
textOptions->set("height", descriptor->textHeight);
|
||||
} else if (descriptor->textDpi > 0) {
|
||||
textOptions->set("dpi", descriptor->textDpi);
|
||||
}
|
||||
if (descriptor->textFont.length() > 0) {
|
||||
textOptions->set("font", const_cast<char*>(descriptor->textFont.data()));
|
||||
}
|
||||
if (descriptor->textFontfile.length() > 0) {
|
||||
textOptions->set("fontfile", const_cast<char*>(descriptor->textFontfile.data()));
|
||||
}
|
||||
image = VImage::text(const_cast<char *>(descriptor->textValue.data()), textOptions);
|
||||
if (!descriptor->textRgba) {
|
||||
image = image.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_B_W));
|
||||
}
|
||||
imageType = ImageType::RAW;
|
||||
} else {
|
||||
// From filesystem
|
||||
imageType = DetermineImageType(descriptor->file.data());
|
||||
|
21
src/common.h
21
src/common.h
@ -71,6 +71,17 @@ namespace sharp {
|
||||
std::string createNoiseType;
|
||||
double createNoiseMean;
|
||||
double createNoiseSigma;
|
||||
std::string textValue;
|
||||
std::string textFont;
|
||||
std::string textFontfile;
|
||||
int textWidth;
|
||||
int textHeight;
|
||||
VipsAlign textAlign;
|
||||
bool textJustify;
|
||||
int textDpi;
|
||||
bool textRgba;
|
||||
int textSpacing;
|
||||
int textAutofitDpi;
|
||||
|
||||
InputDescriptor():
|
||||
buffer(nullptr),
|
||||
@ -95,7 +106,15 @@ namespace sharp {
|
||||
createHeight(0),
|
||||
createBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||
createNoiseMean(0.0),
|
||||
createNoiseSigma(0.0) {}
|
||||
createNoiseSigma(0.0),
|
||||
textWidth(0),
|
||||
textHeight(0),
|
||||
textAlign(VIPS_ALIGN_LOW),
|
||||
textJustify(FALSE),
|
||||
textDpi(72),
|
||||
textRgba(FALSE),
|
||||
textSpacing(0),
|
||||
textAutofitDpi(0) {}
|
||||
};
|
||||
|
||||
// Convenience methods to access the attributes of a Napi::Object
|
||||
|
@ -1185,6 +1185,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
info.Set("trimOffsetTop", static_cast<int32_t>(baton->trimOffsetTop));
|
||||
}
|
||||
|
||||
if (baton->input->textAutofitDpi) {
|
||||
info.Set("textAutofitDpi", static_cast<uint32_t>(baton->input->textAutofitDpi));
|
||||
}
|
||||
|
||||
if (baton->bufferOutLength > 0) {
|
||||
// Add buffer size to info
|
||||
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
|
||||
|
297
test/unit/text.js
Normal file
297
test/unit/text.js
Normal file
@ -0,0 +1,297 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
describe('Text to image', () => {
|
||||
it('text with default values', async () => {
|
||||
const output = fixtures.path('output.text-default.png');
|
||||
const text = sharp({
|
||||
text: {
|
||||
text: 'Hello, world !'
|
||||
}
|
||||
});
|
||||
const info = await text.png().toFile(output);
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(3, info.channels);
|
||||
assert.strictEqual(false, info.premultiplied);
|
||||
assert.ok(info.width > 10);
|
||||
assert.ok(info.height > 8);
|
||||
const metadata = await sharp(output).metadata();
|
||||
assert.strictEqual('uchar', metadata.depth);
|
||||
assert.strictEqual('srgb', metadata.space);
|
||||
assert.strictEqual(72, metadata.density);
|
||||
const stats = await sharp(output).stats();
|
||||
assert.strictEqual(0, stats.channels[0].min);
|
||||
assert.strictEqual(255, stats.channels[0].max);
|
||||
assert.strictEqual(0, stats.channels[1].min);
|
||||
assert.strictEqual(255, stats.channels[1].max);
|
||||
assert.strictEqual(0, stats.channels[2].min);
|
||||
assert.strictEqual(255, stats.channels[2].max);
|
||||
assert.ok(info.textAutofitDpi > 0);
|
||||
});
|
||||
|
||||
it('text with width and height', function (done) {
|
||||
const output = fixtures.path('output.text-width-height.png');
|
||||
const maxWidth = 500;
|
||||
const maxHeight = 500;
|
||||
const text = sharp({
|
||||
text: {
|
||||
text: 'Hello, world!',
|
||||
width: maxWidth,
|
||||
height: maxHeight
|
||||
}
|
||||
});
|
||||
text.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(3, info.channels);
|
||||
assert.ok(info.width > 10 && info.width <= maxWidth);
|
||||
assert.ok(info.height > 10 && info.height <= maxHeight);
|
||||
assert.ok(Math.abs(info.width - maxWidth) < 50);
|
||||
assert.ok(info.textAutofitDpi > 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('text with dpi', function (done) {
|
||||
const output = fixtures.path('output.text-dpi.png');
|
||||
const dpi = 300;
|
||||
const text = sharp({
|
||||
text: {
|
||||
text: 'Hello, world!',
|
||||
dpi: dpi
|
||||
}
|
||||
});
|
||||
text.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
sharp(output).metadata(function (err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(dpi, metadata.density);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('text with color and pango markup', function (done) {
|
||||
const output = fixtures.path('output.text-color-pango.png');
|
||||
const dpi = 300;
|
||||
const text = sharp({
|
||||
text: {
|
||||
text: '<span foreground="red" font="100">red</span><span font="50" background="cyan">blue</span>',
|
||||
rgba: true,
|
||||
dpi: dpi
|
||||
}
|
||||
});
|
||||
text.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(4, info.channels);
|
||||
sharp(output).metadata(function (err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(dpi, metadata.density);
|
||||
assert.strictEqual('uchar', metadata.depth);
|
||||
assert.strictEqual(true, metadata.hasAlpha);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('text with font', function (done) {
|
||||
const output = fixtures.path('output.text-with-font.png');
|
||||
const text = sharp({
|
||||
text: {
|
||||
text: 'Hello, world!',
|
||||
font: 'sans 100'
|
||||
}
|
||||
});
|
||||
text.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(3, info.channels);
|
||||
assert.ok(info.width > 30);
|
||||
assert.ok(info.height > 10);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('text with justify and composite', done => {
|
||||
const output = fixtures.path('output.text-composite.png');
|
||||
const width = 500;
|
||||
const dpi = 300;
|
||||
const text = sharp(fixtures.inputJpg)
|
||||
.resize(width)
|
||||
.composite([{
|
||||
input: {
|
||||
text: {
|
||||
text: '<span foreground="#ffff00">Watermark</span> <span foreground="white"><i>is cool</i></span>',
|
||||
width: 300,
|
||||
height: 300,
|
||||
justify: true,
|
||||
align: 'right',
|
||||
spacing: 50,
|
||||
rgba: true
|
||||
}
|
||||
},
|
||||
gravity: 'northeast'
|
||||
}, {
|
||||
input: {
|
||||
text: {
|
||||
text: '<span background="cyan">cool</span>',
|
||||
font: 'sans 30',
|
||||
dpi: dpi,
|
||||
rgba: true
|
||||
}
|
||||
},
|
||||
left: 30,
|
||||
top: 250
|
||||
}]);
|
||||
text.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(4, info.channels);
|
||||
assert.strictEqual(width, info.width);
|
||||
assert.strictEqual(true, info.premultiplied);
|
||||
sharp(output).metadata(function (err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('srgb', metadata.space);
|
||||
assert.strictEqual('uchar', metadata.depth);
|
||||
assert.strictEqual(true, metadata.hasAlpha);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('bad text input', function () {
|
||||
assert.throws(function () {
|
||||
sharp({
|
||||
text: {
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('fontfile input', function () {
|
||||
// Added for code coverage
|
||||
sharp({
|
||||
text: {
|
||||
text: 'text',
|
||||
fontfile: 'UnknownFont.ttf'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('bad font input', function () {
|
||||
assert.throws(function () {
|
||||
sharp({
|
||||
text: {
|
||||
text: 'text',
|
||||
font: 12
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('bad fontfile input', function () {
|
||||
assert.throws(function () {
|
||||
sharp({
|
||||
text: {
|
||||
text: 'text',
|
||||
fontfile: true
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('bad width input', function () {
|
||||
assert.throws(function () {
|
||||
sharp({
|
||||
text: {
|
||||
text: 'text',
|
||||
width: 'bad'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('bad height input', function () {
|
||||
assert.throws(function () {
|
||||
sharp({
|
||||
text: {
|
||||
text: 'text',
|
||||
height: 'bad'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('bad align input', function () {
|
||||
assert.throws(function () {
|
||||
sharp({
|
||||
text: {
|
||||
text: 'text',
|
||||
align: 'unknown'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('bad justify input', function () {
|
||||
assert.throws(function () {
|
||||
sharp({
|
||||
text: {
|
||||
text: 'text',
|
||||
justify: 'unknown'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('bad dpi input', function () {
|
||||
assert.throws(function () {
|
||||
sharp({
|
||||
text: {
|
||||
text: 'text',
|
||||
dpi: -10
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('bad rgba input', function () {
|
||||
assert.throws(function () {
|
||||
sharp({
|
||||
text: {
|
||||
text: 'text',
|
||||
rgba: -10
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('bad spacing input', function () {
|
||||
assert.throws(function () {
|
||||
sharp({
|
||||
text: {
|
||||
text: 'text',
|
||||
spacing: 'number expected'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('only height or dpi not both', function () {
|
||||
assert.throws(function () {
|
||||
sharp({
|
||||
text: {
|
||||
text: 'text',
|
||||
height: 400,
|
||||
dpi: 100
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user