| Index: samples/gpu2d/svgloader.js
|
| ===================================================================
|
| --- samples/gpu2d/svgloader.js (revision 0)
|
| +++ samples/gpu2d/svgloader.js (revision 0)
|
| @@ -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_();
|
| + }
|
| +};
|
| +
|
|
|
| Property changes on: samples/gpu2d/svgloader.js
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|