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' + } + }); + }); + }); +});