Path: blob/master/node_modules/@jimp/plugin-print/src/index.js
1126 views
import Path from 'path';1import bMFont from 'load-bmfont';2import { isNodePattern, throwError } from '@jimp/utils';3import { measureText, measureTextHeight } from './measure-text';45function xOffsetBasedOnAlignment(constants, font, line, maxWidth, alignment) {6if (alignment === constants.HORIZONTAL_ALIGN_LEFT) {7return 0;8}910if (alignment === constants.HORIZONTAL_ALIGN_CENTER) {11return (maxWidth - measureText(font, line)) / 2;12}1314return maxWidth - measureText(font, line);15}1617function drawCharacter(image, font, x, y, char) {18if (char.width > 0 && char.height > 0) {19const characterPage = font.pages[char.page];2021image.blit(22characterPage,23x + char.xoffset,24y + char.yoffset,25char.x,26char.y,27char.width,28char.height29);30}3132return image;33}3435function printText(font, x, y, text, defaultCharWidth) {36for (let i = 0; i < text.length; i++) {37let char;3839if (font.chars[text[i]]) {40char = text[i];41} else if (/\s/.test(text[i])) {42char = '';43} else {44char = '?';45}4647const fontChar = font.chars[char] || {};48const fontKerning = font.kernings[char];4950drawCharacter(this, font, x, y, fontChar || {});5152const kerning =53fontKerning && fontKerning[text[i + 1]] ? fontKerning[text[i + 1]] : 0;5455x += kerning + (fontChar.xadvance || defaultCharWidth);56}57}5859function splitLines(font, text, maxWidth) {60const words = text.split(' ');61const lines = [];62let currentLine = [];63let longestLine = 0;6465words.forEach(word => {66const line = [...currentLine, word].join(' ');67const length = measureText(font, line);6869if (length <= maxWidth) {70if (length > longestLine) {71longestLine = length;72}7374currentLine.push(word);75} else {76lines.push(currentLine);77currentLine = [word];78}79});8081lines.push(currentLine);8283return {84lines,85longestLine86};87}8889function loadPages(Jimp, dir, pages) {90const newPages = pages.map(page => {91return Jimp.read(dir + '/' + page);92});9394return Promise.all(newPages);95}9697const dir = process.env.DIRNAME || `${__dirname}/../`;9899export default () => ({100constants: {101measureText,102measureTextHeight,103FONT_SANS_8_BLACK: Path.join(104dir,105'fonts/open-sans/open-sans-8-black/open-sans-8-black.fnt'106),107FONT_SANS_10_BLACK: Path.join(108dir,109'fonts/open-sans/open-sans-10-black/open-sans-10-black.fnt'110),111FONT_SANS_12_BLACK: Path.join(112dir,113'fonts/open-sans/open-sans-12-black/open-sans-12-black.fnt'114),115FONT_SANS_14_BLACK: Path.join(116dir,117'fonts/open-sans/open-sans-14-black/open-sans-14-black.fnt'118),119FONT_SANS_16_BLACK: Path.join(120dir,121'fonts/open-sans/open-sans-16-black/open-sans-16-black.fnt'122),123FONT_SANS_32_BLACK: Path.join(124dir,125'fonts/open-sans/open-sans-32-black/open-sans-32-black.fnt'126),127FONT_SANS_64_BLACK: Path.join(128dir,129'fonts/open-sans/open-sans-64-black/open-sans-64-black.fnt'130),131FONT_SANS_128_BLACK: Path.join(132dir,133'fonts/open-sans/open-sans-128-black/open-sans-128-black.fnt'134),135136FONT_SANS_8_WHITE: Path.join(137dir,138'fonts/open-sans/open-sans-8-white/open-sans-8-white.fnt'139),140FONT_SANS_16_WHITE: Path.join(141dir,142'fonts/open-sans/open-sans-16-white/open-sans-16-white.fnt'143),144FONT_SANS_32_WHITE: Path.join(145dir,146'fonts/open-sans/open-sans-32-white/open-sans-32-white.fnt'147),148FONT_SANS_64_WHITE: Path.join(149dir,150'fonts/open-sans/open-sans-64-white/open-sans-64-white.fnt'151),152FONT_SANS_128_WHITE: Path.join(153dir,154'fonts/open-sans/open-sans-128-white/open-sans-128-white.fnt'155),156157/**158* Loads a bitmap font from a file159* @param {string} file the file path of a .fnt file160* @param {function(Error, Jimp)} cb (optional) a function to call when the font is loaded161* @returns {Promise} a promise162*/163loadFont(file, cb) {164if (typeof file !== 'string')165return throwError.call(this, 'file must be a string', cb);166167return new Promise((resolve, reject) => {168cb =169cb ||170function(err, font) {171if (err) reject(err);172else resolve(font);173};174175bMFont(file, (err, font) => {176const chars = {};177const kernings = {};178179if (err) {180return throwError.call(this, err, cb);181}182183for (let i = 0; i < font.chars.length; i++) {184chars[String.fromCharCode(font.chars[i].id)] = font.chars[i];185}186187for (let i = 0; i < font.kernings.length; i++) {188const firstString = String.fromCharCode(font.kernings[i].first);189kernings[firstString] = kernings[firstString] || {};190kernings[firstString][191String.fromCharCode(font.kernings[i].second)192] = font.kernings[i].amount;193}194195loadPages(this, Path.dirname(file), font.pages).then(pages => {196cb(null, {197chars,198kernings,199pages,200common: font.common,201info: font.info202});203});204});205});206}207},208209class: {210/**211* Draws a text on a image on a given boundary212* @param {Jimp} font a bitmap font loaded from `Jimp.loadFont` command213* @param {number} x the x position to start drawing the text214* @param {number} y the y position to start drawing the text215* @param {any} text the text to draw (string or object with `text`, `alignmentX`, and/or `alignmentY`)216* @param {number} maxWidth (optional) the boundary width to draw in217* @param {number} maxHeight (optional) the boundary height to draw in218* @param {function(Error, Jimp)} cb (optional) a function to call when the text is written219* @returns {Jimp} this for chaining of methods220*/221print(font, x, y, text, maxWidth, maxHeight, cb) {222if (typeof maxWidth === 'function' && typeof cb === 'undefined') {223cb = maxWidth;224maxWidth = Infinity;225}226227if (typeof maxWidth === 'undefined') {228maxWidth = Infinity;229}230231if (typeof maxHeight === 'function' && typeof cb === 'undefined') {232cb = maxHeight;233maxHeight = Infinity;234}235236if (typeof maxHeight === 'undefined') {237maxHeight = Infinity;238}239240if (typeof font !== 'object') {241return throwError.call(this, 'font must be a Jimp loadFont', cb);242}243244if (245typeof x !== 'number' ||246typeof y !== 'number' ||247typeof maxWidth !== 'number'248) {249return throwError.call(this, 'x, y and maxWidth must be numbers', cb);250}251252if (typeof maxWidth !== 'number') {253return throwError.call(this, 'maxWidth must be a number', cb);254}255256if (typeof maxHeight !== 'number') {257return throwError.call(this, 'maxHeight must be a number', cb);258}259260let alignmentX;261let alignmentY;262263if (264typeof text === 'object' &&265text.text !== null &&266text.text !== undefined267) {268alignmentX = text.alignmentX || this.constructor.HORIZONTAL_ALIGN_LEFT;269alignmentY = text.alignmentY || this.constructor.VERTICAL_ALIGN_TOP;270({ text } = text);271} else {272alignmentX = this.constructor.HORIZONTAL_ALIGN_LEFT;273alignmentY = this.constructor.VERTICAL_ALIGN_TOP;274text = text.toString();275}276277if (278maxHeight !== Infinity &&279alignmentY === this.constructor.VERTICAL_ALIGN_BOTTOM280) {281y += maxHeight - measureTextHeight(font, text, maxWidth);282} else if (283maxHeight !== Infinity &&284alignmentY === this.constructor.VERTICAL_ALIGN_MIDDLE285) {286y += maxHeight / 2 - measureTextHeight(font, text, maxWidth) / 2;287}288289const defaultCharWidth = Object.entries(font.chars)[0][1].xadvance;290const { lines, longestLine } = splitLines(font, text, maxWidth);291292lines.forEach(line => {293const lineString = line.join(' ');294const alignmentWidth = xOffsetBasedOnAlignment(295this.constructor,296font,297lineString,298maxWidth,299alignmentX300);301302printText.call(303this,304font,305x + alignmentWidth,306y,307lineString,308defaultCharWidth309);310311y += font.common.lineHeight;312});313314if (isNodePattern(cb)) {315cb.call(this, null, this, { x: x + longestLine, y });316}317318return this;319}320}321});322323324