diff --git a/HEADER.js b/HEADER.js index c448a14e..6204b0d7 100644 --- a/HEADER.js +++ b/HEADER.js @@ -49,3 +49,4 @@ fabric.SHARED_ATTRIBUTES = [ * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion. */ fabric.DPI = 96; +fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)'; diff --git a/src/canvas.class.js b/src/canvas.class.js index fc62aeae..32cfdd6b 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -649,7 +649,7 @@ angle = 360 + angle; } - t.target.angle = angle; + t.target.angle = angle % 360; }, /** diff --git a/src/parser.js b/src/parser.js index c43e0a0a..f24e5dec 100644 --- a/src/parser.js +++ b/src/parser.js @@ -56,7 +56,7 @@ return attr; } - function normalizeValue(attr, value, parentAttributes) { + function normalizeValue(attr, value, parentAttributes, fontSize) { var isArray = Object.prototype.toString.call(value) === '[object Array]', parsed; @@ -88,7 +88,7 @@ value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; } else { - parsed = isArray ? value.map(parseUnit) : parseUnit(value); + parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize); } return (!isArray && isNaN(parsed) ? value : parsed); @@ -167,7 +167,7 @@ ], // == begin transform regexp - number = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)', + number = fabric.reNum, commaWsp = '(?:\\s+,?\\s*|,\\s*)', @@ -270,40 +270,6 @@ }; })(); - function parseFontDeclaration(value, oStyle) { - - // TODO: support non-px font size - var match = value.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/); - - if (!match) { - return; - } - - var fontStyle = match[1], - // font variant is not used - // fontVariant = match[2], - fontWeight = match[3], - fontSize = match[4], - lineHeight = match[5], - fontFamily = match[6]; - - if (fontStyle) { - oStyle.fontStyle = fontStyle; - } - if (fontWeight) { - oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); - } - if (fontSize) { - oStyle.fontSize = parseFloat(fontSize); - } - if (fontFamily) { - oStyle.fontFamily = fontFamily; - } - if (lineHeight) { - oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight; - } - } - /** * @private */ @@ -315,12 +281,7 @@ attr = normalizeAttr(pair[0].trim().toLowerCase()); value = normalizeValue(attr, pair[1].trim()); - if (attr === 'font') { - parseFontDeclaration(value, oStyle); - } - else { - oStyle[attr] = value; - } + oStyle[attr] = value; }); } @@ -337,12 +298,7 @@ attr = normalizeAttr(prop.toLowerCase()); value = normalizeValue(attr, style[prop]); - if (attr === 'font') { - parseFontDeclaration(value, oStyle); - } - else { - oStyle[attr] = value; - } + oStyle[attr] = value; } } @@ -431,7 +387,7 @@ } if (attr.nodeName === 'transform') { - currentTrans = currentTrans + ' ' + attr.nodeValue; + currentTrans = attr.nodeValue + ' ' + currentTrans; } else { el2.setAttribute(attr.nodeName, attr.nodeValue); @@ -453,14 +409,12 @@ // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute // matches, e.g.: +14.56e-12, etc. - var reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)', - - reViewBoxAttrValue = new RegExp( + var reViewBoxAttrValue = new RegExp( '^' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*' + + '\\s*(' + fabric.reNum + '+)\\s*,?' + + '\\s*(' + fabric.reNum + '+)\\s*,?' + + '\\s*(' + fabric.reNum + '+)\\s*,?' + + '\\s*(' + fabric.reNum + '+)\\s*' + '$' ), viewBoxAttr = element.getAttribute('viewBox'), @@ -588,7 +542,6 @@ fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc); fabric.cssRules[svgUid] = fabric.getCSSRules(doc); // Precedence of rules: style > class > attribute - fabric.parseElements(elements, function(instances) { fabric.documentParsingTime = new Date() - startTime; if (callback) { @@ -656,6 +609,48 @@ } extend(fabric, { + /** + * Parses a short font declaration, building adding its properties to a style object + * @static + * @function + * @memberOf fabric + * @param {String} value font declaration + * @param {Object} oStyle definition + */ + parseFontDeclaration: function(value, oStyle) { + var fontDeclaration = '(normal|italic)?\\s*(normal|small-caps)?\\s*' + + '(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' + + fabric.reNum + + '(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)', + match = value.match(fontDeclaration); + + if (!match) { + return; + } + var fontStyle = match[1], + // font variant is not used + // fontVariant = match[2], + fontWeight = match[3], + fontSize = match[4], + lineHeight = match[5], + fontFamily = match[6]; + + if (fontStyle) { + oStyle.fontStyle = fontStyle; + } + if (fontWeight) { + oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); + } + if (fontSize) { + oStyle.fontSize = parseUnit(fontSize); + } + if (fontFamily) { + oStyle.fontFamily = fontFamily; + } + if (lineHeight) { + oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight; + } + }, /** * Parses an SVG document, returning all of the gradient declarations found in it @@ -717,7 +712,8 @@ } var value, - parentAttributes = { }; + parentAttributes = { }, + fontSize; if (typeof svgUid === 'undefined') { svgUid = element.getAttribute('svgUid'); @@ -726,12 +722,14 @@ if (element.parentNode && /^symbol|[g|a]$/i.test(element.parentNode.nodeName)) { parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid); } + fontSize = (parentAttributes && parentAttributes.fontSize ) || + element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE; var ownAttributes = attributes.reduce(function(memo, attr) { value = element.getAttribute(attr); if (value) { attr = normalizeAttr(attr); - value = normalizeValue(attr, value, parentAttributes); + value = normalizeValue(attr, value, parentAttributes, fontSize); memo[attr] = value; } @@ -742,7 +740,9 @@ // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes) ownAttributes = extend(ownAttributes, extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element))); - + if (ownAttributes.font) { + fabric.parseFontDeclaration(ownAttributes.font, ownAttributes); + } return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes)); }, @@ -859,7 +859,11 @@ } rule = match[1]; rule.split(',').forEach(function(_rule) { - allRules[_rule.trim()] = fabric.util.object.clone(ruleObj); + _rule = _rule.replace(/^svg/i, '').trim(); + if (_rule === '') { + return; + } + allRules[_rule] = fabric.util.object.clone(ruleObj); }); }); } diff --git a/src/util/misc.js b/src/util/misc.js index e35439ab..6788ca95 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -138,10 +138,12 @@ * @param {Number|String} value number to operate on * @return {Number|String} */ - parseUnit: function(value) { + parseUnit: function(value, fontSize) { var unit = /\D{0,2}$/.exec(value), number = parseFloat(value); - + if (!fontSize) { + fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; + } switch (unit[0]) { case 'mm': return number * fabric.DPI / 25.4; @@ -158,6 +160,9 @@ case 'pc': return number * fabric.DPI / 72 * 12; // or * 16 + case 'em': + return number * fontSize; + default: return number; } diff --git a/test/unit/parser.js b/test/unit/parser.js index 77ace884..64fcdce0 100644 --- a/test/unit/parser.js +++ b/test/unit/parser.js @@ -125,15 +125,15 @@ var element = fabric.document.createElement('path'); element.setAttribute('style', 'left:10px;top:22.3em;width:103.45pt;height:20%;'); - - // TODO: looks like this still fails with % and em values + var styleObj = fabric.parseStyleAttribute(element); + // TODO: looks like this still fails with % values var expectedObject = { 'left': 10, - 'top': 22.3, + 'top': 356.8, 'width': 137.93333333333334, 'height': 20 }; - deepEqual(fabric.parseStyleAttribute(element), expectedObject); + deepEqual(styleObj, expectedObject); }); test('parseStyleAttribute with one pair', function() { @@ -160,13 +160,33 @@ test('parseStyleAttribute with short font declaration', function() { var element = fabric.document.createElement('path'); element.setAttribute('style', 'font: italic 12px Arial,Helvetica,sans-serif'); - + var styleObj = fabric.parseStyleAttribute(element); + if (styleObj.font) { + fabric.parseFontDeclaration(styleObj.font, styleObj); + } var expectedObject = { + 'font': 'italic 12px Arial,Helvetica,sans-serif', 'fontSize': 12, 'fontStyle': 'italic', 'fontFamily': 'Arial,Helvetica,sans-serif' }; - deepEqual(fabric.parseStyleAttribute(element), expectedObject); + + deepEqual(styleObj, expectedObject); + + //testing different unit + element.setAttribute('style', 'font: italic 1.5em Arial,Helvetica,sans-serif'); + var styleObj = fabric.parseStyleAttribute(element); + if (styleObj.font) { + fabric.parseFontDeclaration(styleObj.font, styleObj); + } + var expectedObject = { + 'font': 'italic 1.5em Arial,Helvetica,sans-serif', + 'fontSize': 24, + 'fontStyle': 'italic', + 'fontFamily': 'Arial,Helvetica,sans-serif' + }; + + deepEqual(styleObj, expectedObject); }); test('parseAttributes (style to have higher priority than attribute)', function() {