var SVGParser = require('./core'); var Font = require('../../shapes/font'); function fontStyles(styles){ var font = {}, isFontStyle = /^(font|text-d|kerning|letter|word)[^\_]*$/; for (var style in styles) if (isFontStyle.test(style)) font[style] = styles[style] + (style == 'font-size' ? 'px' : ''); // Iffy font['font-weight'] = (font['font-weight'] > 500 || font['font-weight'] == 'bold' || font['font-weight'] == 'bolder') ? 'bold' : 'normal'; font['font-style'] = font['font-style'] == 'oblique' || font['font-style'] == 'italic' ? 'italic' : 'normal'; return font; }; function progressRow(row, element, styles){ var nx = element.getAttribute('x'), ny = element.getAttribute('y'), dx = element.getAttribute('dx'), dy = element.getAttribute('dy'); var newRow = (nx != null && nx != '') || (ny != null && ny != ''); if (newRow){ adjustRow(row); row = []; row.anchor = styles['text-anchor']; row.x = row.y = 0; } if (dx) row.x += this.parseLength(dx.split(/[\s\,]+/)[0], styles, 'x'); if (dy) row.y += this.parseLength(dy.split(/[\s\,]+/)[0], styles, 'y'); if (nx != null && nx != '') row.x = this.parseLength(nx.split(/[\s\,]+/)[0], styles, 'x'); if (ny != null && ny != '') row.y = this.parseLength(ny.split(/[\s\,]+/)[0], styles, 'y'); if (newRow) row.ix = row.x; return row; }; function adjustRow(row){ var adjustment = row.anchor == 'end' ? (row.ix - row.x) : row.anchor == 'middle' ? (row.ix - row.x) / 2 : 0; if (adjustment) for (var i = 0, l = row.length; i < l; i++) row[i].transform(1,0,0,1,adjustment,0); } SVGParser.implement({ textElement: function(element, styles){ var group = new this.MODE.Group(); this.transform(element, group); this.filter(styles, group); var row = []; row.ix = row.x = row.y = 0; row.anchor = styles['text-anchor']; this.tspanText(element, styles, row, group, adjustRow); return group; }, tspanText: function(element, styles, row, target, continuation){ row = progressRow.call(this, row, element, styles); var node = element.firstChild; var self = this; var next = function(row){ if (node.nodeType == 3){ var text = self.textContent(node, styles); node = node.nextSibling; self.createText(text, styles, row, target, null, function(shape){ if (shape){ if (shape.width) row.x += shape.width + (shape.left || 0); // TODO: Adjust for rotated text row.push(shape); } if (node) next(row); else continuation(row); }); } else { var parseFn = self[node.nodeName + 'Text']; var current = node; node = node.nextSibling; if (parseFn) return parseFn.call(self, current, self.parseStyles(current, styles), row, target, node ? next : continuation); if (node) next(row); else continuation(row); } }; if (node) next(row); else continuation(row); }, aText: function(){ return this.tspanText.apply(this, arguments); }, textPathText: function(element, styles, row, target, continuation){ this.findByURL(element.ownerDocument, element.getAttribute('xlink:href') || element.getAttribute('href'), function(path){ if (!path || !(path = path.getAttribute('d'))) return; var text = this.elementTextContent(element, styles); this.createText(text, styles, null, target, path, function(){ continuation(row); }); }); }, trefText: function(element, styles, row, target, continuation){ row = progressRow.call(this, row, element, styles); this.findByURL(element.ownerDocument, element.getAttribute('xlink:href') || element.getAttribute('href'), function(ref){ if (!ref) return continuation(row); var text = this.elementTextContent(ref, styles); this.createText(text, styles, row, target, null, function(shape){ if (shape){ if (shape.width) row.x += shape.width + (shape.left || 0); // TODO: Adjust for rotated text row.push(shape); } continuation(row); }); }); }, createText: function(text, styles, row, target, path, continuation){ var fontstyle = fontStyles(styles); var create = function(font){ if (font){ var face = font.face; fontstyle = { fontFamily: font.face['font-family'], fontSize: fontstyle['font-size'] }; if (face['font-weight'] > 500) fontstyle.fontWeight = face['font-weight']; if (face['font-stretch'] == 'oblique' || face['font-style'] == 'oblique' || face['font-style'] == 'italic') fontstyle.fontStyle = 'italic'; }; var Text = font ? (this.MODE.Font || Font) : this.MODE.Text; if (row){ var pad = row.pad || ''; row.pad = (/[\s\xA0]*$/).exec(text)[0]; if (row.length == 0) text = text.replace(/^\s+/, ''); text = pad + text.replace(/\s+$/, ''); } else { text = text.replace(/^\s+|\s+$/g, ''); } if (text == ''){ if (row && row.length == 0) row.pad = ''; continuation(); return; } var x = path ? 0 : row.x, y = path ? 0 : row.y - this.getBaseline(styles); var shape = new Text(text, fontstyle, 'start', path); shape.transform(1, 0, 0, 1, x, y); this.fill(styles, shape, x, y); this.stroke(styles, shape); this.filter(styles, shape); shape.inject(target); continuation(shape); } if (this.findFont) this.findFont(fontstyle, create); else create.call(this); }, elementTextContent: function(element, styles){ var node = element.firstChild, text = ''; treewalker: while (node){ if (node.nodeType == 3){ text += this.textContent(node, styles); } if (node.firstChild){ node = node.firstChild; } else { while (!node.nextSibling){ node = node.parentNode; if (!node || node == element) break treewalker; } node = node.nextSibling; } } return text; }, textContent: function(node, styles){ var value = node.nodeValue; if (styles['xml:space'] == 'preserve'){ value = value.replace(/\t|\r?\n/g, ' '); } else { value = value.replace(/\r?\n/g, '').replace(/\s+/g, ' '); } return value; //.replace(/^\s+|\s+$/g, ''); }, getBaseline: function(styles){ var metrics = this.getTextMetrics(styles), shift = styles['baseline-shift']; if (shift == 'baseline') shift = '0'; else if (shift== 'sub') shift = '-0.5em'; else if (shift == 'super') shift = '0.5em'; shift = this.parseLength(shift, styles, 'font'); return metrics.em + metrics.descent + shift; }, getTextMetrics: function(styles){ var fontFamily = styles['font-family'], weight = styles['font-weight'], style = styles['font-style'], size = styles['font-size'], font = this.fonts && this.fonts[fontFamily.replace(/^['"\s]+|['"\s]+$/, '')]; if (font) return { em: size, descent: -(1- font.face.ascent / font.face['units-per-em']) * size }; // TODO: Use cross-platform hack to get native font descent return { em: size, ascent: 0.85 * size, descent: -0.15 * size }; // Good guess for web safe fonts } });