svggraphics.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. // Copyright 2007 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview SvgGraphics sub class that uses SVG to draw the graphics.
  16. * @author arv@google.com (Erik Arvidsson)
  17. */
  18. goog.provide('goog.graphics.SvgGraphics');
  19. goog.require('goog.Timer');
  20. goog.require('goog.dom');
  21. goog.require('goog.events.EventHandler');
  22. goog.require('goog.events.EventType');
  23. goog.require('goog.graphics.AbstractGraphics');
  24. goog.require('goog.graphics.Font');
  25. goog.require('goog.graphics.LinearGradient');
  26. goog.require('goog.graphics.Path');
  27. goog.require('goog.graphics.SolidFill');
  28. goog.require('goog.graphics.Stroke');
  29. goog.require('goog.graphics.SvgEllipseElement');
  30. goog.require('goog.graphics.SvgGroupElement');
  31. goog.require('goog.graphics.SvgImageElement');
  32. goog.require('goog.graphics.SvgPathElement');
  33. goog.require('goog.graphics.SvgRectElement');
  34. goog.require('goog.graphics.SvgTextElement');
  35. goog.require('goog.math');
  36. goog.require('goog.math.Size');
  37. goog.require('goog.style');
  38. goog.require('goog.userAgent');
  39. /**
  40. * A Graphics implementation for drawing using SVG.
  41. * @param {string|number} width The width in pixels. Strings
  42. * expressing percentages of parent with (e.g. '80%') are also accepted.
  43. * @param {string|number} height The height in pixels. Strings
  44. * expressing percentages of parent with (e.g. '80%') are also accepted.
  45. * @param {?number=} opt_coordWidth The coordinate width - if
  46. * omitted or null, defaults to same as width.
  47. * @param {?number=} opt_coordHeight The coordinate height - if
  48. * omitted or null, defaults to same as height.
  49. * @param {goog.dom.DomHelper=} opt_domHelper The DOM helper object for the
  50. * document we want to render in.
  51. * @constructor
  52. * @extends {goog.graphics.AbstractGraphics}
  53. * @deprecated goog.graphics is deprecated. It existed to abstract over browser
  54. * differences before the canvas tag was widely supported. See
  55. * http://en.wikipedia.org/wiki/Canvas_element for details.
  56. * @final
  57. */
  58. goog.graphics.SvgGraphics = function(
  59. width, height, opt_coordWidth, opt_coordHeight, opt_domHelper) {
  60. goog.graphics.AbstractGraphics.call(
  61. this, width, height, opt_coordWidth, opt_coordHeight, opt_domHelper);
  62. /**
  63. * Map from def key to id of def root element.
  64. * Defs are global "defines" of svg that are used to share common attributes,
  65. * for example gradients.
  66. * @type {Object}
  67. * @private
  68. */
  69. this.defs_ = {};
  70. /**
  71. * Whether to manually implement viewBox by using a coordinate transform.
  72. * As of 1/11/08 this is necessary for Safari 3 but not for the nightly
  73. * WebKit build. Apply to webkit versions < 526. 525 is the
  74. * last version used by Safari 3.1.
  75. * @type {boolean}
  76. * @private
  77. */
  78. this.useManualViewbox_ =
  79. goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher(526);
  80. /**
  81. * Event handler.
  82. * @type {goog.events.EventHandler<!goog.graphics.SvgGraphics>}
  83. * @private
  84. */
  85. this.handler_ = new goog.events.EventHandler(this);
  86. };
  87. goog.inherits(goog.graphics.SvgGraphics, goog.graphics.AbstractGraphics);
  88. /**
  89. * The SVG namespace URN
  90. * @private
  91. * @type {string}
  92. */
  93. goog.graphics.SvgGraphics.SVG_NS_ = 'http://www.w3.org/2000/svg';
  94. /**
  95. * The name prefix for def entries
  96. * @private
  97. * @type {string}
  98. */
  99. goog.graphics.SvgGraphics.DEF_ID_PREFIX_ = '_svgdef_';
  100. /**
  101. * The next available unique identifier for a def entry.
  102. * This is a static variable, so that when multiple graphics are used in one
  103. * document, the same def id can not be re-defined by another SvgGraphics.
  104. * @type {number}
  105. * @private
  106. */
  107. goog.graphics.SvgGraphics.nextDefId_ = 0;
  108. /**
  109. * Svg element for definitions for other elements, e.g. linear gradients.
  110. * @type {Element}
  111. * @private
  112. */
  113. goog.graphics.SvgGraphics.prototype.defsElement_;
  114. /**
  115. * Creates an SVG element. Used internally and by different SVG classes.
  116. * @param {string} tagName The type of element to create.
  117. * @param {Object=} opt_attributes Map of name-value pairs for attributes.
  118. * @return {!Element} The created element.
  119. * @private
  120. */
  121. goog.graphics.SvgGraphics.prototype.createSvgElement_ = function(
  122. tagName, opt_attributes) {
  123. var element = this.dom_.getDocument().createElementNS(
  124. goog.graphics.SvgGraphics.SVG_NS_, tagName);
  125. if (opt_attributes) {
  126. this.setElementAttributes(element, opt_attributes);
  127. }
  128. return element;
  129. };
  130. /**
  131. * Sets properties to an SVG element. Used internally and by different
  132. * SVG elements.
  133. * @param {Element} element The svg element.
  134. * @param {Object} attributes Map of name-value pairs for attributes.
  135. */
  136. goog.graphics.SvgGraphics.prototype.setElementAttributes = function(
  137. element, attributes) {
  138. for (var key in attributes) {
  139. element.setAttribute(key, attributes[key]);
  140. }
  141. };
  142. /**
  143. * Appends an element.
  144. *
  145. * @param {goog.graphics.Element} element The element wrapper.
  146. * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
  147. * to append to. If not specified, appends to the main canvas.
  148. * @private
  149. */
  150. goog.graphics.SvgGraphics.prototype.append_ = function(element, opt_group) {
  151. var parent = opt_group || this.canvasElement;
  152. parent.getElement().appendChild(element.getElement());
  153. };
  154. /**
  155. * Sets the fill of the given element.
  156. * @param {goog.graphics.StrokeAndFillElement} element The element wrapper.
  157. * @param {goog.graphics.Fill?} fill The fill object.
  158. * @override
  159. */
  160. goog.graphics.SvgGraphics.prototype.setElementFill = function(element, fill) {
  161. var svgElement = element.getElement();
  162. if (fill instanceof goog.graphics.SolidFill) {
  163. svgElement.setAttribute('fill', fill.getColor());
  164. svgElement.setAttribute('fill-opacity', fill.getOpacity());
  165. } else if (fill instanceof goog.graphics.LinearGradient) {
  166. // create a def key which is just a concat of all the relevant fields
  167. var defKey = 'lg-' + fill.getX1() + '-' + fill.getY1() + '-' +
  168. fill.getX2() + '-' + fill.getY2() + '-' + fill.getColor1() + '-' +
  169. fill.getColor2();
  170. // It seems that the SVG version accepts opacity where the VML does not
  171. var id = this.getDef(defKey);
  172. if (!id) { // No def for this yet, create it
  173. // Create the gradient def entry (only linear gradient are supported)
  174. var gradient = this.createSvgElement_('linearGradient', {
  175. 'x1': fill.getX1(),
  176. 'y1': fill.getY1(),
  177. 'x2': fill.getX2(),
  178. 'y2': fill.getY2(),
  179. 'gradientUnits': 'userSpaceOnUse'
  180. });
  181. var gstyle = 'stop-color:' + fill.getColor1();
  182. if (goog.isNumber(fill.getOpacity1())) {
  183. gstyle += ';stop-opacity:' + fill.getOpacity1();
  184. }
  185. var stop1 =
  186. this.createSvgElement_('stop', {'offset': '0%', 'style': gstyle});
  187. gradient.appendChild(stop1);
  188. // LinearGradients don't have opacity in VML so implement that before
  189. // enabling the following code.
  190. // if (fill.getOpacity() != null) {
  191. // gstyles += 'opacity:' + fill.getOpacity() + ';'
  192. // }
  193. gstyle = 'stop-color:' + fill.getColor2();
  194. if (goog.isNumber(fill.getOpacity2())) {
  195. gstyle += ';stop-opacity:' + fill.getOpacity2();
  196. }
  197. var stop2 =
  198. this.createSvgElement_('stop', {'offset': '100%', 'style': gstyle});
  199. gradient.appendChild(stop2);
  200. // LinearGradients don't have opacity in VML so implement that before
  201. // enabling the following code.
  202. // if (fill.getOpacity() != null) {
  203. // gstyles += 'opacity:' + fill.getOpacity() + ';'
  204. // }
  205. id = this.addDef(defKey, gradient);
  206. }
  207. // Link element to linearGradient definition
  208. svgElement.setAttribute('fill', 'url(#' + id + ')');
  209. } else {
  210. svgElement.setAttribute('fill', 'none');
  211. }
  212. };
  213. /**
  214. * Sets the stroke of the given element.
  215. * @param {goog.graphics.StrokeAndFillElement} element The element wrapper.
  216. * @param {goog.graphics.Stroke?} stroke The stroke object.
  217. * @override
  218. */
  219. goog.graphics.SvgGraphics.prototype.setElementStroke = function(
  220. element, stroke) {
  221. var svgElement = element.getElement();
  222. if (stroke) {
  223. svgElement.setAttribute('stroke', stroke.getColor());
  224. svgElement.setAttribute('stroke-opacity', stroke.getOpacity());
  225. var width = stroke.getWidth();
  226. if (goog.isString(width) && width.indexOf('px') != -1) {
  227. svgElement.setAttribute(
  228. 'stroke-width', parseFloat(width) / this.getPixelScaleX());
  229. } else {
  230. svgElement.setAttribute('stroke-width', width);
  231. }
  232. } else {
  233. svgElement.setAttribute('stroke', 'none');
  234. }
  235. };
  236. /**
  237. * Set the translation and rotation of an element.
  238. *
  239. * If a more general affine transform is needed than this provides
  240. * (e.g. skew and scale) then use setElementAffineTransform.
  241. * @param {goog.graphics.Element} element The element wrapper.
  242. * @param {number} x The x coordinate of the translation transform.
  243. * @param {number} y The y coordinate of the translation transform.
  244. * @param {number} angle The angle of the rotation transform.
  245. * @param {number} centerX The horizontal center of the rotation transform.
  246. * @param {number} centerY The vertical center of the rotation transform.
  247. * @override
  248. */
  249. goog.graphics.SvgGraphics.prototype.setElementTransform = function(
  250. element, x, y, angle, centerX, centerY) {
  251. element.getElement().setAttribute(
  252. 'transform', 'translate(' + x + ',' + y + ') rotate(' + angle + ' ' +
  253. centerX + ' ' + centerY + ')');
  254. };
  255. /**
  256. * Set the transformation of an element.
  257. * @param {goog.graphics.Element} element The element wrapper.
  258. * @param {!goog.graphics.AffineTransform} affineTransform The
  259. * transformation applied to this element.
  260. * @override
  261. */
  262. goog.graphics.SvgGraphics.prototype.setElementAffineTransform = function(
  263. element, affineTransform) {
  264. var t = affineTransform;
  265. var substr = [
  266. t.getScaleX(), t.getShearY(), t.getShearX(), t.getScaleY(),
  267. t.getTranslateX(), t.getTranslateY()
  268. ].join(',');
  269. element.getElement().setAttribute('transform', 'matrix(' + substr + ')');
  270. };
  271. /**
  272. * Creates the DOM representation of the graphics area.
  273. * @override
  274. */
  275. goog.graphics.SvgGraphics.prototype.createDom = function() {
  276. // Set up the standard attributes.
  277. var attributes =
  278. {'width': this.width, 'height': this.height, 'overflow': 'hidden'};
  279. var svgElement = this.createSvgElement_('svg', attributes);
  280. var groupElement = this.createSvgElement_('g');
  281. this.defsElement_ = this.createSvgElement_('defs');
  282. this.canvasElement = new goog.graphics.SvgGroupElement(groupElement, this);
  283. svgElement.appendChild(this.defsElement_);
  284. svgElement.appendChild(groupElement);
  285. // Use the svgElement as the root element.
  286. this.setElementInternal(svgElement);
  287. // Set up the coordinate system.
  288. this.setViewBox_();
  289. };
  290. /**
  291. * Changes the coordinate system position.
  292. * @param {number} left The coordinate system left bound.
  293. * @param {number} top The coordinate system top bound.
  294. * @override
  295. */
  296. goog.graphics.SvgGraphics.prototype.setCoordOrigin = function(left, top) {
  297. this.coordLeft = left;
  298. this.coordTop = top;
  299. this.setViewBox_();
  300. };
  301. /**
  302. * Changes the coordinate size.
  303. * @param {number} coordWidth The coordinate width.
  304. * @param {number} coordHeight The coordinate height.
  305. * @override
  306. */
  307. goog.graphics.SvgGraphics.prototype.setCoordSize = function(
  308. coordWidth, coordHeight) {
  309. goog.graphics.SvgGraphics.superClass_.setCoordSize.apply(this, arguments);
  310. this.setViewBox_();
  311. };
  312. /**
  313. * @return {string} The view box string.
  314. * @private
  315. */
  316. goog.graphics.SvgGraphics.prototype.getViewBox_ = function() {
  317. return this.coordLeft + ' ' + this.coordTop + ' ' +
  318. (this.coordWidth ? this.coordWidth + ' ' + this.coordHeight : '');
  319. };
  320. /**
  321. * Sets up the view box.
  322. * @private
  323. */
  324. goog.graphics.SvgGraphics.prototype.setViewBox_ = function() {
  325. if (this.coordWidth || this.coordLeft || this.coordTop) {
  326. this.getElement().setAttribute('preserveAspectRatio', 'none');
  327. if (this.useManualViewbox_) {
  328. this.updateManualViewBox_();
  329. } else {
  330. this.getElement().setAttribute('viewBox', this.getViewBox_());
  331. }
  332. }
  333. };
  334. /**
  335. * Updates the transform of the root element to fake a viewBox. Should only
  336. * be called when useManualViewbox_ is set.
  337. * @private
  338. */
  339. goog.graphics.SvgGraphics.prototype.updateManualViewBox_ = function() {
  340. if (!this.isInDocument() ||
  341. !(this.coordWidth || this.coordLeft || !this.coordTop)) {
  342. return;
  343. }
  344. var size = this.getPixelSize();
  345. if (size.width == 0) {
  346. // In Safari, invisible SVG is sometimes shown. Explicitly hide it.
  347. this.getElement().style.visibility = 'hidden';
  348. return;
  349. }
  350. this.getElement().style.visibility = '';
  351. var offsetX = -this.coordLeft;
  352. var offsetY = -this.coordTop;
  353. var scaleX = size.width / this.coordWidth;
  354. var scaleY = size.height / this.coordHeight;
  355. this.canvasElement.getElement().setAttribute(
  356. 'transform', 'scale(' + scaleX + ' ' + scaleY + ') ' +
  357. 'translate(' + offsetX + ' ' + offsetY + ')');
  358. };
  359. /**
  360. * Change the size of the canvas.
  361. * @param {number} pixelWidth The width in pixels.
  362. * @param {number} pixelHeight The height in pixels.
  363. * @override
  364. */
  365. goog.graphics.SvgGraphics.prototype.setSize = function(
  366. pixelWidth, pixelHeight) {
  367. goog.style.setSize(this.getElement(), pixelWidth, pixelHeight);
  368. };
  369. /** @override */
  370. goog.graphics.SvgGraphics.prototype.getPixelSize = function() {
  371. if (!goog.userAgent.GECKO) {
  372. return this.isInDocument() ?
  373. goog.style.getSize(this.getElement()) :
  374. goog.graphics.SvgGraphics.base(this, 'getPixelSize');
  375. }
  376. // In Gecko, goog.style.getSize does not work for SVG elements. We have to
  377. // compute the size manually if it is percentage based.
  378. var width = this.width;
  379. var height = this.height;
  380. var computeWidth = goog.isString(width) && width.indexOf('%') != -1;
  381. var computeHeight = goog.isString(height) && height.indexOf('%') != -1;
  382. if (!this.isInDocument() && (computeWidth || computeHeight)) {
  383. return null;
  384. }
  385. var parent;
  386. var parentSize;
  387. if (computeWidth) {
  388. parent = /** @type {Element} */ (this.getElement().parentNode);
  389. parentSize = goog.style.getSize(parent);
  390. width = parseFloat(/** @type {string} */ (width)) * parentSize.width / 100;
  391. }
  392. if (computeHeight) {
  393. parent = parent || /** @type {Element} */ (this.getElement().parentNode);
  394. parentSize = parentSize || goog.style.getSize(parent);
  395. height =
  396. parseFloat(/** @type {string} */ (height)) * parentSize.height / 100;
  397. }
  398. return new goog.math.Size(
  399. /** @type {number} */ (width),
  400. /** @type {number} */ (height));
  401. };
  402. /**
  403. * Remove all drawing elements from the graphics.
  404. * @override
  405. */
  406. goog.graphics.SvgGraphics.prototype.clear = function() {
  407. this.canvasElement.clear();
  408. goog.dom.removeChildren(this.defsElement_);
  409. this.defs_ = {};
  410. };
  411. /**
  412. * Draw an ellipse.
  413. *
  414. * @param {number} cx Center X coordinate.
  415. * @param {number} cy Center Y coordinate.
  416. * @param {number} rx Radius length for the x-axis.
  417. * @param {number} ry Radius length for the y-axis.
  418. * @param {goog.graphics.Stroke?} stroke Stroke object describing the
  419. * stroke.
  420. * @param {goog.graphics.Fill?} fill Fill object describing the fill.
  421. * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
  422. * to append to. If not specified, appends to the main canvas.
  423. *
  424. * @return {!goog.graphics.EllipseElement} The newly created element.
  425. * @override
  426. */
  427. goog.graphics.SvgGraphics.prototype.drawEllipse = function(
  428. cx, cy, rx, ry, stroke, fill, opt_group) {
  429. var element = this.createSvgElement_(
  430. 'ellipse', {'cx': cx, 'cy': cy, 'rx': rx, 'ry': ry});
  431. var wrapper =
  432. new goog.graphics.SvgEllipseElement(element, this, stroke, fill);
  433. this.append_(wrapper, opt_group);
  434. return wrapper;
  435. };
  436. /**
  437. * Draw a rectangle.
  438. *
  439. * @param {number} x X coordinate (left).
  440. * @param {number} y Y coordinate (top).
  441. * @param {number} width Width of rectangle.
  442. * @param {number} height Height of rectangle.
  443. * @param {goog.graphics.Stroke?} stroke Stroke object describing the
  444. * stroke.
  445. * @param {goog.graphics.Fill?} fill Fill object describing the fill.
  446. * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
  447. * to append to. If not specified, appends to the main canvas.
  448. *
  449. * @return {!goog.graphics.RectElement} The newly created element.
  450. * @override
  451. */
  452. goog.graphics.SvgGraphics.prototype.drawRect = function(
  453. x, y, width, height, stroke, fill, opt_group) {
  454. var element = this.createSvgElement_(
  455. 'rect', {'x': x, 'y': y, 'width': width, 'height': height});
  456. var wrapper = new goog.graphics.SvgRectElement(element, this, stroke, fill);
  457. this.append_(wrapper, opt_group);
  458. return wrapper;
  459. };
  460. /**
  461. * Draw an image.
  462. *
  463. * @param {number} x X coordinate (left).
  464. * @param {number} y Y coordinate (top).
  465. * @param {number} width Width of the image.
  466. * @param {number} height Height of the image.
  467. * @param {string} src The source fo the image.
  468. * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
  469. * to append to. If not specified, appends to the main canvas.
  470. *
  471. * @return {!goog.graphics.ImageElement} The newly created image wrapped in a
  472. * rectangle element.
  473. */
  474. goog.graphics.SvgGraphics.prototype.drawImage = function(
  475. x, y, width, height, src, opt_group) {
  476. var element = this.createSvgElement_('image', {
  477. 'x': x,
  478. 'y': y,
  479. 'width': width,
  480. 'height': height,
  481. 'image-rendering': 'optimizeQuality',
  482. 'preserveAspectRatio': 'none'
  483. });
  484. element.setAttributeNS('http://www.w3.org/1999/xlink', 'href', src);
  485. var wrapper = new goog.graphics.SvgImageElement(element, this);
  486. this.append_(wrapper, opt_group);
  487. return wrapper;
  488. };
  489. /**
  490. * Draw a text string vertically centered on a given line.
  491. *
  492. * @param {string} text The text to draw.
  493. * @param {number} x1 X coordinate of start of line.
  494. * @param {number} y1 Y coordinate of start of line.
  495. * @param {number} x2 X coordinate of end of line.
  496. * @param {number} y2 Y coordinate of end of line.
  497. * @param {string} align Horizontal alignment: left (default), center, right.
  498. * @param {goog.graphics.Font} font Font describing the font properties.
  499. * @param {goog.graphics.Stroke?} stroke Stroke object describing the
  500. * stroke.
  501. * @param {goog.graphics.Fill?} fill Fill object describing the fill.
  502. * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
  503. * to append to. If not specified, appends to the main canvas.
  504. *
  505. * @return {!goog.graphics.TextElement} The newly created element.
  506. * @override
  507. */
  508. goog.graphics.SvgGraphics.prototype.drawTextOnLine = function(
  509. text, x1, y1, x2, y2, align, font, stroke, fill, opt_group) {
  510. var angle = Math.round(goog.math.angle(x1, y1, x2, y2));
  511. var dx = x2 - x1;
  512. var dy = y2 - y1;
  513. var lineLength = Math.round(Math.sqrt(dx * dx + dy * dy)); // Length of line
  514. // SVG baseline is on the glyph's base line. We estimate it as 85% of the
  515. // font height. This is just a rough estimate, but do not have a better way.
  516. var fontSize = font.size;
  517. var attributes = {'font-family': font.family, 'font-size': fontSize};
  518. var baseline = Math.round(fontSize * 0.85);
  519. var textY = Math.round(y1 - (fontSize / 2) + baseline);
  520. var textX = x1;
  521. if (align == 'center') {
  522. textX += Math.round(lineLength / 2);
  523. attributes['text-anchor'] = 'middle';
  524. } else if (align == 'right') {
  525. textX += lineLength;
  526. attributes['text-anchor'] = 'end';
  527. }
  528. attributes['x'] = textX;
  529. attributes['y'] = textY;
  530. if (font.bold) {
  531. attributes['font-weight'] = 'bold';
  532. }
  533. if (font.italic) {
  534. attributes['font-style'] = 'italic';
  535. }
  536. if (angle != 0) {
  537. attributes['transform'] = 'rotate(' + angle + ' ' + x1 + ' ' + y1 + ')';
  538. }
  539. var element = this.createSvgElement_('text', attributes);
  540. element.appendChild(this.dom_.getDocument().createTextNode(text));
  541. // Bypass a Firefox-Mac bug where text fill is ignored. If text has no stroke,
  542. // set a stroke, otherwise the text will not be visible.
  543. if (stroke == null && goog.userAgent.GECKO && goog.userAgent.MAC) {
  544. var color = 'black';
  545. // For solid fills, use the fill color
  546. if (fill instanceof goog.graphics.SolidFill) {
  547. color = fill.getColor();
  548. }
  549. stroke = new goog.graphics.Stroke(1, color);
  550. }
  551. var wrapper = new goog.graphics.SvgTextElement(element, this, stroke, fill);
  552. this.append_(wrapper, opt_group);
  553. return wrapper;
  554. };
  555. /**
  556. * Draw a path.
  557. *
  558. * @param {!goog.graphics.Path} path The path object to draw.
  559. * @param {goog.graphics.Stroke?} stroke Stroke object describing the
  560. * stroke.
  561. * @param {goog.graphics.Fill?} fill Fill object describing the fill.
  562. * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
  563. * to append to. If not specified, appends to the main canvas.
  564. *
  565. * @return {!goog.graphics.PathElement} The newly created element.
  566. * @override
  567. */
  568. goog.graphics.SvgGraphics.prototype.drawPath = function(
  569. path, stroke, fill, opt_group) {
  570. var element = this.createSvgElement_(
  571. 'path', {'d': goog.graphics.SvgGraphics.getSvgPath(path)});
  572. var wrapper = new goog.graphics.SvgPathElement(element, this, stroke, fill);
  573. this.append_(wrapper, opt_group);
  574. return wrapper;
  575. };
  576. /**
  577. * Returns a string representation of a logical path suitable for use in
  578. * an SVG element.
  579. *
  580. * @param {goog.graphics.Path} path The logical path.
  581. * @return {string} The SVG path representation.
  582. * @suppress {deprecated} goog.graphics is deprecated.
  583. */
  584. goog.graphics.SvgGraphics.getSvgPath = function(path) {
  585. var list = [];
  586. path.forEachSegment(function(segment, args) {
  587. switch (segment) {
  588. case goog.graphics.Path.Segment.MOVETO:
  589. list.push('M');
  590. Array.prototype.push.apply(list, args);
  591. break;
  592. case goog.graphics.Path.Segment.LINETO:
  593. list.push('L');
  594. Array.prototype.push.apply(list, args);
  595. break;
  596. case goog.graphics.Path.Segment.CURVETO:
  597. list.push('C');
  598. Array.prototype.push.apply(list, args);
  599. break;
  600. case goog.graphics.Path.Segment.ARCTO:
  601. var extent = args[3];
  602. list.push(
  603. 'A', args[0], args[1], 0, Math.abs(extent) > 180 ? 1 : 0,
  604. extent > 0 ? 1 : 0, args[4], args[5]);
  605. break;
  606. case goog.graphics.Path.Segment.CLOSE:
  607. list.push('Z');
  608. break;
  609. }
  610. });
  611. return list.join(' ');
  612. };
  613. /**
  614. * Create an empty group of drawing elements.
  615. *
  616. * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
  617. * to append to. If not specified, appends to the main canvas.
  618. *
  619. * @return {!goog.graphics.GroupElement} The newly created group.
  620. * @override
  621. */
  622. goog.graphics.SvgGraphics.prototype.createGroup = function(opt_group) {
  623. var element = this.createSvgElement_('g');
  624. var parent = opt_group || this.canvasElement;
  625. parent.getElement().appendChild(element);
  626. return new goog.graphics.SvgGroupElement(element, this);
  627. };
  628. /**
  629. * Measure and return the width (in pixels) of a given text string.
  630. * Text measurement is needed to make sure a text can fit in the allocated area.
  631. * The way text length is measured is by writing it into a div that is after
  632. * the visible area, measure the div width, and immediately erase the written
  633. * value.
  634. *
  635. * @override
  636. */
  637. goog.graphics.SvgGraphics.prototype.getTextWidth = function(text, font) {
  638. // TODO(user) Implement
  639. throw new Error("unimplemented method");
  640. };
  641. /**
  642. * Adds a definition of an element to the global definitions.
  643. * @param {string} defKey This is a key that should be unique in a way that
  644. * if two definitions are equal the should have the same key.
  645. * @param {Element} defElement DOM element to add as a definition. It must
  646. * have an id attribute set.
  647. * @return {string} The assigned id of the defElement.
  648. */
  649. goog.graphics.SvgGraphics.prototype.addDef = function(defKey, defElement) {
  650. if (defKey in this.defs_) {
  651. return this.defs_[defKey];
  652. }
  653. var id = goog.graphics.SvgGraphics.DEF_ID_PREFIX_ +
  654. goog.graphics.SvgGraphics.nextDefId_++;
  655. defElement.setAttribute('id', id);
  656. this.defs_[defKey] = id;
  657. // Add the def defElement of the defs list.
  658. var defs = this.defsElement_;
  659. defs.appendChild(defElement);
  660. return id;
  661. };
  662. /**
  663. * Returns the id of a definition element.
  664. * @param {string} defKey This is a key that should be unique in a way that
  665. * if two definitions are equal the should have the same key.
  666. * @return {?string} The id of the found definition element or null if
  667. * not found.
  668. */
  669. goog.graphics.SvgGraphics.prototype.getDef = function(defKey) {
  670. return defKey in this.defs_ ? this.defs_[defKey] : null;
  671. };
  672. /**
  673. * Removes a definition of an elemnt from the global definitions.
  674. * @param {string} defKey This is a key that should be unique in a way that
  675. * if two definitions are equal they should have the same key.
  676. */
  677. goog.graphics.SvgGraphics.prototype.removeDef = function(defKey) {
  678. var id = this.getDef(defKey);
  679. if (id) {
  680. var element = this.dom_.getElement(id);
  681. this.defsElement_.removeChild(element);
  682. delete this.defs_[defKey];
  683. }
  684. };
  685. /** @override */
  686. goog.graphics.SvgGraphics.prototype.enterDocument = function() {
  687. var oldPixelSize = this.getPixelSize();
  688. goog.graphics.SvgGraphics.superClass_.enterDocument.call(this);
  689. // Dispatch a resize if this is the first time the size value is accurate.
  690. if (!oldPixelSize) {
  691. this.dispatchEvent(goog.events.EventType.RESIZE);
  692. }
  693. // For percentage based heights, listen for changes to size.
  694. if (this.useManualViewbox_) {
  695. var width = this.width;
  696. var height = this.height;
  697. if (typeof width == 'string' && width.indexOf('%') != -1 &&
  698. typeof height == 'string' && height.indexOf('%') != -1) {
  699. // SVG elements don't behave well with respect to size events, so we
  700. // resort to polling.
  701. this.handler_.listen(
  702. goog.graphics.SvgGraphics.getResizeCheckTimer_(), goog.Timer.TICK,
  703. this.updateManualViewBox_);
  704. }
  705. this.updateManualViewBox_();
  706. }
  707. };
  708. /** @override */
  709. goog.graphics.SvgGraphics.prototype.exitDocument = function() {
  710. goog.graphics.SvgGraphics.superClass_.exitDocument.call(this);
  711. // Stop polling.
  712. if (this.useManualViewbox_) {
  713. this.handler_.unlisten(
  714. goog.graphics.SvgGraphics.getResizeCheckTimer_(), goog.Timer.TICK,
  715. this.updateManualViewBox_);
  716. }
  717. };
  718. /**
  719. * Disposes of the component by removing event handlers, detacing DOM nodes from
  720. * the document body, and removing references to them.
  721. * @override
  722. * @protected
  723. */
  724. goog.graphics.SvgGraphics.prototype.disposeInternal = function() {
  725. delete this.defs_;
  726. delete this.defsElement_;
  727. delete this.canvasElement;
  728. this.handler_.dispose();
  729. delete this.handler_;
  730. goog.graphics.SvgGraphics.superClass_.disposeInternal.call(this);
  731. };
  732. /**
  733. * The centralized resize checking timer.
  734. * @type {goog.Timer|undefined}
  735. * @private
  736. */
  737. goog.graphics.SvgGraphics.resizeCheckTimer_;
  738. /**
  739. * @return {goog.Timer} The centralized timer object used for interval timing.
  740. * @private
  741. */
  742. goog.graphics.SvgGraphics.getResizeCheckTimer_ = function() {
  743. if (!goog.graphics.SvgGraphics.resizeCheckTimer_) {
  744. goog.graphics.SvgGraphics.resizeCheckTimer_ = new goog.Timer(400);
  745. goog.graphics.SvgGraphics.resizeCheckTimer_.start();
  746. }
  747. return /** @type {goog.Timer} */ (
  748. goog.graphics.SvgGraphics.resizeCheckTimer_);
  749. };
  750. /** @override */
  751. goog.graphics.SvgGraphics.prototype.isDomClonable = function() {
  752. return true;
  753. };