diff options
Diffstat (limited to 'o3d/samples/gpu2d/svgloader.js')
-rw-r--r-- | o3d/samples/gpu2d/svgloader.js | 974 |
1 files changed, 974 insertions, 0 deletions
diff --git a/o3d/samples/gpu2d/svgloader.js b/o3d/samples/gpu2d/svgloader.js new file mode 100644 index 0000000..5a88415 --- /dev/null +++ b/o3d/samples/gpu2d/svgloader.js @@ -0,0 +1,974 @@ +/* + * Copyright 2010, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @fileoverview Loads SVG files and creates o3djs.gpu2d.Paths from + * them, inserting all of the associated geometric nodes under the + * passed Transform. + * <P> + * This file depends on the O3D APIs, o3djs.base, o3djs.io, o3djs.math, + * o3djs.gpu2d, and the XML for <SCRIPT> parser available from + * http://xmljs.sourceforge.net/ . + */ + +/** + * Constructs a new SVGLoader. + * @constructor + */ +function SVGLoader() { +} + +/** + * Helper function to defer the execution of another function. + * @param {function(): void} func function to execute later. + * @private + */ +function runLater_(func) { + setTimeout(func, 0); +} + +/** + * Loads an SVG file at the given URL. The file is downloaded in the + * background. Graphical objects are allocated in the given Pack, and + * created materials are registered under the given DrawList, + * typically the zOrderedDrawList from an + * o3djs.rendergraph.ViewInfo. The created Shapes are parented under + * the given Transform. If the optional completion callback is + * specified, it is called once the file has been downloaded and + * processed. + * @param {string} url URL of the SVG file to load. + * @param {boolean} flipY Whether the Y coordinates should be flipped + * to better match the 3D coordinate system. + * @param {!o3d.Pack} pack Pack to manage created objects. + * @param {!o3d.DrawList} drawList DrawList to use for created + * materials. + * @param {!o3d.Transform} transform Transform under which to place + * created Shapes. + * @param {function(string, *): void} + * opt_completionCallback Optional completion callback which is + * called after the file has been processed. The URL of the file + * is passed as the first argument. The second argument indicates + * whether the file was loaded successfully (true) or not (false). + * The third argument is an error detail if the file failed to + * load successfully. + */ +SVGLoader.prototype.load = function(url, + flipY, + pack, + drawList, + transform, + opt_completionCallback) { + var that = this; + o3djs.io.loadTextFile(url, function(text, exception) { + if (exception) { + runLater_(function() { + if (opt_completionCallback) + opt_completionCallback(url, + false, + e); + }); + } else { + runLater_(function() { + try { + that.parse_(text, + flipY, + pack, + drawList, + transform); + if (opt_completionCallback) + opt_completionCallback(url, true, null); + } catch (e) { + if (window.console) + window.console.log(e); + if (opt_completionCallback) + opt_completionCallback(url, false, e); + } + }); + } + }); +}; + +/** + * Does the parsing of the SVG file. + * @param {string} svgText the text of the SVG file. + * @param {boolean} flipY Whether the Y coordinates should be flipped + * to better match the 3D coordinate system. + * @param {!o3d.Pack} pack Pack to manage created objects. + * @param {!o3d.DrawList} drawList DrawList to use for created + * materials. + * @param {!o3d.Transform} transform Transform under which to place + * created Shapes. + * @private + */ +SVGLoader.prototype.parse_ = function(svgText, + flipY, + pack, + drawList, + transform) { + /** + * The pack in which to create shapes, materials, etc. + * @type {!o3d.Pack} + * @private + */ + this.pack_ = pack; + + /** + * Stack of matrices. Entering a new graphics context pushes a new + * matrix. + * @type {!Array.<!o3djs.math.Matrix4>} + * @private + */ + this.matrixStack_ = []; + + /** + * Stack of transforms. Entering a new graphics context pushes a new + * transform. + * @type {!Array.<!o3d.Transform>} + * @private + */ + this.transformStack_ = []; + + this.matrixStack_.push(o3djs.math.identity(4)); + this.transformStack_.push(transform); + var parser = new SAXDriver(); + var eventHandler = new SVGSAXHandler_(this, + parser, + flipY, + pack, + drawList); + parser.setDocumentHandler(eventHandler); + parser.parse(svgText); +}; + +/** + * Returns the current transform. + * @return {!o3d.Transform} + * @private + */ +SVGLoader.prototype.currentTransform_ = function() { + var len = this.transformStack_.length; + return this.transformStack_[len - 1]; +}; + +/** + * Returns the current matrix. + * @return {!o3djs.math.Matrix4} + * @private + */ +SVGLoader.prototype.currentMatrix_ = function() { + var len = this.matrixStack_.length; + return this.matrixStack_[len - 1]; +}; + +/** + * Sets the current matrix. + * @param {!o3djs.math.Matrix4} matrix the new current matrix. + * @private + */ +SVGLoader.prototype.setCurrentMatrix_ = function(matrix) { + var length = this.matrixStack_.length; + this.matrixStack_[length - 1] = matrix; + this.transformStack_[length - 1].localMatrix = matrix; +}; + +/** + * Pushes a new transform / matrix pair. + * @private + */ +SVGLoader.prototype.pushTransform_ = function() { + this.matrixStack_.push(o3djs.math.identity(4)); + var xform = this.pack_.createObject('o3d.Transform'); + xform.parent = this.currentTransform_(); + this.transformStack_.push(xform); +}; + +/** + * Pops a transform / matrix pair. + * @private + */ +SVGLoader.prototype.popTransform_ = function() { + this.matrixStack_.pop(); + this.transformStack_.pop(); +}; + +/** + * Supports the "matrix" command in the graphics context; not yet + * implemented. + * @param {number} a matrix element + * @param {number} b matrix element + * @param {number} c matrix element + * @param {number} d matrix element + * @param {number} e matrix element + * @param {number} f matrix element + * @private + */ +SVGLoader.prototype.matrix_ = function(a, b, c, d, e, f) { + // TODO(kbr): implement + throw 'matrix command not yet implemented'; +}; + +/** + * Supports the "translate" command in the graphics context. + * @param {number} x x translation + * @param {number} y y translation + * @private + */ +SVGLoader.prototype.translate_ = function(x, y) { + var tmp = o3djs.math.matrix4.translation([x, y, 0]); + this.setCurrentMatrix_( + o3djs.math.mulMatrixMatrix(tmp, this.currentMatrix_())); +}; + +/** + * Supports the "scale" command in the graphics context; not yet + * implemented. + * @param {number} sx x scale + * @param {number} sy y scale + * @private + */ +SVGLoader.prototype.scale_ = function(sx, sy) { + // TODO(kbr): implement + throw 'scale command not yet implemented'; +}; + +/** + * Supports the "rotate" command in the graphics context. + * @param {number} angle angle to rotate, in degrees. + * @param {number} cx x component of rotation center. + * @param {number} cy y component of rotation center. + * @private + */ +SVGLoader.prototype.rotate_ = function(angle, cx, cy) { + var rot = o3djs.math.matrix4.rotationZ(o3djs.math.degToRad(angle)); + if (cx || cy) { + var xlate1 = o3djs.math.matrix4.translation([cx, cy, 0]); + var xlate2 = o3djs.math.matrix4.translation([-cx, -cy, 0]); + rot = o3djs.math.mulMatrixMatrix(xlate2, rot); + rot = o3djs.math.mulMatrixMatrix(rot, xlate1); + } + this.setCurrentMatrix_(rot); +}; + +/** + * Supports the "skewX" command in the graphics context; not yet + * implemented. + * @param {number} angle skew X angle, in degrees. + * @private + */ +SVGLoader.prototype.skewX_ = function(angle) { + // TODO(kbr): implement + throw 'skewX command not yet implemented'; +}; + +/** + * Supports the "skewY" command in the graphics context; not yet + * implemented. + * @param {number} angle skew Y angle, in degrees. + * @private + */ +SVGLoader.prototype.skewY_ = function(angle) { + // TODO(kbr): implement + throw 'skewY command not yet implemented'; +}; + +/** + * Parses the data from an SVG path element, constructing an + * o3djs.gpu2d.Path from it. + * @param {string} pathData the path's data (the "d" attribute). + * @param {number} lineNumber the line number of the current parse, + * for error reporting. + * @param {boolean} flipY Whether the Y coordinates should be flipped + * to better match the 3D coordinate system. + * @param {!o3d.Pack} pack Pack to manage created objects. + * @param {!o3d.DrawList} drawList DrawList to use for created + * materials. + * @private + */ +SVGLoader.prototype.parsePath_ = function(pathData, + lineNumber, + flipY, + pack, + drawList) { + var parser = new PathDataParser_(this, + lineNumber, + flipY, + pack, + drawList); + var path = parser.parse(pathData); + if (this.fill_) { + path.setFill(this.fill_); + } + this.currentTransform_().addShape(path.shape); +}; + +/** + * Parses the style from an SVG path element or graphics context, + * preparing to set it on the next created o3djs.gpu2d.Path. If it + * doesn't know how to handle it, the default color of solid black + * will be used. + * @param {string} styleData the string containing the "style" + * attribute. + * @param {number} lineNumber the line number of the current parse, + * for error reporting. + * @param {!o3d.Pack} pack Pack to manage created objects. + * @private + */ +SVGLoader.prototype.parseStyle_ = function(styleData, + lineNumber, + pack) { + this.fill_ = null; + var portions = styleData.split(';'); + for (var i = 0; i < portions.length; i++) { + var keyVal = portions[i].split(':'); + var key = keyVal[0]; + var val = keyVal[1]; + if (key == 'stroke') { + // TODO(kbr): support strokes + } else if (key == 'stroke-width') { + // TODO(kbr): support stroke width + } else if (key == 'fill') { + if (val.charAt(0) == '#') { + var r = parseInt(val.substr(1, 2), 16); + var g = parseInt(val.substr(3, 2), 16); + var b = parseInt(val.substr(5, 2), 16); + var fill = o3djs.gpu2d.createColor(pack, + r / 255.0, + g / 255.0, + b / 255.0, + 1.0); + this.fill_ = fill; + } else if (val.substr(0, 4) == 'rgb(' && + val.charAt(val.length - 1) == ')') { + var rgbStrings = val.substr(4, val.length - 5).split(','); + var r = parseInt(rgbStrings[0]); + var g = parseInt(rgbStrings[1]); + var b = parseInt(rgbStrings[2]); + var fill = o3djs.gpu2d.createColor(pack, + r / 255.0, + g / 255.0, + b / 255.0, + 1.0); + this.fill_ = fill; + } + } + } +}; + +/** + * Parses the data from an SVG transform attribute, storing the result + * in the current o3d.Transform. + * @param {string} data the transform's data. + * @param {number} lineNumber the line number of the current parse, + * for error reporting. + * @private + */ +SVGLoader.prototype.parseTransform_ = function(data, + lineNumber) { + var parser = new TransformDataParser_(this, + lineNumber); + parser.parse(data); +}; + +//---------------------------------------------------------------------- +// BaseDataParser -- base class for parsers dealing with SVG data + +/** + * Base class for parsers dealing with SVG data. + * @param {SVGLoader} loader The SVG loader. + * @param {number} lineNumber the line number of the current parse, + * for error reporting. + * @constructor + * @private + */ +BaseDataParser_ = function(loader, + lineNumber) { + this.loader_ = loader; + this.lineNumber_ = lineNumber; + this.putBackToken_ = null; +} + +/** + * Types of tokens. + * @enum + * @private + */ +BaseDataParser_.TokenTypes = { + WORD: 1, + NUMBER: 2, + LPAREN: 3, + RPAREN: 4, + NONE: 5 +}; + +/** + * Parses the given SVG data. + * @param {string} data the SVG data. + * @private + */ +BaseDataParser_.prototype.parse = function(data) { + var parseState = { + // First index of current token + firstIndex: 0, + // Last index of current token (exclusive) + lastIndex: 0, + // Line number of the path element + lineNumber: this.lineNumber_ + }; + var done = false; + while (!done) { + var tok = this.nextToken_(parseState, data); + switch (tok.kind) { + case BaseDataParser_.TokenTypes.WORD: + // Allow the parsing to override the specification of the last + // command + this.setLastCommand_(tok.val); + this.parseWord_(parseState, data, tok.val); + break; + case BaseDataParser_.TokenTypes.NUMBER: + // Assume this is a repeat of the last command + this.putBack_(tok); + this.parseWord_(parseState, data, this.lastCommand_); + break; + default: + done = true; + break; + } + } +}; + +/** + * Sets the last parsed command. + * @param {string} command the last parsed command. + * @private + */ +BaseDataParser_.prototype.setLastCommand_ = function(command) { + this.lastCommand_ = command; +}; + +/** + * Returns true if the given character is a whitespace or separator. + * @param {string} c the character to test. + * @private + */ +BaseDataParser_.prototype.isWhitespaceOrSeparator_ = function(c) { + return (c == ',' || + c == ' ' || + c == '\t' || + c == '\r' || + c == '\n'); +}; + +/** + * Puts back a token to be consumed during the next iteration. There + * is only a one-token put back buffer. + * @param {!{kind: BaseDataParser_TokenTypes, val: string}} tok The + * token to put back. + * @private + */ +BaseDataParser_.prototype.putBack_ = function(tok) { + this.putBackToken_ = tok; +}; + +/** + * Returns the next token. + * @param {!{firstIndex: number, lastIndex: number, lineNumber: + * number}} parseState The parse state. + * @param {string} data The data being parsed. + * @private + */ +BaseDataParser_.prototype.nextToken_ = function(parseState, data) { + if (this.putBackToken_) { + var tmp = this.putBackToken_; + this.putBackToken_ = null; + return tmp; + } + parseState.firstIndex = parseState.lastIndex; + if (parseState.firstIndex < data.length) { + // Eat whitespace and separators + while (true) { + var curChar = data.charAt(parseState.firstIndex); + if (this.isWhitespaceOrSeparator_(curChar)) { + ++parseState.firstIndex; + if (parseState.firstIndex >= data.length) + break; + } else { + break; + } + } + } + if (parseState.firstIndex >= data.length) + return { kind: BaseDataParser_.TokenTypes.NONE, val: null }; + parseState.lastIndex = parseState.firstIndex; + // Surround the next token + var curChar = data.charAt(parseState.lastIndex++); + if (curChar == '-' || + curChar == '.' || + (curChar >= '0' && curChar <= '9')) { + while (true) { + var t = data.charAt(parseState.lastIndex); + if (t == '.' || + (t >= '0' && t <= '9')) { + ++parseState.lastIndex; + } else { + break; + } + } + // See whether an exponential format follows: i.e. 136e-3 + if (data.charAt(parseState.lastIndex) == 'e') { + ++parseState.lastIndex; + if (data.charAt(parseState.lastIndex) == '-') { + ++parseState.lastIndex; + } + while (true) { + var t = data.charAt(parseState.lastIndex); + if (t >= '0' && t <= '9') { + ++parseState.lastIndex; + } else { + break; + } + } + } + return { kind: BaseDataParser_.TokenTypes.NUMBER, + val: parseFloat(data.substring(parseState.firstIndex, + parseState.lastIndex)) }; + } else if ((curChar >= 'A' && curChar <= 'Z') || + (curChar >= 'a' && curChar <= 'z')) { + // Consume all adjacent letters -- this is satisfactory both for + // the grammars of the "transform" and "d" attributes + while (true) { + var t = data.charAt(parseState.lastIndex); + if ((t >= 'A' && t <= 'Z') || + (t >= 'a' && t <= 'z')) { + ++parseState.lastIndex; + } else { + break; + } + } + return { kind: BaseDataParser_.TokenTypes.WORD, + val: data.substring(parseState.firstIndex, + parseState.lastIndex) }; + } else if (curChar == '(') { + return { kind: BaseDataParser_.TokenTypes.LPAREN, + val: data.substring(parseState.firstIndex, + parseState.lastIndex) }; + } else if (curChar == ')') { + return { kind: BaseDataParser_.TokenTypes.RPAREN, + val: data.substring(parseState.firstIndex, + parseState.lastIndex) }; + } + throw 'Expected number or word at line ' + parseState.lineNumber; +}; + +/** + * Verifies that the next token is of the given kind, throwing an + * exception if not. + * @param {!{firstIndex: number, lastIndex: number, lineNumber: + * number}} parseState The parse state. + * @param {string} data The data being parsed. + * @param {BaseDataParser_TokenTypes} tokenType The expected token + * type. + */ +BaseDataParser_.prototype.expect_ = function(parseState, data, tokenType) { + var tok = this.nextToken_(parseState, data); + if (tok.kind != tokenType) { + throw 'At line number ' + parseState.lineNumber + + ': expected token type ' + tokenType + + ', got ' + tok.kind; + } +}; + +/** + * Parses a series of floating-point numbers. + * @param {!{firstIndex: number, lastIndex: number, lineNumber: + * number}} parseState The parse state. + * @param {string} data The data being parsed. + * @param {number} numFloats The number of floating-point numbers to + * parse. + * @return {!Array.<number>} + * @private + */ +BaseDataParser_.prototype.parseFloats_ = function(parseState, + data, + numFloats) { + var result = []; + for (var i = 0; i < numFloats; ++i) { + var tok = this.nextToken_(parseState, data); + if (tok.kind != BaseDataParser_.TokenTypes.NUMBER) + throw "Expected number at line " + + parseState.lineNumber + + ", character " + + parseState.firstIndex + + "; got \"" + tok.val + "\""; + result.push(tok.val); + } + return result; +}; + +//---------------------------------------------------------------------- +// PathDataParser + +/** + * Parser for the data in an SVG path. + * @param {SVGLoader} loader The SVG loader. + * @param {number} lineNumber the line number of the current parse, + * for error reporting. + * @param {boolean} flipY Whether the Y coordinates should be flipped + * to better match the 3D coordinate system. + * @param {!o3d.Pack} pack Pack to manage created objects. + * @param {!o3d.DrawList} drawList DrawList to use for created + * materials. + * @constructor + * @extends {BaseDataParser_} + * @private + */ +PathDataParser_ = function(loader, + lineNumber, + flipY, + pack, + drawList) { + BaseDataParser_.call(this, loader, lineNumber); + this.flipY_ = flipY; + this.pack_ = pack; + this.drawList_ = drawList; + this.curX_ = 0; + this.curY_ = 0; +}; + +o3djs.base.inherit(PathDataParser_, BaseDataParser_); + +/** + * Parses data from an SVG path. + * @param {string} data SVG path data. + * @private + */ +PathDataParser_.prototype.parse = function(data) { + this.path_ = o3djs.gpu2d.createPath(this.pack_, this.drawList_); + BaseDataParser_.prototype.parse.call(this, data); + this.path_.update(); + return this.path_; +}; + +/** + * Parses the data for the given word (command) from the path data. + * @param {!{firstIndex: number, lastIndex: number, lineNumber: + * number}} parseState The parse state. + * @param {string} data The data being parsed. + * @param {string} word The character for the current command. + * @private + */ +PathDataParser_.prototype.parseWord_ = function(parseState, + data, + word) { + if (word == 'M' || word == 'm') { + var absolute = (word == 'M'); + var coords = this.parseFloats_(parseState, data, 2); + this.doYFlip_(coords); + if (!absolute) { + this.makeAbsolute_(coords); + } + this.path_.moveTo(coords[0], coords[1]); + this.curX_ = coords[0]; + this.curY_ = coords[1]; + // This is a horrible portion of the SVG spec + if (absolute) { + this.setLastCommand_('L'); + } else { + this.setLastCommand_('l'); + } + } else if (word == 'L' || word == 'l') { + var absolute = (word == 'L'); + var coords = this.parseFloats_(parseState, data, 2); + this.doYFlip_(coords); + if (!absolute) { + this.makeAbsolute_(coords); + } + this.path_.lineTo(coords[0], coords[1]); + this.curX_ = coords[0]; + this.curY_ = coords[1]; + } else if (word == 'H' || word == 'h') { + var absolute = (word == 'H'); + var coords = this.parseFloats_(parseState, data, 1); + if (!absolute) { + coords[0] += this.curX_; + } + this.path_.lineTo(coords[0], this.curY_); + this.curX_ = coords[0]; + } else if (word == 'V' || word == 'v') { + var absolute = (word == 'V'); + var coords = this.parseFloats_(parseState, data, 1); + if (this.flipY_) { + coords[0] = -coords[0]; + } + if (!absolute) { + coords[0] += this.curY_; + } + this.path_.lineTo(this.curX_, coords[0]); + this.curY_ = coords[0]; + } else if (word == 'Q' || word == 'q') { + var absolute = (word == 'Q'); + var coords = this.parseFloats_(parseState, data, 4); + this.doYFlip_(coords); + if (!absolute) { + this.makeAbsolute_(coords); + } + this.path_.quadraticTo(coords[0], coords[1], coords[2], coords[3]); + this.curX_ = coords[2]; + this.curY_ = coords[3]; + // TODO(kbr): support shorthand quadraticTo (T/t) + } else if (word == 'C' || word == 'c') { + var absolute = (word == 'C'); + var coords = this.parseFloats_(parseState, data, 6); + this.doYFlip_(coords); + if (!absolute) { + this.makeAbsolute_(coords); + } + this.path_.cubicTo(coords[0], coords[1], + coords[2], coords[3], + coords[4], coords[5]); + this.curX_ = coords[4]; + this.curY_ = coords[5]; + // TODO(kbr): support shorthand cubicTo (S/s) + } else if (word == 'Z' || word == 'z') { + this.path_.close(); + } else { + throw 'Unknown word ' + word + ' at line ' + parseState.lineNumber; + } +}; + +/** + * Negates the Y coordinates of the passed 2D coordinate list if the + * flipY flag is set on this parser. + * @param {!Array.<number>} coords Array of 2D coordinates. + * @private + */ + +PathDataParser_.prototype.doYFlip_ = function(coords) { + if (this.flipY_) { + for (var i = 0; i < coords.length; i += 2) { + coords[i+1] = -coords[i+1]; + } + } +}; + +/** + * Transforms relative coordinates to absolute coordinates. + * @param {!Array.<number>} coords Array of 2D coordinates. + * @private + */ +PathDataParser_.prototype.makeAbsolute_ = function(coords) { + for (var i = 0; i < coords.length; i += 2) { + coords[i] += this.curX_; + coords[i+1] += this.curY_; + } +}; + +//---------------------------------------------------------------------- +// TransformDataParser + +/** + * Parser for the data in an SVG transform. + * @param {SVGLoader} loader The SVG loader. + * @param {number} lineNumber the line number of the current parse, + * for error reporting. + * @constructor + * @extends {BaseDataParser_} + * @private + */ +TransformDataParser_ = function(loader, + lineNumber) { + BaseDataParser_.call(this, loader, lineNumber); +}; + +o3djs.base.inherit(TransformDataParser_, BaseDataParser_); + +/** + * Parses the data for the given word (command) from the path data. + * @param {!{firstIndex: number, lastIndex: number, lineNumber: + * number}} parseState The parse state. + * @param {string} data The data being parsed. + * @param {string} word The character for the current command. + * @private + */ +TransformDataParser_.prototype.parseWord_ = function(parseState, + data, + word) { + if (!((word == 'matrix') || + (word == 'translate') || + (word == 'scale') || + (word == 'rotate') || + (word == 'skewX') || + (word == 'skewY'))) { + throw 'Unknown transform definition ' + word + + ' at line ' + parseState.lineNumber; + } + + this.expect_(parseState, data, BaseDataParser_.TokenTypes.LPAREN); + // Some of the commands take a variable number of arguments + var floats; + switch (word) { + case 'matrix': + floats = this.parseFloats_(parseState, data, 6); + this.loader_.matrix_(floats[0], floats[1], + floats[2], floats[3], + floats[4], floats[5]); + break; + case 'translate': + floats = this.parseFloats_(parseState, data, 1); + var tok = this.nextToken_(parseState, data); + if (tok.kind == BaseDataParser_.TokenTypes.NUMBER) { + floats.push(tok.val); + } else { + this.putBack_(tok); + floats.push(0); + } + this.loader_.translate_(floats[0], floats[1]); + break; + case 'scale': + floats = this.parseFloats_(parseState, data, 1); + var tok = this.nextToken_(parseState, data); + if (tok.kind == BaseDataParser_.TokenTypes.NUMBER) { + floats.push(tok.val); + } else { + this.putBack_(tok); + floats.push(floats[0]); + } + this.loader_.scale_(floats[0], floats[1]); + break; + case 'rotate': + floats = this.parseFloats_(parseState, data, 1); + var tok = this.nextToken_(parseState, data); + this.putBack_(tok); + if (tok.kind == BaseDataParser_.TokenTypes.NUMBER) { + floats = floats.concat(this.parseFloats_(parseState, data, 2)); + } else { + floats.push(0); + floats.push(0); + } + this.loader_.rotate_(floats[0], floats[1], floats[2]); + break; + case 'skewX': + floats = this.parseFloats_(parseState, data, 1); + this.loader_.skewX_(floats[0]); + break; + case 'skewY': + floats = this.parseFloats_(parseState, data, 1); + this.loader_.skewY_(floats[0]); + break; + default: + throw 'Unknown word ' + word + ' at line ' + parseState.lineNumber; + } + this.expect_(parseState, data, BaseDataParser_.TokenTypes.RPAREN); +}; + +//---------------------------------------------------------------------- +// SVGSaxHandler + +/** + * Handler which integrates with the XMLJS SAX parser. + * @param {!SVGLoader} loader The SVG loader. + * @param {Object} parser the XMLJS SAXDriver. + * @param {boolean} flipY Whether the Y coordinates should be flipped + * to better match the 3D coordinate system. + * @param {!o3d.Pack} pack Pack to manage created objects. + * @param {!o3d.DrawList} drawList DrawList to use for created + * materials. + * @constructor + * @private + */ +SVGSAXHandler_ = function(loader, parser, flipY, pack, drawList) { + this.loader_ = loader; + this.parser_ = parser; + this.flipY_ = flipY; + this.pack_ = pack; + this.drawList_ = drawList; +}; + +/** + * Handler for the start of an element. + * @param {string} name the name of the element. + * @param {Object} attributes the XMLJS attributes object. + * @private + */ +SVGSAXHandler_.prototype.startElement = function(name, attributes) { + switch (name) { + case 'path': + var pushed = false; + var transformData = attributes.getValueByName('transform'); + if (transformData) { + pushed = true; + this.loader_.pushTransform_(); + this.loader_.parseTransform_(transformData, + this.parser_.getLineNumber()); + } + if (attributes.getValueByName('style')) { + this.loader_.parseStyle_(attributes.getValueByName('style'), + this.parser_.getLineNumber(), + this.pack_); + } + this.loader_.parsePath_(attributes.getValueByName('d'), + this.parser_.getLineNumber(), + this.flipY_, + this.pack_, + this.drawList_); + if (pushed) { + this.loader_.popTransform_(); + } + break; + case 'g': + this.loader_.pushTransform_(); + if (attributes.getValueByName('style')) { + this.loader_.parseStyle_(attributes.getValueByName('style'), + this.parser_.getLineNumber(), + this.pack_); + } + var data = attributes.getValueByName('transform'); + if (data) { + this.loader_.parseTransform_(data, + this.parser_.getLineNumber()); + } + break; + default: + // No other commands supported yet + break; + } +}; + +/** + * Handler for the end of an element. + * @param {string} name the name of the element. + * @private + */ +SVGSAXHandler_.prototype.endElement = function(name) { + if (name == 'g') { + this.loader_.popTransform_(); + } +}; + |