diff --git a/docs/api-composite.md b/docs/api-composite.md
index 12cc100ba..ebc38f3a6 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 b690c5390..52b7df97d 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
diff --git a/lib/composite.js b/lib/composite.js
index b6b576bc6..8373767e1 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 eb2c3c4ae..249406889 100644
--- a/lib/constructor.js
+++ b/lib/constructor.js
@@ -126,6 +126,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 c90f7a631..877e7b3de 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,78 @@ 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.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.textFontfile)) {
+ if (is.string(inputOptions.text.textFontfile)) {
+ inputDescriptor.textFontfile = inputOptions.text.fontfile;
+ } else {
+ throw is.invalidParameterError('text.textFontfile', 'string', inputOptions.text.textFontfile);
+ }
+ }
+ 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 +588,6 @@ module.exports = function (Sharp) {
metadata,
stats
});
+ // Class attributes
+ Sharp.align = align;
};
diff --git a/package.json b/package.json
index 1b31fd7ba..a8bf87fe3 100644
--- a/package.json
+++ b/package.json
@@ -83,7 +83,8 @@
"Chris Banks ",
"Ompal Singh ",
"Brodan "
+ "Ankur Parihar ",
+ "Brahim Ait elhaj "
],
"scripts": {
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)",
diff --git a/src/common.cc b/src/common.cc
index 751e41d73..151b20328 100644
--- a/src/common.cc
+++ b/src/common.cc
@@ -133,6 +133,42 @@ 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, "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(
+ 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(AttrAsInt64(input, "limitInputPixels"));
// Allow switch from random to sequential access
@@ -394,6 +430,25 @@ namespace sharp {
image.get_image()->Type = image.guess_interpretation();
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("width", descriptor->textWidth)
+ ->set("height", descriptor->textHeight)
+ ->set("align", descriptor->textAlign)
+ ->set("justify", descriptor->textJustify)
+ ->set("dpi", descriptor->textDpi)
+ ->set("rgba", descriptor->textRgba)
+ ->set("spacing", descriptor->textSpacing);
+ if (descriptor->textFont.length() > 0) {
+ textOptions->set("font", const_cast(descriptor->textFont.data()));
+ }
+ if (descriptor->textFontfile.length() > 0) {
+ textOptions->set("fontfile", const_cast(descriptor->textFontfile.data()));
+ }
+ image = VImage::new_memory().text(const_cast(descriptor->textValue.data()), textOptions);
+ image.get_image()->Type = image.guess_interpretation();
+ imageType = ImageType::RAW;
} else {
// From filesystem
imageType = DetermineImageType(descriptor->file.data());
diff --git a/src/common.h b/src/common.h
index 2a43597d6..3e29c4404 100644
--- a/src/common.h
+++ b/src/common.h
@@ -71,6 +71,16 @@ 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;
InputDescriptor():
buffer(nullptr),
@@ -95,7 +105,14 @@ 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) {}
};
// Convenience methods to access the attributes of a Napi::Object
diff --git a/test/fixtures/expected/text-color-pango.png b/test/fixtures/expected/text-color-pango.png
new file mode 100644
index 000000000..bad21a9eb
Binary files /dev/null and b/test/fixtures/expected/text-color-pango.png differ
diff --git a/test/fixtures/expected/text-composite.png b/test/fixtures/expected/text-composite.png
new file mode 100644
index 000000000..7db8414a9
Binary files /dev/null and b/test/fixtures/expected/text-composite.png differ
diff --git a/test/fixtures/expected/text-font.png b/test/fixtures/expected/text-font.png
new file mode 100644
index 000000000..8cf8588d9
Binary files /dev/null and b/test/fixtures/expected/text-font.png differ
diff --git a/test/fixtures/expected/text-justify-center.png b/test/fixtures/expected/text-justify-center.png
new file mode 100644
index 000000000..0b28d82ef
Binary files /dev/null and b/test/fixtures/expected/text-justify-center.png differ
diff --git a/test/fixtures/expected/text-justify-centre.png b/test/fixtures/expected/text-justify-centre.png
new file mode 100644
index 000000000..0b28d82ef
Binary files /dev/null and b/test/fixtures/expected/text-justify-centre.png differ
diff --git a/test/fixtures/expected/text-justify-left.png b/test/fixtures/expected/text-justify-left.png
new file mode 100644
index 000000000..887d8fc4f
Binary files /dev/null and b/test/fixtures/expected/text-justify-left.png differ
diff --git a/test/fixtures/expected/text-justify-right.png b/test/fixtures/expected/text-justify-right.png
new file mode 100644
index 000000000..67308f716
Binary files /dev/null and b/test/fixtures/expected/text-justify-right.png differ
diff --git a/test/fixtures/expected/text-justify.png b/test/fixtures/expected/text-justify.png
new file mode 100644
index 000000000..887d8fc4f
Binary files /dev/null and b/test/fixtures/expected/text-justify.png differ
diff --git a/test/fixtures/expected/text-spacing.png b/test/fixtures/expected/text-spacing.png
new file mode 100644
index 000000000..22104fddd
Binary files /dev/null and b/test/fixtures/expected/text-spacing.png differ
diff --git a/test/fixtures/fonts/bitstream_vera_sans/COPYRIGHT.TXT b/test/fixtures/fonts/bitstream_vera_sans/COPYRIGHT.TXT
new file mode 100644
index 000000000..e651be1c4
--- /dev/null
+++ b/test/fixtures/fonts/bitstream_vera_sans/COPYRIGHT.TXT
@@ -0,0 +1,124 @@
+Bitstream Vera Fonts Copyright
+
+The fonts have a generous copyright, allowing derivative works (as
+long as "Bitstream" or "Vera" are not in the names), and full
+redistribution (so long as they are not *sold* by themselves). They
+can be be bundled, redistributed and sold with any software.
+
+The fonts are distributed under the following copyright:
+
+Copyright
+=========
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream
+Vera is a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and associated
+documentation files (the "Font Software"), to reproduce and distribute
+the Font Software, including without limitation the rights to use,
+copy, merge, publish, distribute, and/or sell copies of the Font
+Software, and to permit persons to whom the Font Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be included in all copies of one or more of the Font Software
+typefaces.
+
+The Font Software may be modified, altered, or added to, and in
+particular the designs of glyphs or characters in the Fonts may be
+modified and additional glyphs or characters may be added to the
+Fonts, only if the fonts are renamed to names not containing either
+the words "Bitstream" or the word "Vera".
+
+This License becomes null and void to the extent applicable to Fonts
+or Font Software that has been modified and is distributed under the
+"Bitstream Vera" names.
+
+The Font Software may be sold as part of a larger software package but
+no copy of one or more of the Font Software typefaces may be sold by
+itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
+BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL,
+OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT
+SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font
+Software without prior written authorization from the Gnome Foundation
+or Bitstream Inc., respectively. For further information, contact:
+fonts at gnome dot org.
+
+Copyright FAQ
+=============
+
+ 1. I don't understand the resale restriction... What gives?
+
+ Bitstream is giving away these fonts, but wishes to ensure its
+ competitors can't just drop the fonts as is into a font sale system
+ and sell them as is. It seems fair that if Bitstream can't make money
+ from the Bitstream Vera fonts, their competitors should not be able to
+ do so either. You can sell the fonts as part of any software package,
+ however.
+
+ 2. I want to package these fonts separately for distribution and
+ sale as part of a larger software package or system. Can I do so?
+
+ Yes. A RPM or Debian package is a "larger software package" to begin
+ with, and you aren't selling them independently by themselves.
+ See 1. above.
+
+ 3. Are derivative works allowed?
+ Yes!
+
+ 4. Can I change or add to the font(s)?
+ Yes, but you must change the name(s) of the font(s).
+
+ 5. Under what terms are derivative works allowed?
+
+ You must change the name(s) of the fonts. This is to ensure the
+ quality of the fonts, both to protect Bitstream and Gnome. We want to
+ ensure that if an application has opened a font specifically of these
+ names, it gets what it expects (though of course, using fontconfig,
+ substitutions could still could have occurred during font
+ opening). You must include the Bitstream copyright. Additional
+ copyrights can be added, as per copyright law. Happy Font Hacking!
+
+ 6. If I have improvements for Bitstream Vera, is it possible they might get
+ adopted in future versions?
+
+ Yes. The contract between the Gnome Foundation and Bitstream has
+ provisions for working with Bitstream to ensure quality additions to
+ the Bitstream Vera font family. Please contact us if you have such
+ additions. Note, that in general, we will want such additions for the
+ entire family, not just a single font, and that you'll have to keep
+ both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add
+ glyphs to the font, they must be stylistically in keeping with Vera's
+ design. Vera cannot become a "ransom note" font. Jim Lyles will be
+ providing a document describing the design elements used in Vera, as a
+ guide and aid for people interested in contributing to Vera.
+
+ 7. I want to sell a software package that uses these fonts: Can I do so?
+
+ Sure. Bundle the fonts with your software and sell your software
+ with the fonts. That is the intent of the copyright.
+
+ 8. If applications have built the names "Bitstream Vera" into them,
+ can I override this somehow to use fonts of my choosing?
+
+ This depends on exact details of the software. Most open source
+ systems and software (e.g., Gnome, KDE, etc.) are now converting to
+ use fontconfig (see www.fontconfig.org) to handle font configuration,
+ selection and substitution; it has provisions for overriding font
+ names and subsituting alternatives. An example is provided by the
+ supplied local.conf file, which chooses the family Bitstream Vera for
+ "sans", "serif" and "monospace". Other software (e.g., the XFree86
+ core server) has other mechanisms for font substitution.
+
diff --git a/test/fixtures/fonts/bitstream_vera_sans/README.TXT b/test/fixtures/fonts/bitstream_vera_sans/README.TXT
new file mode 100644
index 000000000..0f71795a7
--- /dev/null
+++ b/test/fixtures/fonts/bitstream_vera_sans/README.TXT
@@ -0,0 +1,11 @@
+Contained herin is the Bitstream Vera font family.
+
+The Copyright information is found in the COPYRIGHT.TXT file (along
+with being incoporated into the fonts themselves).
+
+The releases notes are found in the file "RELEASENOTES.TXT".
+
+We hope you enjoy Vera!
+
+ Bitstream, Inc.
+ The Gnome Project
diff --git a/test/fixtures/fonts/bitstream_vera_sans/RELEASENOTES.TXT b/test/fixtures/fonts/bitstream_vera_sans/RELEASENOTES.TXT
new file mode 100644
index 000000000..270bc0d40
--- /dev/null
+++ b/test/fixtures/fonts/bitstream_vera_sans/RELEASENOTES.TXT
@@ -0,0 +1,162 @@
+Bitstream Vera Fonts - April 16, 2003
+=====================================
+
+The version number of these fonts is 1.10 to distinguish them from the
+beta test fonts.
+
+Note that the Vera copyright is incorporated in the fonts themselves.
+The License field in the fonts contains the copyright license as it
+appears below. The TrueType copyright field is not large enough to
+contain the full license, so the license is incorporated (as you might
+think if you thought about it) into the license field, which
+unfortunately can be obscure to find. (In pfaedit, see: Element->Font
+Info->TTFNames->License).
+
+Our apologies for it taking longer to complete the fonts than planned.
+Beta testers requested a tighter line spacing (less leading) and Jim
+Lyles redesigned Vera's accents to bring its line spacing to more
+typical of other fonts. This took additional time and effort. Our
+thanks to Jim for this effort above and beyond the call of duty.
+
+There are four monospace and sans faces (normal, oblique, bold, bold
+oblique) and two serif faces (normal and bold). Fontconfig/Xft2 (see
+www.fontconfig.org) can artificially oblique the serif faces for you:
+this loses hinting and distorts the faces slightly, but is visibly
+different than normal and bold, and reasonably pleasing.
+
+On systems with fontconfig 2.0 or 2.1 installed, making your sans,
+serif and monospace fonts default to these fonts is very easy. Just
+drop the file local.conf into your /etc/fonts directory. This will
+make the Bitstream fonts your default fonts for all applications using
+fontconfig (if sans, serif, or monospace names are used, as they often
+are as default values in many desktops). The XML in local.conf may
+need modification to enable subpixel decimation, if appropriate,
+however, the commented out phrase does so for XFree86 4.3, in the case
+that the server does not have sufficient information to identify the
+use of a flat panel. Fontconfig 2.2 adds Vera to the list of font
+families and will, by default use it as the default sans, serif and
+monospace fonts.
+
+During the testing of the final Vera fonts, we learned that screen
+fonts in general are only typically hinted to work correctly at
+integer pixel sizes. Vera is coded internally for integer sizes only.
+We need to investigate further to see if there are commonly used fonts
+that are hinted to be rounded but are not rounded to integer sizes due
+to oversights in their coding.
+
+Most fonts work best at 8 pixels and below if anti-aliased only, as
+the amount of work required to hint well at smaller and smaller sizes
+becomes astronomical. GASP tables are typically used to control
+whether hinting is used or not, but Freetype/Xft does not currently
+support GASP tables (which are present in Vera).
+
+To mitigate this problem, both for Vera and other fonts, there will be
+(very shortly) a new fontconfig 2.2 release that will, by default not
+apply hints if the size is below 8 pixels. if you should have a font
+that in fact has been hinted more agressively, you can use fontconfig
+to note this exception. We believe this should improve many hinted
+fonts in addition to Vera, though implemeting GASP support is likely
+the right long term solution.
+
+Font rendering in Gnome or KDE is the combination of algorithms in
+Xft2 and Freetype, along with hinting in the fonts themselves. It is
+vital to have sufficient information to disentangle problems that you
+may observe.
+
+Note that having your font rendering system set up correctly is vital
+to proper judgement of problems of the fonts:
+
+ * Freetype may or may not be configured to in ways that may
+ implement execution of possibly patented (in some parts of the world)
+ TrueType hinting algorithms, particularly at small sizes. Best
+ results are obtained while using these algorithms.
+
+ * The freetype autohinter (used when the possibly patented
+ algorithms are not used) continues to improve with each release. If
+ you are using the autohinter, please ensure you are using an up to
+ date version of freetype before reporting problems.
+
+ * Please identify what version of freetype you are using in any
+ bug reports, and how your freetype is configured.
+
+ * Make sure you are not using the freetype version included in
+ XFree86 4.3, as it has bugs that significantly degrade most fonts,
+ including Vera. if you build XFree86 4.3 from source yourself, you may
+ have installed this broken version without intending it (as I
+ did). Vera was verified with the recently released Freetype 2.1.4. On
+ many systems, 'ldd" can be used to see which freetype shared library
+ is actually being used.
+
+ * Xft/X Render does not (yet) implement gamma correction. This
+ causes significant problems rendering white text on a black background
+ (causing partial pixels to be insufficiently shaded) if the gamma of
+ your monitor has not been compensated for, and minor problems with
+ black text on a while background. The program "xgamma" can be used to
+ set a gamma correction value in the X server's color pallette. Most
+ monitors have a gamma near 2.
+
+ * Note that the Vera family uses minimal delta hinting. Your
+ results on other systems when not used anti-aliased may not be
+ entirely satisfying. We are primarily interested in reports of
+ problems on open source systems implementing Xft2/fontconfig/freetype
+ (which implements antialiasing and hinting adjustements, and
+ sophisticated subpixel decimation on flatpanels). Also, the
+ algorithms used by Xft2 adjust the hints to integer widths and the
+ results are crisper on open source systems than on Windows or
+ MacIntosh.
+
+ * Your fontconfig may (probably does) predate the release of
+ fontconfig 2.2, and you may see artifacts not present when the font is
+ used at very small sizes with hinting enabled. "vc-list -V" can be
+ used to see what version you have installed.
+
+We believe and hope that these fonts will resolve the problems
+reported during beta test. The largest change is the reduction of
+leading (interline spacing), which had annoyed a number of people, and
+reduced Vera's utility for some applcations. The Vera monospace font
+should also now make '0' and 'O' and '1' and 'l' more clearly
+distinguishable.
+
+The version of these fonts is version 1.10. Fontconfig should be
+choosing the new version of the fonts if both the released fonts and
+beta test fonts are installed (though please discard them: they have
+names of form tt20[1-12]gn.ttf). Note that older versions of
+fontconfig sometimes did not rebuild their cache correctly when new
+fonts are installed: please upgrade to fontconfig 2.2. "fc-cache -f"
+can be used to force rebuilding fontconfig's cache files.
+
+If you note problems, please send them to fonts at gnome dot org, with
+exactly which face and size and unicode point you observe the problem
+at. The xfd utility from XFree86 CVS may be useful for this (e.g. "xfd
+-fa sans"). A possibly more useful program to examine fonts at a
+variety of sizes is the "waterfall" program found in Keith Packard's
+CVS.
+
+ $ cvs -d :pserver:anoncvs@keithp.com:/local/src/CVS login
+ Logging in to :pserver:anoncvs@keithp.com:2401/local/src/CVS
+ CVS password:
+ $ cvs -d :pserver:anoncvs@keithp.com:/local/src/CVS co waterfall
+ $ cd waterfall
+ $ xmkmf -a
+ $ make
+ # make install
+ # make install.man
+
+Again, please make sure you are running an up-to-date freetype, and
+that you are only examining integer sizes.
+
+Reporting Problems
+==================
+
+Please send problem reports to fonts at gnome org, with the following
+information:
+
+ 1. Version of Freetype, Xft2 and fontconfig
+ 2. Whether TT hinting is being used, or the autohinter
+ 3. Application being used
+ 4. Character/Unicode code point that has problems (if applicable)
+ 5. Version of which operating system
+ 6. Please include a screenshot, when possible.
+
+Please check the fonts list archives before reporting problems to cut
+down on duplication.
diff --git a/test/fixtures/fonts/bitstream_vera_sans/Vera.ttf b/test/fixtures/fonts/bitstream_vera_sans/Vera.ttf
new file mode 100644
index 000000000..58cd6b5e6
Binary files /dev/null and b/test/fixtures/fonts/bitstream_vera_sans/Vera.ttf differ
diff --git a/test/fixtures/fonts/bitstream_vera_sans/VeraBI.ttf b/test/fixtures/fonts/bitstream_vera_sans/VeraBI.ttf
new file mode 100644
index 000000000..b55eee397
Binary files /dev/null and b/test/fixtures/fonts/bitstream_vera_sans/VeraBI.ttf differ
diff --git a/test/fixtures/fonts/bitstream_vera_sans/VeraBd.ttf b/test/fixtures/fonts/bitstream_vera_sans/VeraBd.ttf
new file mode 100644
index 000000000..51d6111d7
Binary files /dev/null and b/test/fixtures/fonts/bitstream_vera_sans/VeraBd.ttf differ
diff --git a/test/fixtures/fonts/bitstream_vera_sans/VeraIt.ttf b/test/fixtures/fonts/bitstream_vera_sans/VeraIt.ttf
new file mode 100644
index 000000000..cc23c9efd
Binary files /dev/null and b/test/fixtures/fonts/bitstream_vera_sans/VeraIt.ttf differ
diff --git a/test/unit/text.js b/test/unit/text.js
new file mode 100644
index 000000000..906e1d766
--- /dev/null
+++ b/test/unit/text.js
@@ -0,0 +1,298 @@
+'use strict';
+
+const assert = require('assert');
+
+const sharp = require('../../');
+const fixtures = require('../fixtures');
+const path = require('path');
+const fs = require('fs');
+
+const FONT_FILE_PATH = path.join(__dirname, '../fixtures/fonts/bitstream_vera_sans/VeraBI.ttf');
+
+describe('Text to image', function () {
+ it('text with default values', function (done) {
+ const output = fixtures.path('output.text-default.png');
+ const text = sharp({
+ text: {
+ text: 'Hello world'
+ }
+ });
+ text.toFile(output, function (err, info) {
+ if (err) throw err;
+ assert.strictEqual('png', info.format);
+ assert.strictEqual(3, info.channels);
+ assert.ok(info.width > 0);
+ assert.ok(info.height > 0);
+ sharp(output).metadata(function (err, metadata) {
+ if (err) throw err;
+ assert.strictEqual('srgb', metadata.space);
+ assert.strictEqual(72, metadata.density);
+ done();
+ });
+ });
+ });
+
+ it('text with width and height', function (done) {
+ const output = fixtures.path('output.text-width-height.png');
+ const maxWidth = 100;
+ const maxHeight = 80;
+ const text = sharp({
+ text: {
+ text: 'This is a long text that will be broken into multiple lines',
+ 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 > 0 && info.width <= maxWidth);
+ assert.ok(info.height > 0 && info.height <= maxHeight);
+ sharp(output).metadata(function (err, metadata) {
+ if (err) throw err;
+ assert.strictEqual('srgb', metadata.space);
+ assert.strictEqual(72, metadata.density);
+ done();
+ });
+ });
+ });
+
+ it('text with dpi', function (done) {
+ const output = fixtures.path('output.text-dpi.png');
+ const dpi = 300;
+ const text = sharp({
+ text: {
+ text: 'This is a long text introducing the dpi option to the world !',
+ dpi: dpi
+ }
+ });
+ text.toFile(output, function (err, info) {
+ if (err) throw err;
+ assert.strictEqual('png', info.format);
+ assert.strictEqual(3, info.channels);
+ sharp(output).metadata(function (err, metadata) {
+ if (err) throw err;
+ assert.strictEqual('srgb', metadata.space);
+ assert.strictEqual(dpi, metadata.density);
+ done();
+ });
+ });
+ });
+
+ it('text with color and pango markup', function (done) {
+ const text = sharp({
+ text: {
+ text: 'redblue',
+ rgba: true,
+ dpi: 300
+ }
+ });
+ text.png().toBuffer((err, data, info) => {
+ if (err) throw err;
+ assert.strictEqual('png', info.format);
+ assert.strictEqual(4, info.channels);
+ fixtures.assertSimilar(fixtures.expected('text-color-pango.png'), data, done);
+ });
+ });
+
+ it('text with font', function (done) {
+ const text = sharp({
+ text: {
+ text: 'This is biG !',
+ font: 'Bitstream Vera Sans Bold Oblique 80',
+ fontfile: FONT_FILE_PATH
+ }
+ });
+ text.png().toBuffer((err, data, info) => {
+ if (err) throw err;
+ assert.strictEqual('png', info.format);
+ assert.strictEqual(3, info.channels);
+ fixtures.assertSimilar(fixtures.expected('text-font.png'), data, { threshold: 2 }, done);
+ });
+ });
+
+ describe('text align', () => {
+ const inputOptions = {
+ text: 'This is a text to justify',
+ justify: true,
+ width: 300,
+ dpi: 300
+ };
+ Object.keys(sharp.align).forEach(align => {
+ it(align, done => {
+ const expected = fixtures.expected('text-justify-' + align + '.png');
+ const text = sharp({
+ text: {
+ ...inputOptions,
+ align: align
+ }
+ });
+ text.png().toBuffer((err, data, info) => {
+ if (err) throw err;
+ assert.strictEqual('png', info.format);
+ assert.strictEqual(3, info.channels);
+ fixtures.assertSimilar(expected, data, done);
+ });
+ });
+ });
+ it('default (left)', function (done) {
+ const expected = fixtures.expected('text-justify-left.png');
+ const text = sharp({
+ text: {
+ ...inputOptions
+ }
+ });
+ text.png().toBuffer((err, data, info) => {
+ if (err) throw err;
+ fixtures.assertSimilar(expected, data, done);
+ });
+ });
+ });
+
+ it('text with spacing', function (done) {
+ const text = sharp({
+ text: {
+ text: 'Hello world again',
+ dpi: 300,
+ width: 50,
+ spacing: 50
+ }
+ });
+ text.png().toBuffer((err, data, info) => {
+ if (err) throw err;
+ fixtures.assertSimilar(fixtures.expected('text-spacing.png'), data, done);
+ });
+ });
+
+ it('text with composite', done => {
+ const width = 500;
+ const text = sharp(fixtures.inputJpg)
+ .resize(width)
+ .composite([{
+ input: {
+ text: {
+ text: 'Watermark is cool',
+ font: 'Bitstream Vera Sans Bold Oblique',
+ fontfile: FONT_FILE_PATH,
+ dpi: 300,
+ rgba: true
+ }
+ },
+ gravity: 'northeast'
+ }, {
+ input: {
+ text: {
+ text: 'cool',
+ font: 'Bitstream Vera Sans Bold Oblique 30',
+ fontfile: FONT_FILE_PATH,
+ dpi: 300,
+ rgba: true
+ }
+ },
+ left: 30,
+ top: 250
+ }]);
+ text.png().toBuffer((err, data, 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);
+ fixtures.assertSimilar(fixtures.expected('text-composite.png'), data, done);
+ });
+ });
+
+ it('font file exists', function (done) {
+ fs.stat(FONT_FILE_PATH, (err, stats) => {
+ if (err) throw err;
+ done();
+ });
+ });
+
+ it('bad text input', function () {
+ assert.throws(function () {
+ sharp({
+ text: {
+ }
+ });
+ });
+ });
+
+ it('bad font input', function () {
+ assert.throws(function () {
+ sharp({
+ text: {
+ text: 'text',
+ font: 12
+ }
+ });
+ });
+ });
+
+ 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 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'
+ }
+ });
+ });
+ });
+});