From 4f6b6ff9cafaa907d04b49b549662e1198bd3ef1 Mon Sep 17 00:00:00 2001 From: denim2x Date: Wed, 9 Aug 2017 15:26:24 +0300 Subject: [PATCH] 'deltaY', superscript, subscript etc. Honor 'deltaY' during rendering; provide functionality for 'superscript' and 'subscript' --- src/shapes/text.class.js | 189 ++++++++++++++++++++++++++++++--------- 1 file changed, 146 insertions(+), 43 deletions(-) diff --git a/src/shapes/text.class.js b/src/shapes/text.class.js index b16de8555df..46695640357 100644 --- a/src/shapes/text.class.js +++ b/src/shapes/text.class.js @@ -45,6 +45,7 @@ 'charSpacing', 'styles' ); + /** * Text class * @class fabric.Text @@ -57,7 +58,7 @@ /** * Properties which when set cause object to change dimensions - * @type Object + * @type Array * @private */ _dimensionAffectingProps: [ @@ -168,6 +169,26 @@ */ lineHeight: 1.16, + /** + * Superscript characteristics (based on https://tr.im/subscript_superscript) + * @type Object + * @default + */ + superscript: { + size: 0.60, // fontSize factor + baseline: 0.67 // baseline-shift factor (upwards) + }, + + /** + * Subscript charateristics (based on https://tr.im/subscript_superscript) + * @type Object + * @default + */ + subscript: { + size: 0.62, // fontSize factor + baseline: -0.25 // baseline-shift factor (downwards) + }, + /** * Background color of text lines * @type String @@ -235,8 +256,8 @@ charSpacing: 0, /** - * Object containing character styles - * (where top-level properties corresponds to line number and 2nd-level properties -- to char number in a line) + * Object containing character styles - top-level properties -> line numbers, + * 2nd-level properties - charater numbers * @type Object * @default */ @@ -253,7 +274,7 @@ _measuringContext: null, /** - * Array of properties that define a style unit. + * Array of properties that define a style unit (of 'styles'). * @type {Array} * @default */ @@ -833,13 +854,13 @@ }, /** - * return height of char in fontSize for a character at lineIndex, charIndex - * @param {Number} l line Index - * @param {Number} c char index - * @return {Number} fontSize of that character + * Computes height of character at given position + * @param {Number} line the line number + * @param {Number} char the character number + * @return {Number} fontSize of the character */ - getHeightOfChar: function(l, c) { - return this.getValueOfPropertyAt(l, c, 'fontSize'); + getHeightOfChar: function(line, char) { + return this.getValueOfPropertyAt(line, char, 'fontSize'); }, /** @@ -894,22 +915,25 @@ * @param {String} grapheme to be measured * @param {Number} lineIndex index of the line where the char is * @param {Number} charIndex position in the line - * @param {String} [previousChar] character preceding the one to be measured + * @param {String} [prevGrapheme] character preceding the one to be measured */ - _getGraphemeBox: function(grapheme, lineIndex, charIndex, previousGrapheme, skipLeft) { - var charStyle = this.getCompleteStyleDeclaration(lineIndex, charIndex), - prevCharStyle = previousGrapheme ? this.getCompleteStyleDeclaration(lineIndex, charIndex - 1) : { }, - info = this._measureChar(grapheme, charStyle, previousGrapheme, prevCharStyle), - kernedWidth = info.kernedWidth, width = info.width; + _getGraphemeBox: function(grapheme, lineIndex, charIndex, prevGrapheme, skipLeft) { + var style = this.getCompleteStyleDeclaration(lineIndex, charIndex), + prevStyle = prevGrapheme ? this.getCompleteStyleDeclaration(lineIndex, charIndex - 1) : { }, + info = this._measureChar(grapheme, style, prevGrapheme, prevStyle), + kernedWidth = info.kernedWidth, + width = info.width; if (this.charSpacing !== 0) { width += this._getWidthOfCharSpacing(); kernedWidth += this._getWidthOfCharSpacing(); } + + // TODO: Take 'deltaY' into account var box = { width: width, left: 0, - height: charStyle.fontSize, + height: style.fontSize, kernedWidth: kernedWidth, }; if (charIndex > 0 && !skipLeft) { @@ -920,32 +944,26 @@ }, /** - * Calculate height of chosen line - * height of line is based mainly on fontSize - * @private - * @param {Number} lineIndex index of the line to calculate + * Calculate height of line at 'lineIndex' + * @param {Number} lineIndex index of line to calculate + * @return {Number} */ getHeightOfLine: function(lineIndex) { if (this.__lineHeights[lineIndex]) { return this.__lineHeights[lineIndex]; } - var line = this._textLines[lineIndex], - maxHeight = this.getHeightOfChar(lineIndex, 0); - - for (var i = 1, len = line.length; i < len; i++) { - var currentCharHeight = this.getHeightOfChar(lineIndex, i); - if (currentCharHeight > maxHeight) { - maxHeight = currentCharHeight; - } + var line = this._textLines[lineIndex], maxHeight = 0; + for (var i = 0, len = line.length; i < len; i++) { + maxHeight = Math.max(this.getHeightOfChar(lineIndex, i), maxHeight); + // TODO: Take 'deltaY' into account to avoid overlaps (may require 'allowOverlaps' property) } - this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult; - return this.__lineHeights[lineIndex]; + + return this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult; }, /** - * calculate text box height - * @private + * Calculate text box height */ calcTextHeight: function() { var lineHeight, height = 0; @@ -1093,7 +1111,7 @@ * @param {Number} top Top coordinate * @param {Number} lineHeight Height of the line */ - _renderChar: function(method, ctx, lineIndex, charIndex, _char, left, top) { + _renderChar: function(method, ctx, lineIndex, charIndex, _char, left, top, lineHeight) { var decl = this._getStyleDeclaration(lineIndex, charIndex), fullDecl = this.getCompleteStyleDeclaration(lineIndex, charIndex), shouldFill = method === 'fillText' && fullDecl.fill, @@ -1109,11 +1127,77 @@ if (decl && decl.textBackgroundColor) { this._removeShadow(ctx); } + if (decl && decl.deltaY) { + top += decl.deltaY; + } + shouldFill && ctx.fillText(_char, left, top); shouldStroke && ctx.strokeText(_char, left, top); decl && ctx.restore(); }, + /** + * Scales various attributes (given as keys in 'schema') based on + * the character's 'fontSize' + * @param {Number} line the line number + * @param {Number} char the character number + * @param {Object} schema object specifying what attributes to scale - and how + * @returns this + */ + scaleChar: function(line, char, schema) { + switch (typeof schema) { + case 'number': + schema = { size: schema }; + break; + case 'object': + break; + default: + throw "[scale()] Invalid 'schema': " + schema; + } + + var size = this.getValueOfPropertyAt(line, char, 'fontSize'); + for (var key in schema) { // accounts for inherited properties + var value = schema[key]; + if (typeof value != 'number') + return; + + switch (key) { + case 'fontSize': + case 'size': + this.setPropertyAt(line, char, 'fontSize', size * value); + break; + + case 'baseline': + this.setPropertyAt(line, char, 'deltaY', size * -value); + break; + } + } + + return this; + }, + + /** + * Turns the character into a 'superior figure' (aka. 'superscript') + * @param {Number} line the line number + * @param {Number} char the character number + * @returns this + */ + setSuperscript: function(line, char) { + this.scaleChar(line, char, this.superscript); + return this; + }, + + /** + * Turns the character into an 'inferior figure' (aka. 'subscript') + * @param {Number} line the line number + * @param {Number} char the character number + * @returns this + */ + setSubscript: function(line, char) { + this.scaleChar(line, char, this.subscript); + return this; + }, + /** * @private * @param {Object} prevStyle @@ -1126,7 +1210,8 @@ prevStyle.fontSize !== thisStyle.fontSize || prevStyle.fontFamily !== thisStyle.fontFamily || prevStyle.fontWeight !== thisStyle.fontWeight || - prevStyle.fontStyle !== thisStyle.fontStyle + prevStyle.fontStyle !== thisStyle.fontStyle || + prevStyle.deltaY != thisStyle.deltaY ); }, @@ -1204,16 +1289,34 @@ }, /** - * @private - * @param {Number} LineIndex - * @param {Number} charIndex - * @param {String} property - + * Retrieves the value of property at given character position + * @param {Number} lineIndex the line number + * @param {Number} charIndex the charater number + * @param {String} property the property name + * @returns the value of 'property' */ getValueOfPropertyAt: function(lineIndex, charIndex, property) { - var charStyle = this._getStyleDeclaration(lineIndex, charIndex), - styleDecoration = charStyle && typeof charStyle[property] !== 'undefined'; - return styleDecoration ? charStyle[property] : this[property]; + var charStyle = this._getStyleDeclaration(lineIndex, charIndex); + + if (charStyle && typeof charStyle[property] !== 'undefined') + return charStyle[property]; + + return this[property]; + }, + + /** + * Assigns 'value' to the property 'key' at given charater position + * @param {Number} line the line number + * @param {Number} char the character number + * @param {String} key the property name + * @param {Any} value the value + * @returns 'value' + */ + setPropertyAt: function(line, char, key, value) { + var decl = this._getStyleDeclaration(line, char) || {}; + decl[key] = value; + this._setStyleDeclaration(line, char, decl); + return value; }, /**