vmlgraphics.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960
  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 VmlGraphics sub class that uses VML to draw the graphics.
  16. * @author arv@google.com (Erik Arvidsson)
  17. */
  18. goog.provide('goog.graphics.VmlGraphics');
  19. goog.require('goog.array');
  20. goog.require('goog.dom.TagName');
  21. goog.require('goog.dom.safe');
  22. goog.require('goog.events');
  23. goog.require('goog.events.EventHandler');
  24. goog.require('goog.events.EventType');
  25. goog.require('goog.graphics.AbstractGraphics');
  26. goog.require('goog.graphics.Font');
  27. goog.require('goog.graphics.LinearGradient');
  28. goog.require('goog.graphics.Path');
  29. goog.require('goog.graphics.SolidFill');
  30. goog.require('goog.graphics.VmlEllipseElement');
  31. goog.require('goog.graphics.VmlGroupElement');
  32. goog.require('goog.graphics.VmlImageElement');
  33. goog.require('goog.graphics.VmlPathElement');
  34. goog.require('goog.graphics.VmlRectElement');
  35. goog.require('goog.graphics.VmlTextElement');
  36. goog.require('goog.html.uncheckedconversions');
  37. goog.require('goog.math');
  38. goog.require('goog.math.Size');
  39. goog.require('goog.reflect');
  40. goog.require('goog.string');
  41. goog.require('goog.string.Const');
  42. goog.require('goog.style');
  43. goog.require('goog.userAgent');
  44. /**
  45. * A Graphics implementation for drawing using VML.
  46. * @param {string|number} width The (non-zero) width in pixels. Strings
  47. * expressing percentages of parent with (e.g. '80%') are also accepted.
  48. * @param {string|number} height The (non-zero) height in pixels. Strings
  49. * expressing percentages of parent with (e.g. '80%') are also accepted.
  50. * @param {?number=} opt_coordWidth The coordinate width - if
  51. * omitted or null, defaults to same as width.
  52. * @param {?number=} opt_coordHeight The coordinate height - if
  53. * omitted or null, defaults to same as height.
  54. * @param {goog.dom.DomHelper=} opt_domHelper The DOM helper object for the
  55. * document we want to render in.
  56. * @constructor
  57. * @extends {goog.graphics.AbstractGraphics}
  58. * @deprecated goog.graphics is deprecated. It existed to abstract over browser
  59. * differences before the canvas tag was widely supported. See
  60. * http://en.wikipedia.org/wiki/Canvas_element for details.
  61. * @final
  62. */
  63. goog.graphics.VmlGraphics = function(
  64. width, height, opt_coordWidth, opt_coordHeight, opt_domHelper) {
  65. goog.graphics.AbstractGraphics.call(
  66. this, width, height, opt_coordWidth, opt_coordHeight, opt_domHelper);
  67. this.handler_ = new goog.events.EventHandler(this);
  68. this.registerDisposable(this.handler_);
  69. };
  70. goog.inherits(goog.graphics.VmlGraphics, goog.graphics.AbstractGraphics);
  71. /**
  72. * The prefix to use for VML elements
  73. * @private
  74. * @type {string}
  75. */
  76. goog.graphics.VmlGraphics.VML_PREFIX_ = 'g_vml_';
  77. /**
  78. * The VML namespace URN
  79. * @private
  80. * @type {string}
  81. */
  82. goog.graphics.VmlGraphics.VML_NS_ = 'urn:schemas-microsoft-com:vml';
  83. /**
  84. * The VML behavior URL.
  85. * @private
  86. * @type {string}
  87. */
  88. goog.graphics.VmlGraphics.VML_IMPORT_ = '#default#VML';
  89. /**
  90. * Whether the document is using IE8 standards mode, and therefore needs hacks.
  91. * @private
  92. * @type {boolean}
  93. */
  94. goog.graphics.VmlGraphics.IE8_MODE_ = goog.global.document &&
  95. goog.global.document.documentMode && goog.global.document.documentMode >= 8;
  96. /**
  97. * The coordinate multiplier to allow sub-pixel rendering
  98. * @type {number}
  99. */
  100. goog.graphics.VmlGraphics.COORD_MULTIPLIER = 100;
  101. /**
  102. * Converts the given size to a css size. If it is a percentage, leaves it
  103. * alone. Otherwise assumes px.
  104. *
  105. * @param {number|string} size The size to use.
  106. * @return {string} The position adjusted for COORD_MULTIPLIER.
  107. */
  108. goog.graphics.VmlGraphics.toCssSize = function(size) {
  109. return goog.isString(size) && goog.string.endsWith(size, '%') ?
  110. size :
  111. parseFloat(size.toString()) + 'px';
  112. };
  113. /**
  114. * Multiplies positioning coordinates by COORD_MULTIPLIER to allow sub-pixel
  115. * coordinates. Also adds a half pixel offset to match SVG.
  116. *
  117. * This function is internal for the VML supporting classes, and
  118. * should not be used externally.
  119. *
  120. * @param {number|string} number A position in pixels.
  121. * @return {number} The position adjusted for COORD_MULTIPLIER.
  122. */
  123. goog.graphics.VmlGraphics.toPosCoord = function(number) {
  124. return Math.round(
  125. (parseFloat(number.toString()) - 0.5) *
  126. goog.graphics.VmlGraphics.COORD_MULTIPLIER);
  127. };
  128. /**
  129. * Add a "px" suffix to a number of pixels, and multiplies all coordinates by
  130. * COORD_MULTIPLIER to allow sub-pixel coordinates.
  131. *
  132. * This function is internal for the VML supporting classes, and
  133. * should not be used externally.
  134. *
  135. * @param {number|string} number A position in pixels.
  136. * @return {string} The position with suffix 'px'.
  137. */
  138. goog.graphics.VmlGraphics.toPosPx = function(number) {
  139. return goog.graphics.VmlGraphics.toPosCoord(number) + 'px';
  140. };
  141. /**
  142. * Multiplies the width or height coordinate by COORD_MULTIPLIER to allow
  143. * sub-pixel coordinates.
  144. *
  145. * This function is internal for the VML supporting classes, and
  146. * should not be used externally.
  147. *
  148. * @param {string|number} number A size in units.
  149. * @return {number} The size multiplied by the correct factor.
  150. */
  151. goog.graphics.VmlGraphics.toSizeCoord = function(number) {
  152. return Math.round(
  153. parseFloat(number.toString()) *
  154. goog.graphics.VmlGraphics.COORD_MULTIPLIER);
  155. };
  156. /**
  157. * Add a "px" suffix to a number of pixels, and multiplies all coordinates by
  158. * COORD_MULTIPLIER to allow sub-pixel coordinates.
  159. *
  160. * This function is internal for the VML supporting classes, and
  161. * should not be used externally.
  162. *
  163. * @param {number|string} number A size in pixels.
  164. * @return {string} The size with suffix 'px'.
  165. */
  166. goog.graphics.VmlGraphics.toSizePx = function(number) {
  167. return goog.graphics.VmlGraphics.toSizeCoord(number) + 'px';
  168. };
  169. /**
  170. * Sets an attribute on the given VML element, in the way best suited to the
  171. * current version of IE. Should only be used in the goog.graphics package.
  172. * @param {Element} element The element to set an attribute
  173. * on.
  174. * @param {string} name The name of the attribute to set.
  175. * @param {string} value The value to set it to.
  176. */
  177. goog.graphics.VmlGraphics.setAttribute = function(element, name, value) {
  178. if (goog.graphics.VmlGraphics.IE8_MODE_) {
  179. element[name] = value;
  180. } else {
  181. element.setAttribute(name, value);
  182. }
  183. };
  184. /**
  185. * Event handler.
  186. * @type {goog.events.EventHandler}
  187. * @private
  188. */
  189. goog.graphics.VmlGraphics.prototype.handler_;
  190. /**
  191. * Creates a VML element. Used internally and by different VML classes.
  192. * @param {string} tagName The type of element to create.
  193. * @return {!Element} The created element.
  194. */
  195. goog.graphics.VmlGraphics.prototype.createVmlElement = function(tagName) {
  196. var element = this.dom_.createElement(
  197. goog.graphics.VmlGraphics.VML_PREFIX_ + ':' + tagName);
  198. element.id = goog.string.createUniqueString();
  199. return element;
  200. };
  201. /**
  202. * Returns the VML element with the given id that is a child of this graphics
  203. * object.
  204. * Should be considered package private, and not used externally.
  205. * @param {string} id The element id to find.
  206. * @return {Element} The element with the given id, or null if none is found.
  207. */
  208. goog.graphics.VmlGraphics.prototype.getVmlElement = function(id) {
  209. return this.dom_.getElement(id);
  210. };
  211. /**
  212. * Resets the graphics so they will display properly on IE8. Noop in older
  213. * versions.
  214. * @private
  215. */
  216. goog.graphics.VmlGraphics.prototype.updateGraphics_ = function() {
  217. if (goog.graphics.VmlGraphics.IE8_MODE_ && this.isInDocument()) {
  218. // There's a risk of mXSS here, as the browser is not guaranteed to
  219. // return the HTML that was originally written, when innerHTML is read.
  220. // However, given that this a deprecated API and affects only IE, it seems
  221. // an acceptable risk.
  222. var html = goog.html.uncheckedconversions
  223. .safeHtmlFromStringKnownToSatisfyTypeContract(
  224. goog.string.Const.from('Assign innerHTML to itself'),
  225. this.getElement().innerHTML);
  226. goog.dom.safe.setInnerHtml(
  227. /** @type {!Element} */ (this.getElement()), html);
  228. }
  229. };
  230. /**
  231. * Appends an element.
  232. *
  233. * @param {goog.graphics.Element} element The element wrapper.
  234. * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
  235. * to append to. If not specified, appends to the main canvas.
  236. * @private
  237. */
  238. goog.graphics.VmlGraphics.prototype.append_ = function(element, opt_group) {
  239. var parent = opt_group || this.canvasElement;
  240. parent.getElement().appendChild(element.getElement());
  241. this.updateGraphics_();
  242. };
  243. /**
  244. * Sets the fill for the given element.
  245. * @param {goog.graphics.StrokeAndFillElement} element The element wrapper.
  246. * @param {goog.graphics.Fill?} fill The fill object.
  247. * @override
  248. */
  249. goog.graphics.VmlGraphics.prototype.setElementFill = function(element, fill) {
  250. var vmlElement = element.getElement();
  251. goog.graphics.VmlGraphics.removeFill_(vmlElement);
  252. if (fill instanceof goog.graphics.SolidFill) {
  253. // NOTE(arv): VML does not understand 'transparent' so hard code support
  254. // for it.
  255. if (fill.getColor() == 'transparent') {
  256. vmlElement.filled = false;
  257. } else if (fill.getOpacity() != 1) {
  258. vmlElement.filled = true;
  259. // Set opacity (number 0-1 is translated to percent)
  260. var fillNode = this.createVmlElement('fill');
  261. fillNode.opacity = Math.round(fill.getOpacity() * 100) + '%';
  262. fillNode.color = fill.getColor();
  263. vmlElement.appendChild(fillNode);
  264. } else {
  265. vmlElement.filled = true;
  266. vmlElement.fillcolor = fill.getColor();
  267. }
  268. } else if (fill instanceof goog.graphics.LinearGradient) {
  269. vmlElement.filled = true;
  270. // Add a 'fill' element
  271. var gradient = this.createVmlElement('fill');
  272. gradient.color = fill.getColor1();
  273. gradient.color2 = fill.getColor2();
  274. if (goog.isNumber(fill.getOpacity1())) {
  275. gradient.opacity = fill.getOpacity1();
  276. }
  277. if (goog.isNumber(fill.getOpacity2())) {
  278. gradient.opacity2 = fill.getOpacity2();
  279. }
  280. var angle =
  281. goog.math.angle(fill.getX1(), fill.getY1(), fill.getX2(), fill.getY2());
  282. // Our angles start from 0 to the right, and grow clockwise.
  283. // MSIE starts from 0 to top, and grows anti-clockwise.
  284. angle = Math.round(goog.math.standardAngle(270 - angle));
  285. gradient.angle = angle;
  286. gradient.type = 'gradient';
  287. vmlElement.appendChild(gradient);
  288. } else {
  289. vmlElement.filled = false;
  290. }
  291. this.updateGraphics_();
  292. };
  293. /**
  294. * Sets the stroke for the given element.
  295. * @param {goog.graphics.StrokeAndFillElement} element The element wrapper.
  296. * @param {goog.graphics.Stroke?} stroke The stroke object.
  297. * @override
  298. */
  299. goog.graphics.VmlGraphics.prototype.setElementStroke = function(
  300. element, stroke) {
  301. var vmlElement = element.getElement();
  302. if (stroke) {
  303. vmlElement.stroked = true;
  304. var width = stroke.getWidth();
  305. if (goog.isString(width) && width.indexOf('px') == -1) {
  306. width = parseFloat(width);
  307. } else {
  308. width = width * this.getPixelScaleX();
  309. }
  310. var strokeElement = vmlElement.getElementsByTagName('stroke')[0];
  311. if (!strokeElement) {
  312. strokeElement = strokeElement || this.createVmlElement('stroke');
  313. vmlElement.appendChild(strokeElement);
  314. }
  315. strokeElement.opacity = stroke.getOpacity();
  316. strokeElement.weight = width + 'px';
  317. strokeElement.color = stroke.getColor();
  318. } else {
  319. vmlElement.stroked = false;
  320. }
  321. this.updateGraphics_();
  322. };
  323. /**
  324. * Set the translation and rotation of an element.
  325. *
  326. * If a more general affine transform is needed than this provides
  327. * (e.g. skew and scale) then use setElementAffineTransform.
  328. * @param {goog.graphics.Element} element The element wrapper.
  329. * @param {number} x The x coordinate of the translation transform.
  330. * @param {number} y The y coordinate of the translation transform.
  331. * @param {number} angle The angle of the rotation transform.
  332. * @param {number} centerX The horizontal center of the rotation transform.
  333. * @param {number} centerY The vertical center of the rotation transform.
  334. * @override
  335. */
  336. goog.graphics.VmlGraphics.prototype.setElementTransform = function(
  337. element, x, y, angle, centerX, centerY) {
  338. var el = element.getElement();
  339. el.style.left = goog.graphics.VmlGraphics.toPosPx(x);
  340. el.style.top = goog.graphics.VmlGraphics.toPosPx(y);
  341. if (angle || el.rotation) {
  342. el.rotation = angle;
  343. el.coordsize = goog.graphics.VmlGraphics.toSizeCoord(centerX * 2) + ' ' +
  344. goog.graphics.VmlGraphics.toSizeCoord(centerY * 2);
  345. }
  346. };
  347. /**
  348. * Set the transformation of an element.
  349. * @param {!goog.graphics.Element} element The element wrapper.
  350. * @param {!goog.graphics.AffineTransform} affineTransform The
  351. * transformation applied to this element.
  352. * @override
  353. */
  354. goog.graphics.VmlGraphics.prototype.setElementAffineTransform = function(
  355. element, affineTransform) {
  356. var t = affineTransform;
  357. var vmlElement = element.getElement();
  358. goog.graphics.VmlGraphics.removeSkew_(vmlElement);
  359. var skewNode = this.createVmlElement('skew');
  360. skewNode.on = 'true';
  361. // Move the transform origin to 0px,0px of the graphics.
  362. // In VML, 0,0 means the center of the element, -0.5,-0.5 left top conner of
  363. // it.
  364. skewNode.origin =
  365. (-vmlElement.style.pixelLeft / vmlElement.style.pixelWidth - 0.5) + ',' +
  366. (-vmlElement.style.pixelTop / vmlElement.style.pixelHeight - 0.5);
  367. skewNode.offset = t.getTranslateX().toFixed(1) + 'px,' +
  368. t.getTranslateY().toFixed(1) + 'px';
  369. skewNode.matrix = [
  370. t.getScaleX().toFixed(6), t.getShearX().toFixed(6),
  371. t.getShearY().toFixed(6), t.getScaleY().toFixed(6), 0, 0
  372. ].join(',');
  373. vmlElement.appendChild(skewNode);
  374. this.updateGraphics_();
  375. };
  376. /**
  377. * Removes the skew information from a dom element.
  378. * @param {Element} element DOM element.
  379. * @private
  380. */
  381. goog.graphics.VmlGraphics.removeSkew_ = function(element) {
  382. goog.array.forEach(element.childNodes, function(child) {
  383. if (child.tagName == 'skew') {
  384. element.removeChild(child);
  385. }
  386. });
  387. };
  388. /**
  389. * Removes the fill information from a dom element.
  390. * @param {Element} element DOM element.
  391. * @private
  392. */
  393. goog.graphics.VmlGraphics.removeFill_ = function(element) {
  394. element.fillcolor = '';
  395. goog.array.forEach(element.childNodes, function(child) {
  396. if (child.tagName == 'fill') {
  397. element.removeChild(child);
  398. }
  399. });
  400. };
  401. /**
  402. * Set top, left, width and height for an element.
  403. * This function is internal for the VML supporting classes, and
  404. * should not be used externally.
  405. *
  406. * @param {Element} element DOM element.
  407. * @param {number} left Left ccordinate in pixels.
  408. * @param {number} top Top ccordinate in pixels.
  409. * @param {number} width Width in pixels.
  410. * @param {number} height Height in pixels.
  411. */
  412. goog.graphics.VmlGraphics.setPositionAndSize = function(
  413. element, left, top, width, height) {
  414. var style = element.style;
  415. style.position = 'absolute';
  416. style.left = goog.graphics.VmlGraphics.toPosPx(left);
  417. style.top = goog.graphics.VmlGraphics.toPosPx(top);
  418. style.width = goog.graphics.VmlGraphics.toSizePx(width);
  419. style.height = goog.graphics.VmlGraphics.toSizePx(height);
  420. if (element.tagName == 'shape') {
  421. element.coordsize = goog.graphics.VmlGraphics.toSizeCoord(width) + ' ' +
  422. goog.graphics.VmlGraphics.toSizeCoord(height);
  423. }
  424. };
  425. /**
  426. * Creates an element spanning the surface.
  427. *
  428. * @param {string} type The type of element to create.
  429. * @return {!Element} The created, positioned, and sized element.
  430. * @private
  431. */
  432. goog.graphics.VmlGraphics.prototype.createFullSizeElement_ = function(type) {
  433. var element = this.createVmlElement(type);
  434. var size = this.getCoordSize();
  435. goog.graphics.VmlGraphics.setPositionAndSize(
  436. element, 0, 0, size.width, size.height);
  437. return element;
  438. };
  439. /**
  440. * IE magic - if this "no-op" logic is not here, the 'if' statement in createDom
  441. * will fail intermittently. The logic is used to prevent the JsCompiler from
  442. * stripping this piece of code, which it quite reasonably thinks is doing
  443. * nothing. Put it in try-catch block to prevent "Unspecified Error" when
  444. * this statement is executed in a defer JS in IE.
  445. * More info here:
  446. * http://www.mail-archive.com/users@openlayers.org/msg01838.html
  447. */
  448. if (goog.userAgent.IE) {
  449. try {
  450. goog.reflect.sinkValue(document.namespaces);
  451. } catch (e) {
  452. }
  453. }
  454. /**
  455. * Creates the DOM representation of the graphics area.
  456. * @override
  457. */
  458. goog.graphics.VmlGraphics.prototype.createDom = function() {
  459. var doc = this.dom_.getDocument();
  460. // Add the namespace.
  461. if (!doc.namespaces[goog.graphics.VmlGraphics.VML_PREFIX_]) {
  462. if (goog.graphics.VmlGraphics.IE8_MODE_) {
  463. doc.namespaces.add(
  464. goog.graphics.VmlGraphics.VML_PREFIX_,
  465. goog.graphics.VmlGraphics.VML_NS_,
  466. goog.graphics.VmlGraphics.VML_IMPORT_);
  467. } else {
  468. doc.namespaces.add(
  469. goog.graphics.VmlGraphics.VML_PREFIX_,
  470. goog.graphics.VmlGraphics.VML_NS_);
  471. }
  472. // We assume that we only need to add the CSS if the namespace was not
  473. // present
  474. var ss = doc.createStyleSheet();
  475. ss.cssText = goog.graphics.VmlGraphics.VML_PREFIX_ + '\\:*' +
  476. '{behavior:url(#default#VML)}';
  477. }
  478. // Outer a DIV with overflow hidden for clipping.
  479. // All inner elements are absolutely positioned on-top of this div.
  480. var pixelWidth = this.width;
  481. var pixelHeight = this.height;
  482. var divElement = this.dom_.createDom(goog.dom.TagName.DIV, {
  483. 'style': 'overflow:hidden;position:relative;width:' +
  484. goog.graphics.VmlGraphics.toCssSize(pixelWidth) + ';height:' +
  485. goog.graphics.VmlGraphics.toCssSize(pixelHeight)
  486. });
  487. this.setElementInternal(divElement);
  488. var group = this.createVmlElement('group');
  489. var style = group.style;
  490. style.position = 'absolute';
  491. style.left = style.top = '0';
  492. style.width = this.width;
  493. style.height = this.height;
  494. if (this.coordWidth) {
  495. group.coordsize = goog.graphics.VmlGraphics.toSizeCoord(this.coordWidth) +
  496. ' ' +
  497. goog.graphics.VmlGraphics.toSizeCoord(
  498. /** @type {number} */ (this.coordHeight));
  499. } else {
  500. group.coordsize = goog.graphics.VmlGraphics.toSizeCoord(pixelWidth) + ' ' +
  501. goog.graphics.VmlGraphics.toSizeCoord(pixelHeight);
  502. }
  503. if (goog.isDef(this.coordLeft)) {
  504. group.coordorigin = goog.graphics.VmlGraphics.toSizeCoord(this.coordLeft) +
  505. ' ' + goog.graphics.VmlGraphics.toSizeCoord(this.coordTop);
  506. } else {
  507. group.coordorigin = '0 0';
  508. }
  509. divElement.appendChild(group);
  510. this.canvasElement = new goog.graphics.VmlGroupElement(group, this);
  511. goog.events.listen(
  512. divElement, goog.events.EventType.RESIZE,
  513. goog.bind(this.handleContainerResize_, this));
  514. };
  515. /**
  516. * Changes the canvas element size to match the container element size.
  517. * @private
  518. */
  519. goog.graphics.VmlGraphics.prototype.handleContainerResize_ = function() {
  520. var size = goog.style.getSize(this.getElement());
  521. var style = this.canvasElement.getElement().style;
  522. if (size.width) {
  523. style.width = size.width + 'px';
  524. style.height = size.height + 'px';
  525. } else {
  526. var current = this.getElement();
  527. while (current && current.currentStyle &&
  528. current.currentStyle.display != 'none') {
  529. current = current.parentNode;
  530. }
  531. if (current && current.currentStyle) {
  532. this.handler_.listen(
  533. current, 'propertychange', this.handleContainerResize_);
  534. }
  535. }
  536. this.dispatchEvent(goog.events.EventType.RESIZE);
  537. };
  538. /**
  539. * Handle property changes on hidden ancestors.
  540. * @param {goog.events.BrowserEvent} e The browser event.
  541. * @private
  542. */
  543. goog.graphics.VmlGraphics.prototype.handlePropertyChange_ = function(e) {
  544. var prop = e.getBrowserEvent().propertyName;
  545. if (prop == 'display' || prop == 'className') {
  546. this.handler_.unlisten(
  547. /** @type {Element} */ (e.target), 'propertychange',
  548. this.handlePropertyChange_);
  549. this.handleContainerResize_();
  550. }
  551. };
  552. /**
  553. * Changes the coordinate system position.
  554. * @param {number} left The coordinate system left bound.
  555. * @param {number} top The coordinate system top bound.
  556. * @override
  557. */
  558. goog.graphics.VmlGraphics.prototype.setCoordOrigin = function(left, top) {
  559. this.coordLeft = left;
  560. this.coordTop = top;
  561. this.canvasElement.getElement().coordorigin =
  562. goog.graphics.VmlGraphics.toSizeCoord(this.coordLeft) + ' ' +
  563. goog.graphics.VmlGraphics.toSizeCoord(this.coordTop);
  564. };
  565. /**
  566. * Changes the coordinate size.
  567. * @param {number} coordWidth The coordinate width.
  568. * @param {number} coordHeight The coordinate height.
  569. * @override
  570. */
  571. goog.graphics.VmlGraphics.prototype.setCoordSize = function(
  572. coordWidth, coordHeight) {
  573. goog.graphics.VmlGraphics.superClass_.setCoordSize.apply(this, arguments);
  574. this.canvasElement.getElement().coordsize =
  575. goog.graphics.VmlGraphics.toSizeCoord(coordWidth) + ' ' +
  576. goog.graphics.VmlGraphics.toSizeCoord(coordHeight);
  577. };
  578. /**
  579. * Change the size of the canvas.
  580. * @param {number} pixelWidth The width in pixels.
  581. * @param {number} pixelHeight The height in pixels.
  582. * @override
  583. */
  584. goog.graphics.VmlGraphics.prototype.setSize = function(
  585. pixelWidth, pixelHeight) {
  586. goog.style.setSize(this.getElement(), pixelWidth, pixelHeight);
  587. };
  588. /**
  589. * @return {!goog.math.Size} Returns the number of pixels spanned by the
  590. * surface.
  591. * @override
  592. */
  593. goog.graphics.VmlGraphics.prototype.getPixelSize = function() {
  594. var el = this.getElement();
  595. // The following relies on the fact that the size can never be 0.
  596. return new goog.math.Size(
  597. el.style.pixelWidth || el.offsetWidth || 1,
  598. el.style.pixelHeight || el.offsetHeight || 1);
  599. };
  600. /**
  601. * Remove all drawing elements from the graphics.
  602. * @override
  603. */
  604. goog.graphics.VmlGraphics.prototype.clear = function() {
  605. this.canvasElement.clear();
  606. };
  607. /**
  608. * Draw an ellipse.
  609. *
  610. * @param {number} cx Center X coordinate.
  611. * @param {number} cy Center Y coordinate.
  612. * @param {number} rx Radius length for the x-axis.
  613. * @param {number} ry Radius length for the y-axis.
  614. * @param {goog.graphics.Stroke?} stroke Stroke object describing the
  615. * stroke.
  616. * @param {goog.graphics.Fill?} fill Fill object describing the fill.
  617. * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
  618. * to append to. If not specified, appends to the main canvas.
  619. *
  620. * @return {!goog.graphics.EllipseElement} The newly created element.
  621. * @override
  622. */
  623. goog.graphics.VmlGraphics.prototype.drawEllipse = function(
  624. cx, cy, rx, ry, stroke, fill, opt_group) {
  625. var element = this.createVmlElement('oval');
  626. goog.graphics.VmlGraphics.setPositionAndSize(
  627. element, cx - rx, cy - ry, rx * 2, ry * 2);
  628. var wrapper = new goog.graphics.VmlEllipseElement(
  629. element, this, cx, cy, rx, ry, stroke, fill);
  630. this.append_(wrapper, opt_group);
  631. return wrapper;
  632. };
  633. /**
  634. * Draw a rectangle.
  635. *
  636. * @param {number} x X coordinate (left).
  637. * @param {number} y Y coordinate (top).
  638. * @param {number} width Width of rectangle.
  639. * @param {number} height Height of rectangle.
  640. * @param {goog.graphics.Stroke?} stroke Stroke object describing the
  641. * stroke.
  642. * @param {goog.graphics.Fill?} fill Fill object describing the fill.
  643. * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
  644. * to append to. If not specified, appends to the main canvas.
  645. *
  646. * @return {!goog.graphics.RectElement} The newly created element.
  647. * @override
  648. */
  649. goog.graphics.VmlGraphics.prototype.drawRect = function(
  650. x, y, width, height, stroke, fill, opt_group) {
  651. var element = this.createVmlElement('rect');
  652. goog.graphics.VmlGraphics.setPositionAndSize(element, x, y, width, height);
  653. var wrapper = new goog.graphics.VmlRectElement(element, this, stroke, fill);
  654. this.append_(wrapper, opt_group);
  655. return wrapper;
  656. };
  657. /**
  658. * Draw an image.
  659. *
  660. * @param {number} x X coordinate (left).
  661. * @param {number} y Y coordinate (top).
  662. * @param {number} width Width of image.
  663. * @param {number} height Height of image.
  664. * @param {string} src Source of the image.
  665. * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
  666. * to append to. If not specified, appends to the main canvas.
  667. *
  668. * @return {!goog.graphics.ImageElement} The newly created element.
  669. */
  670. goog.graphics.VmlGraphics.prototype.drawImage = function(
  671. x, y, width, height, src, opt_group) {
  672. var element = this.createVmlElement('image');
  673. goog.graphics.VmlGraphics.setPositionAndSize(element, x, y, width, height);
  674. goog.graphics.VmlGraphics.setAttribute(element, 'src', src);
  675. var wrapper = new goog.graphics.VmlImageElement(element, this);
  676. this.append_(wrapper, opt_group);
  677. return wrapper;
  678. };
  679. /**
  680. * Draw a text string vertically centered on a given line.
  681. *
  682. * @param {string} text The text to draw.
  683. * @param {number} x1 X coordinate of start of line.
  684. * @param {number} y1 Y coordinate of start of line.
  685. * @param {number} x2 X coordinate of end of line.
  686. * @param {number} y2 Y coordinate of end of line.
  687. * @param {?string} align Horizontal alignment: left (default), center, right.
  688. * @param {goog.graphics.Font} font Font describing the font properties.
  689. * @param {goog.graphics.Stroke?} stroke Stroke object describing the stroke.
  690. * @param {goog.graphics.Fill?} fill Fill object describing the fill.
  691. * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
  692. * to append to. If not specified, appends to the main canvas.
  693. *
  694. * @return {!goog.graphics.TextElement} The newly created element.
  695. * @override
  696. */
  697. goog.graphics.VmlGraphics.prototype.drawTextOnLine = function(
  698. text, x1, y1, x2, y2, align, font, stroke, fill, opt_group) {
  699. var shape = this.createFullSizeElement_('shape');
  700. var pathElement = this.createVmlElement('path');
  701. var path = 'M' + goog.graphics.VmlGraphics.toPosCoord(x1) + ',' +
  702. goog.graphics.VmlGraphics.toPosCoord(y1) + 'L' +
  703. goog.graphics.VmlGraphics.toPosCoord(x2) + ',' +
  704. goog.graphics.VmlGraphics.toPosCoord(y2) + 'E';
  705. goog.graphics.VmlGraphics.setAttribute(pathElement, 'v', path);
  706. goog.graphics.VmlGraphics.setAttribute(pathElement, 'textpathok', 'true');
  707. var textPathElement = this.createVmlElement('textpath');
  708. textPathElement.setAttribute('on', 'true');
  709. var style = textPathElement.style;
  710. style.fontSize = font.size * this.getPixelScaleX();
  711. style.fontFamily = font.family;
  712. if (align != null) {
  713. style['v-text-align'] = align;
  714. }
  715. if (font.bold) {
  716. style.fontWeight = 'bold';
  717. }
  718. if (font.italic) {
  719. style.fontStyle = 'italic';
  720. }
  721. goog.graphics.VmlGraphics.setAttribute(textPathElement, 'string', text);
  722. shape.appendChild(pathElement);
  723. shape.appendChild(textPathElement);
  724. var wrapper = new goog.graphics.VmlTextElement(shape, this, stroke, fill);
  725. this.append_(wrapper, opt_group);
  726. return wrapper;
  727. };
  728. /**
  729. * Draw a path.
  730. *
  731. * @param {!goog.graphics.Path} path The path object to draw.
  732. * @param {goog.graphics.Stroke?} stroke Stroke object describing the stroke.
  733. * @param {goog.graphics.Fill?} fill Fill object describing the fill.
  734. * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
  735. * to append to. If not specified, appends to the main canvas.
  736. *
  737. * @return {!goog.graphics.PathElement} The newly created element.
  738. * @override
  739. */
  740. goog.graphics.VmlGraphics.prototype.drawPath = function(
  741. path, stroke, fill, opt_group) {
  742. var element = this.createFullSizeElement_('shape');
  743. goog.graphics.VmlGraphics.setAttribute(
  744. element, 'path', goog.graphics.VmlGraphics.getVmlPath(path));
  745. var wrapper = new goog.graphics.VmlPathElement(element, this, stroke, fill);
  746. this.append_(wrapper, opt_group);
  747. return wrapper;
  748. };
  749. /**
  750. * Returns a string representation of a logical path suitable for use in
  751. * a VML element.
  752. *
  753. * @param {goog.graphics.Path} path The logical path.
  754. * @return {string} The VML path representation.
  755. * @suppress {deprecated} goog.graphics is deprecated.
  756. */
  757. goog.graphics.VmlGraphics.getVmlPath = function(path) {
  758. var list = [];
  759. path.forEachSegment(function(segment, args) {
  760. switch (segment) {
  761. case goog.graphics.Path.Segment.MOVETO:
  762. list.push('m');
  763. Array.prototype.push.apply(
  764. list, goog.array.map(args, goog.graphics.VmlGraphics.toSizeCoord));
  765. break;
  766. case goog.graphics.Path.Segment.LINETO:
  767. list.push('l');
  768. Array.prototype.push.apply(
  769. list, goog.array.map(args, goog.graphics.VmlGraphics.toSizeCoord));
  770. break;
  771. case goog.graphics.Path.Segment.CURVETO:
  772. list.push('c');
  773. Array.prototype.push.apply(
  774. list, goog.array.map(args, goog.graphics.VmlGraphics.toSizeCoord));
  775. break;
  776. case goog.graphics.Path.Segment.CLOSE:
  777. list.push('x');
  778. break;
  779. case goog.graphics.Path.Segment.ARCTO:
  780. var toAngle = args[2] + args[3];
  781. var cx = goog.graphics.VmlGraphics.toSizeCoord(
  782. args[4] - goog.math.angleDx(toAngle, args[0]));
  783. var cy = goog.graphics.VmlGraphics.toSizeCoord(
  784. args[5] - goog.math.angleDy(toAngle, args[1]));
  785. var rx = goog.graphics.VmlGraphics.toSizeCoord(args[0]);
  786. var ry = goog.graphics.VmlGraphics.toSizeCoord(args[1]);
  787. // VML angles are in fd units (see http://www.w3.org/TR/NOTE-VML) and
  788. // are positive counter-clockwise.
  789. var fromAngle = Math.round(args[2] * -65536);
  790. var extent = Math.round(args[3] * -65536);
  791. list.push('ae', cx, cy, rx, ry, fromAngle, extent);
  792. break;
  793. }
  794. });
  795. return list.join(' ');
  796. };
  797. /**
  798. * Create an empty group of drawing elements.
  799. *
  800. * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
  801. * to append to. If not specified, appends to the main canvas.
  802. *
  803. * @return {!goog.graphics.GroupElement} The newly created group.
  804. * @override
  805. */
  806. goog.graphics.VmlGraphics.prototype.createGroup = function(opt_group) {
  807. var element = this.createFullSizeElement_('group');
  808. var parent = opt_group || this.canvasElement;
  809. parent.getElement().appendChild(element);
  810. return new goog.graphics.VmlGroupElement(element, this);
  811. };
  812. /**
  813. * Measure and return the width (in pixels) of a given text string.
  814. * Text measurement is needed to make sure a text can fit in the allocated
  815. * area. The way text length is measured is by writing it into a div that is
  816. * after the visible area, measure the div width, and immediately erase the
  817. * written value.
  818. *
  819. * @param {string} text The text string to measure.
  820. * @param {goog.graphics.Font} font The font object describing the font style.
  821. *
  822. * @return {number} The width in pixels of the text strings.
  823. * @override
  824. */
  825. goog.graphics.VmlGraphics.prototype.getTextWidth = function(text, font) {
  826. // TODO(arv): Implement
  827. return 0;
  828. };
  829. /** @override */
  830. goog.graphics.VmlGraphics.prototype.enterDocument = function() {
  831. goog.graphics.VmlGraphics.superClass_.enterDocument.call(this);
  832. this.handleContainerResize_();
  833. this.updateGraphics_();
  834. };
  835. /**
  836. * Disposes of the component by removing event handlers, detacing DOM nodes from
  837. * the document body, and removing references to them.
  838. * @override
  839. * @protected
  840. */
  841. goog.graphics.VmlGraphics.prototype.disposeInternal = function() {
  842. this.canvasElement = null;
  843. goog.graphics.VmlGraphics.superClass_.disposeInternal.call(this);
  844. };