From 60021f31beb68131f7b0d361cfd990f599a05c3c Mon Sep 17 00:00:00 2001 From: denim2x Date: Thu, 10 Aug 2017 20:52:03 +0300 Subject: [PATCH] 'deltaY', superscript, subscript etc. Honors 'deltaY' during rendering; provides functionality for 'superscript' & 'subscript'; some bug-fixing --- src/mixins/object.svg_export.js | 6 ++ src/shapes/text.class.js | 179 ++++++++++++++++++++++---------- src/util/lang_class.js | 4 + test/unit/text.js | 30 ++++++ 4 files changed, 166 insertions(+), 53 deletions(-) diff --git a/src/mixins/object.svg_export.js b/src/mixins/object.svg_export.js index 323ff0e9288..db0da1b6869 100644 --- a/src/mixins/object.svg_export.js +++ b/src/mixins/object.svg_export.js @@ -60,6 +60,7 @@ /** * Returns styles-string for svg-export + * @param {Object} style the object from which to retrieve style properties * @return {String} */ getSvgSpanStyles: function(style) { @@ -86,6 +87,11 @@ ].join(''); }, + /** + * Returns text-decoration property for svg-export + * @param {Object} style the object from which to retrieve style properties + * @return {String} + */ getSvgTextDecoration: function(style) { if ('overline' in style || 'underline' in style || 'linethrough' in style) { return 'text-decoration: ' + (style.overline ? 'overline ' : '') + diff --git a/src/shapes/text.class.js b/src/shapes/text.class.js index b16de8555df..12ca709f1bd 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 schema object (based on https://tr.im/subscript_superscript) + * @type Object + * @default + */ + superscript: { + size: 0.60, // fontSize factor + baseline: 0.67 // baseline-shift factor (upwards) + }, + + /** + * Subscript schema object (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 */ @@ -443,16 +464,9 @@ */ _extendStyles: function(index, styles) { var loc = this.get2DCursorLocation(index); - - if (!this._getLineStyle(loc.lineIndex)) { - this._setLineStyle(loc.lineIndex, {}); - } - - if (!this._getStyleDeclaration(loc.lineIndex, loc.charIndex)) { - this._setStyleDeclaration(loc.lineIndex, loc.charIndex, {}); - } - - fabric.util.object.extend(this._getStyleDeclaration(loc.lineIndex, loc.charIndex), styles); + var decl = this._getStyleDeclaration(loc.lineIndex, loc.charIndex) || {}; + fabric.util.object.extend(decl, styles); + this._setStyleDeclaration(loc.lineIndex, loc.charIndex, decl); }, /** @@ -738,7 +752,9 @@ * @private */ _setStyleDeclaration: function(lineIndex, charIndex, style) { - this.styles[lineIndex][charIndex] = style; + var decl = this._getLineStyle(lineIndex) || {}; + decl[charIndex] = style; + this._setLineStyle(lineIndex, decl); }, /** @@ -833,13 +849,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,23 +910,26 @@ * @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(); } + var box = { width: width, left: 0, - height: charStyle.fontSize, + height: style.fontSize, kernedWidth: kernedWidth, + deltaY: style.deltaY || 0, }; if (charIndex > 0 && !skipLeft) { var previousBox = this.__charBounds[lineIndex][charIndex - 1]; @@ -920,32 +939,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; @@ -1109,11 +1122,35 @@ 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(); }, + /** + * Turns the character into a 'superior figure' (aka. 'superscript') + * @param {Number} line the line number + * @param {Number} char the character number + * @returns {Object} this + */ + setSuperscript: function(line, char) { + return this.superscript.apply(this, line, char); + }, + + /** + * Turns the character into an 'inferior figure' (aka. 'subscript') + * @param {Number} line the line number + * @param {Number} char the character number + * @returns {Object} this + */ + setSubscript: function(line, char) { + return this.subscript.apply(this, line, char); + }, + /** * @private * @param {Object} prevStyle @@ -1126,7 +1163,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 +1242,35 @@ }, /** - * @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 character 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 {Object} this + */ + setPropertyAt: function(line, char, key, value) { + var decl = this._getStyleDeclaration(line, char) || {}; + decl[key] = value; + this._setStyleDeclaration(line, char, decl); + return this; }, /** @@ -1379,6 +1436,22 @@ complexity: function() { return 1; } + }, function(prototype, Text) { + prototype.superscript.apply = prototype.subscript.apply = + /** + * Mutates the style at given position in 'self' based on values from 'this' + * @param {Object} self the object to be mutated + * @param {Number} line the line number + * @param {Number} char the character number + * @returns {Object} self + */ + function(self, line, char) { + var size = self.getValueOfPropertyAt(line, char, 'fontSize'); + var dy = self.getValueOfPropertyAt(line, char, 'deltaY') || 0; + self.setPropertyAt(line, char, 'fontSize', size * this.size); + self.setPropertyAt(line, char, 'deltaY', dy + size * -this.baseline); + return self; + }; }); /* _FROM_SVG_START_ */ diff --git a/src/util/lang_class.js b/src/util/lang_class.js index 63fd1ce220c..59c36f950eb 100644 --- a/src/util/lang_class.js +++ b/src/util/lang_class.js @@ -13,6 +13,10 @@ /** @ignore */ addMethods = function(klass, source, parent) { + if (typeof source === 'function') { + return source(klass.prototype, klass, parent); + } + for (var property in source) { if (property in klass.prototype && diff --git a/test/unit/text.js b/test/unit/text.js index 2f825a6f076..073e20c4251 100644 --- a/test/unit/text.js +++ b/test/unit/text.js @@ -382,4 +382,34 @@ equal(cache, cache2, 'you get the same cache'); }); + test('text superscript', function() { + var text = new fabric.Text('xxx'); + var size = text.fontSize; + var schema = text.superscript; + + ok(typeof text.setSuperscript === 'function'); + text.setSuperscript(0, 1); + + equal(text.styles[0][0].fontSize, size, 'character 0: fontSize remained the same'); + equal(text.styles[0][0].deltaY, undefined, 'character 0: deltaY is not set'); + + equal(text.styles[0][1].fontSize, size * schema.size, 'character 1: fontSize was reduced'); + equal(text.styles[0][1].deltaY, size * -schema.baseline, 'character 1: deltaY has been set'); + }); + + test('text subscript', function() { + var text = new fabric.Text('xxx'); + var size = text.fontSize; + var schema = text.subscript; + + ok(typeof text.setSubscript === 'function'); + text.setSubscript(0, 1); + + equal(text.styles[0][0].fontSize, size, 'character 0: fontSize remained the same'); + equal(text.styles[0][0].deltaY, undefined, 'character 0: deltaY is not set'); + + equal(text.styles[0][1].fontSize, size * schema.size, 'character 1: fontSize was reduced'); + equal(text.styles[0][1].deltaY, size * -schema.baseline, 'character 1: deltaY has been set'); + }); + })();