UNPKG

node-canvas-text

Version:

Using a provided Opentype.js font object, draws a string, sized to fit inside a given rectangle on an HTML5 canvas

177 lines (144 loc) 6.04 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var measureText = function measureText(text, font, fontSize) { var method = arguments.length <= 3 || arguments[3] === undefined ? 'box' : arguments[3]; var ascent = 0, descent = 0, width = 0, scale = 1 / font.unitsPerEm * fontSize, glyphs = font.stringToGlyphs(text); for (var i = 0; i < glyphs.length; i++) { var glyph = glyphs[i]; if (glyph.advanceWidth) { width += glyph.advanceWidth * scale; } if (i < glyphs.length - 1) { var kerningValue = font.getKerningValue(glyph, glyphs[i + 1]); width += kerningValue * scale; } var _glyph$getMetrics = glyph.getMetrics(); var yMin = _glyph$getMetrics.yMin; var yMax = _glyph$getMetrics.yMax; ascent = Math.max(ascent, yMax); descent = Math.min(descent, yMin); } return { width: width, height: method == 'box' ? Math.abs(ascent) * scale + Math.abs(descent) * scale : Math.abs(ascent) * scale, actualBoundingBoxAscent: ascent * scale, actualBoundingBoxDescent: descent * scale, fontBoundingBoxAscent: font.ascender * scale, fontBoundingBoxDescent: font.descender * scale }; }; var padRectangle = function padRectangle(rectangle, padding) { return { x: rectangle.x - padding, y: rectangle.y - padding, width: rectangle.width + padding * 2, height: rectangle.height + padding * 2 }; }; exports.default = function (ctx, text, fontObject) { var _rectangle = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3]; var _options = arguments.length <= 4 || arguments[4] === undefined ? {} : arguments[4]; var paddedRect = _extends({ x: 0, y: 0, width: 100, height: 100 }, _rectangle); var options = _extends({ minSize: 10, maxSize: 200, granularity: 1, hAlign: 'left', vAlign: 'bottom', fitMethod: 'box', textFillStyle: '#000', rectFillStyle: 'transparent', rectFillOnlyText: false, textPadding: 0, fillPadding: 0, drawRect: false }, _options); if (typeof text != 'string') throw 'Missing string parameter'; if ((typeof fontObject === 'undefined' ? 'undefined' : _typeof(fontObject)) != 'object') throw 'Missing fontObject parameter'; if ((typeof ctx === 'undefined' ? 'undefined' : _typeof(ctx)) != 'object') throw 'Missing ctx parameter'; if (options.minSize > options.maxSize) throw 'Min font size can not be larger than max font size'; var originalRect = paddedRect; paddedRect = padRectangle(paddedRect, options.textPadding); ctx.save(); var fontSize = options.maxSize; var textMetrics = measureText(text, fontObject, fontSize, options.fitMethod); var textWidth = textMetrics.width; var textHeight = textMetrics.height; while ((textWidth > paddedRect.width || textHeight > paddedRect.height) && fontSize >= options.minSize) { fontSize = fontSize - options.granularity; textMetrics = measureText(text, fontObject, fontSize, options.fitMethod); textWidth = textMetrics.width; textHeight = textMetrics.height; } // Calculate text coordinates based on options var xPos = paddedRect.x; var yPos = options.fitMethod == 'box' ? paddedRect.y + paddedRect.height - Math.abs(textMetrics.actualBoundingBoxDescent) : paddedRect.y + paddedRect.height; switch (options.hAlign) { case 'right': xPos = xPos + paddedRect.width - textWidth; break; case 'center':case 'middle': xPos = xPos + paddedRect.width / 2 - textWidth / 2; break; case 'left': break; default: throw "Invalid options.hAlign parameter: " + options.hAlign; break; } switch (options.vAlign) { case 'top': yPos = yPos - paddedRect.height + textHeight; break; case 'center':case 'middle': yPos = yPos + textHeight / 2 - paddedRect.height / 2; break; case 'bottom':case 'baseline': break; default: throw "Invalid options.vAlign parameter: " + options.vAlign; break; } ctx.fillStyle = 'transparent'; // Draw fill rectangle if needed if (options.rectFillStyle != 'transparent') { var fillRect = options.rectFillOnlyText ? { x: xPos, y: yPos - textHeight, width: textWidth, height: textHeight } : originalRect; fillRect = padRectangle(fillRect, options.fillPadding); ctx.fillStyle = options.rectFillStyle; ctx.fillRect(fillRect.x, fillRect.y, fillRect.width, fillRect.height); ctx.fillStyle = 'transparent'; } // Draw text var fontPath = fontObject.getPath(text, xPos, yPos, fontSize); fontPath.fill = options.textFillStyle; fontPath.draw(ctx); // Draw bounding rectangle if (options.drawRect) { // TODO: Figure out how to not stroke the text itself, just the rectangle ctx.save(); ctx.strokeStyle = 'red'; ctx.rect(paddedRect.x, paddedRect.y, paddedRect.width, paddedRect.height); ctx.stroke(); ctx.strokeStyle = 'transparent'; ctx.restore(); } ctx.restore(); };