aboutsummaryrefslogtreecommitdiff
path: root/contexts/data/lib/closure-library/closure/goog/graphics/vmlgraphics.js
blob: 113246829ad29944b12e1537b85483ccd4fcb35b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview VmlGraphics sub class that uses VML to draw the graphics.
 * @author arv@google.com (Erik Arvidsson)
 * @author yoah@google.com (Yoah Bar-David)
 */


goog.provide('goog.graphics.VmlGraphics');


goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventType');
goog.require('goog.graphics.AbstractGraphics');
goog.require('goog.graphics.Font');
goog.require('goog.graphics.LinearGradient');
goog.require('goog.graphics.SolidFill');
goog.require('goog.graphics.Stroke');
goog.require('goog.graphics.VmlEllipseElement');
goog.require('goog.graphics.VmlGroupElement');
goog.require('goog.graphics.VmlImageElement');
goog.require('goog.graphics.VmlPathElement');
goog.require('goog.graphics.VmlRectElement');
goog.require('goog.graphics.VmlTextElement');
goog.require('goog.math.Size');
goog.require('goog.string');
goog.require('goog.style');



/**
 * A Graphics implementation for drawing using VML.
 * @param {string|number} width The (non-zero) width in pixels.  Strings
 *     expressing percentages of parent with (e.g. '80%') are also accepted.
 * @param {string|number} height The (non-zero) height in pixels.  Strings
 *     expressing percentages of parent with (e.g. '80%') are also accepted.
 * @param {?number=} opt_coordWidth The coordinate width - if
 *     omitted or null, defaults to same as width.
 * @param {?number=} opt_coordHeight The coordinate height - if
 *     omitted or null, defaults to same as height.
 * @param {goog.dom.DomHelper=} opt_domHelper The DOM helper object for the
 *     document we want to render in.
 * @constructor
 * @extends {goog.graphics.AbstractGraphics}
 */
goog.graphics.VmlGraphics = function(width, height,
                                     opt_coordWidth, opt_coordHeight,
                                     opt_domHelper) {
  goog.graphics.AbstractGraphics.call(this, width, height,
                                      opt_coordWidth, opt_coordHeight,
                                      opt_domHelper);
  this.handler_ = new goog.events.EventHandler(this);
};
goog.inherits(goog.graphics.VmlGraphics, goog.graphics.AbstractGraphics);


/**
 * The prefix to use for VML elements
 * @private
 * @type {string}
 */
goog.graphics.VmlGraphics.VML_PREFIX_ = 'g_vml_';


/**
 * The VML namespace URN
 * @private
 * @type {string}
 */
goog.graphics.VmlGraphics.VML_NS_ = 'urn:schemas-microsoft-com:vml';


/**
 * The VML behavior URL.
 * @private
 * @type {string}
 */
goog.graphics.VmlGraphics.VML_IMPORT_ = '#default#VML';


/**
 * Whether the document is using IE8 standards mode, and therefore needs hacks.
 * @private
 * @type {boolean}
 */
goog.graphics.VmlGraphics.IE8_MODE_ = document.documentMode &&
    document.documentMode >= 8;


/**
 * The coordinate multiplier to allow sub-pixel rendering
 * @type {number}
 */
goog.graphics.VmlGraphics.COORD_MULTIPLIER = 100;


/**
 * Converts the given size to a css size.  If it is a percentage, leaves it
 * alone.  Otherwise assumes px.
 *
 * @param {number|string} size The size to use.
 * @return {string} The position adjusted for COORD_MULTIPLIER.
 */
goog.graphics.VmlGraphics.toCssSize = function(size) {
  return goog.isString(size) && goog.string.endsWith(size, '%') ?
         size : parseFloat(size.toString()) + 'px';
};


/**
 * Multiplies positioning coordinates by COORD_MULTIPLIER to allow sub-pixel
 * coordinates.  Also adds a half pixel offset to match SVG.
 *
 * This function is internal for the VML supporting classes, and
 * should not be used externally.
 *
 * @param {number|string} number A position in pixels.
 * @return {number} The position adjusted for COORD_MULTIPLIER.
 */
goog.graphics.VmlGraphics.toPosCoord = function(number) {
  return Math.round((parseFloat(number.toString()) - 0.5) *
      goog.graphics.VmlGraphics.COORD_MULTIPLIER);
};


/**
 * Add a "px" suffix to a number of pixels, and multiplies all coordinates by
 * COORD_MULTIPLIER to allow sub-pixel coordinates.
 *
 * This function is internal for the VML supporting classes, and
 * should not be used externally.
 *
 * @param {number|string} number A position in pixels.
 * @return {string} The position with suffix 'px'.
 */
goog.graphics.VmlGraphics.toPosPx = function(number) {
  return goog.graphics.VmlGraphics.toPosCoord(number) + 'px';
};


/**
 * Multiplies the width or height coordinate by COORD_MULTIPLIER to allow
 * sub-pixel coordinates.
 *
 * This function is internal for the VML supporting classes, and
 * should not be used externally.
 *
 * @param {string|number} number A size in units.
 * @return {number} The size multiplied by the correct factor.
 */
goog.graphics.VmlGraphics.toSizeCoord = function(number) {
  return Math.round(parseFloat(number.toString()) *
      goog.graphics.VmlGraphics.COORD_MULTIPLIER);
};


/**
 * Add a "px" suffix to a number of pixels, and multiplies all coordinates by
 * COORD_MULTIPLIER to allow sub-pixel coordinates.
 *
 * This function is internal for the VML supporting classes, and
 * should not be used externally.
 *
 * @param {number|string} number A size in pixels.
 * @return {string} The size with suffix 'px'.
 */
goog.graphics.VmlGraphics.toSizePx = function(number) {
  return goog.graphics.VmlGraphics.toSizeCoord(number) + 'px';
};


/**
 * Sets an attribute on the given VML element, in the way best suited to the
 * current version of IE.  Should only be used in the goog.graphics package.
 * @param {Element} element The element to set an attribute
 *     on.
 * @param {string} name The name of the attribute to set.
 * @param {string} value The value to set it to.
 */
goog.graphics.VmlGraphics.setAttribute = function(element, name, value) {
  if (goog.graphics.VmlGraphics.IE8_MODE_) {
    element[name] = value;
  } else {
    element.setAttribute(name, value);
  }
};


/**
 * Event handler.
 * @type {goog.events.EventHandler}
 * @private
 */
goog.graphics.VmlGraphics.prototype.handler_;


/**
 * Creates a VML element. Used internally and by different VML classes.
 * @param {string} tagName The type of element to create.
 * @return {Element} The created element.
 */
goog.graphics.VmlGraphics.prototype.createVmlElement = function(tagName) {
  var element =
      this.dom_.createElement(goog.graphics.VmlGraphics.VML_PREFIX_ + ':' +
                              tagName);
  element.id = goog.string.createUniqueString();
  return element;
};


/**
 * Returns the VML element with the given id that is a child of this graphics
 * object.
 * Should be considered package private, and not used externally.
 * @param {string} id The element id to find.
 * @return {Element} The element with the given id, or null if none is found.
 */
goog.graphics.VmlGraphics.prototype.getVmlElement = function(id) {
  return this.dom_.getElement(id);
};


/**
 * Resets the graphics so they will display properly on IE8.  Noop in older
 * versions.
 * @private
 */
goog.graphics.VmlGraphics.prototype.updateGraphics_ = function() {
  if (goog.graphics.VmlGraphics.IE8_MODE_ && this.isInDocument()) {
    this.getElement().innerHTML = this.getElement().innerHTML;
  }
};


/**
 * Appends an element.
 *
 * @param {goog.graphics.Element} element The element wrapper.
 * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
 *     to append to. If not specified, appends to the main canvas.
 * @private
 */
goog.graphics.VmlGraphics.prototype.append_ = function(element, opt_group) {
  var parent = opt_group || this.canvasElement;
  parent.getElement().appendChild(element.getElement());
  this.updateGraphics_();
};


/**
 * Sets the fill for the given element.
 * @param {goog.graphics.StrokeAndFillElement} element The element wrapper.
 * @param {goog.graphics.Fill?} fill The fill object.
 * @override
 */
goog.graphics.VmlGraphics.prototype.setElementFill = function(element, fill) {
  var vmlElement = element.getElement();
  this.removeFill(vmlElement);
  if (fill instanceof goog.graphics.SolidFill) {
    // NOTE(arv): VML does not understand 'transparent' so hard code support
    // for it.
    if (fill.getColor() == 'transparent') {
      vmlElement.filled = false;
    } else if (fill.getOpacity() != 1) {
      vmlElement.filled = true;
      // Set opacity (number 0-1 is translated to percent)
      var fillNode = this.createVmlElement('fill');
      fillNode.opacity = Math.round(fill.getOpacity() * 100) + '%';
      fillNode.color = fill.getColor();
      vmlElement.appendChild(fillNode);
    } else {
      vmlElement.filled = true;
      vmlElement.fillcolor = fill.getColor();
    }
  } else if (fill instanceof goog.graphics.LinearGradient) {
    vmlElement.filled = true;
    // Add a 'fill' element
    var gradient = this.createVmlElement('fill');
    gradient.color = fill.getColor1();
    gradient.color2 = fill.getColor2();
    if (goog.isNumber(fill.getOpacity1())) {
      gradient.opacity = fill.getOpacity1();
    }
    if (goog.isNumber(fill.getOpacity2())) {
      gradient.opacity2 = fill.getOpacity2();
    }
    var angle = goog.math.angle(fill.getX1(), fill.getY1(),
        fill.getX2(), fill.getY2());
    // Our angles start from 0 to the right, and grow clockwise.
    // MSIE starts from 0 to top, and grows anti-clockwise.
    angle = Math.round(goog.math.standardAngle(270 - angle));
    gradient.angle = angle;
    gradient.type = 'gradient';
    vmlElement.appendChild(gradient);
  } else {
    vmlElement.filled = false;
  }
  this.updateGraphics_();
};


/**
 * Sets the stroke for the given element.
 * @param {goog.graphics.StrokeAndFillElement} element The element wrapper.
 * @param {goog.graphics.Stroke?} stroke The stroke object.
 * @override
 */
goog.graphics.VmlGraphics.prototype.setElementStroke = function(element,
    stroke) {
  var vmlElement = element.getElement();
  if (stroke) {
    vmlElement.stroked = true;

    var width = stroke.getWidth();
    if (goog.isString(width) && width.indexOf('px') == -1) {
      width = parseFloat(width);
    } else {
      width = width * this.getPixelScaleX();
    }

    var strokeElement = vmlElement.getElementsByTagName('stroke')[0];
    if (width < 1) {
      strokeElement = strokeElement || this.createVmlElement('stroke');
      strokeElement.opacity = width;
      strokeElement.weight = '1px';
      strokeElement.color = stroke.getColor();
      vmlElement.appendChild(strokeElement);
    } else {
      if (strokeElement) {
        vmlElement.removeChild(strokeElement);
      }
      vmlElement.strokecolor = stroke.getColor();
      vmlElement.strokeweight = width + 'px';
    }
  } else {
    vmlElement.stroked = false;
  }
  this.updateGraphics_();
};


/**
 * Set the transformation of an element.
 * @param {goog.graphics.Element} element The element wrapper.
 * @param {number} x The x coordinate of the translation transform.
 * @param {number} y The y coordinate of the translation transform.
 * @param {number} angle The angle of the rotation transform.
 * @param {number} centerX The horizontal center of the rotation transform.
 * @param {number} centerY The vertical center of the rotation transform.
 * @override
 */
goog.graphics.VmlGraphics.prototype.setElementTransform = function(element, x,
    y, angle, centerX, centerY) {
  var el = element.getElement();

  el.style.left = goog.graphics.VmlGraphics.toPosPx(x);
  el.style.top = goog.graphics.VmlGraphics.toPosPx(y);
  if (angle || el.rotation) {
    el.rotation = angle;
    el.coordsize = goog.graphics.VmlGraphics.toSizeCoord(centerX * 2) + ' ' +
        goog.graphics.VmlGraphics.toSizeCoord(centerY * 2);
  }
};


/**
 * Removes the fill information from a dom element.
 * @param {Element} element DOM element.
 */
goog.graphics.VmlGraphics.prototype.removeFill = function(element) {
  element.fillcolor = '';
  var v = element.childNodes.length;
  for (var i = 0; i < element.childNodes.length; i++) {
    var child = element.childNodes[i];
    if (child.tagName == 'fill') {
      element.removeChild(child);
    }
  }
};


/**
 * Set top, left, width and height for an element.
 * This function is internal for the VML supporting classes, and
 * should not be used externally.
 *
 * @param {Element} element DOM element.
 * @param {number} left Left ccordinate in pixels.
 * @param {number} top Top ccordinate in pixels.
 * @param {number} width Width in pixels.
 * @param {number} height Height in pixels.
 */
goog.graphics.VmlGraphics.setPositionAndSize = function(
    element, left, top, width, height) {
  var style = element.style;
  style.position = 'absolute';
  style.left = goog.graphics.VmlGraphics.toPosPx(left);
  style.top = goog.graphics.VmlGraphics.toPosPx(top);
  style.width = goog.graphics.VmlGraphics.toSizePx(width);
  style.height = goog.graphics.VmlGraphics.toSizePx(height);

  if (element.tagName == 'shape') {
    element.coordsize = goog.graphics.VmlGraphics.toSizeCoord(width) + ' ' +
                        goog.graphics.VmlGraphics.toSizeCoord(height);
  }
};


/**
 * Creates an element spanning the surface.
 *
 * @param {string} type The type of element to create.
 * @return {Element} The created, positioned, and sized element.
 * @private
 */
goog.graphics.VmlGraphics.prototype.createFullSizeElement_ = function(type) {
  var element = this.createVmlElement(type);
  var size = this.getCoordSize();
  goog.graphics.VmlGraphics.setPositionAndSize(element, 0, 0, size.width,
      size.height);
  return element;
};


/**
 * IE magic - if this "no-op" line is not here, the if statement below will
 * fail intermittently.  The eval is used to prevent the JsCompiler from
 * stripping this piece of code, which it quite reasonably thinks is doing
 * nothing. Put it in try-catch block to prevent "Unspecified Error" when
 * this statement is executed in a defer JS in IE.
 * More info here:
 * http://www.mail-archive.com/users@openlayers.org/msg01838.html
 */
try {
  eval('document.namespaces');
} catch (ex) {}


/**
 * Creates the DOM representation of the graphics area.
 * @override
 */
goog.graphics.VmlGraphics.prototype.createDom = function() {
  var doc = this.dom_.getDocument();

  // Add the namespace.
  if (!doc.namespaces[goog.graphics.VmlGraphics.VML_PREFIX_]) {
    if (goog.graphics.VmlGraphics.IE8_MODE_) {
      doc.namespaces.add(goog.graphics.VmlGraphics.VML_PREFIX_,
                         goog.graphics.VmlGraphics.VML_NS_,
                         goog.graphics.VmlGraphics.VML_IMPORT_);
    } else {
      doc.namespaces.add(goog.graphics.VmlGraphics.VML_PREFIX_,
                         goog.graphics.VmlGraphics.VML_NS_);
    }

    // We assume that we only need to add the CSS if the namespace was not
    // present
    var ss = doc.createStyleSheet();
    ss.cssText = goog.graphics.VmlGraphics.VML_PREFIX_ + '\\:*' +
                 '{behavior:url(#default#VML)}';
  }

  // Outer a DIV with overflow hidden for clipping.
  // All inner elements are absolutly positioned on-top of this div.
  var pixelWidth = this.width;
  var pixelHeight = this.height;
  var divElement = this.dom_.createDom('div', {
    'style': 'overflow:hidden;position:relative;width:' +
        goog.graphics.VmlGraphics.toCssSize(pixelWidth) + ';height:' +
        goog.graphics.VmlGraphics.toCssSize(pixelHeight)
  });

  this.setElementInternal(divElement);

  var group = this.createVmlElement('group');
  var style = group.style;

  style.position = 'absolute';
  style.left = style.top = 0;
  style.width = this.width;
  style.height = this.height;
  if (this.coordWidth) {
    group.coordsize =
        goog.graphics.VmlGraphics.toSizeCoord(this.coordWidth) + ' ' +
        goog.graphics.VmlGraphics.toSizeCoord(
            /** @type {number} */ (this.coordHeight));
  } else {
    group.coordsize = goog.graphics.VmlGraphics.toSizeCoord(pixelWidth) + ' ' +
        goog.graphics.VmlGraphics.toSizeCoord(pixelHeight);
  }

  if (goog.isDef(this.coordLeft)) {
    group.coordorigin = goog.graphics.VmlGraphics.toSizeCoord(this.coordLeft) +
        ' ' + goog.graphics.VmlGraphics.toSizeCoord(this.coordTop);
  } else {
    group.coordorigin = '0 0';
  }
  divElement.appendChild(group);

  this.canvasElement = new goog.graphics.VmlGroupElement(group, this);

  goog.events.listen(divElement, goog.events.EventType.RESIZE, goog.bind(
      this.handleContainerResize_, this));
};


/**
 * Changes the canvas element size to match the container element size.
 * @private
 */
goog.graphics.VmlGraphics.prototype.handleContainerResize_ = function() {
  var size = goog.style.getSize(this.getElement());
  var style = this.canvasElement.getElement().style;

  if (size.width) {
    style.width = size.width + 'px';
    style.height = size.height + 'px';
  } else {
    var current = this.getElement();
    while (current && current.currentStyle &&
        current.currentStyle.display != 'none') {
      current = current.parentNode;
    }
    if (current && current.currentStyle) {
      this.handler_.listen(current, 'propertychange',
          this.handleContainerResize_);
    }
  }

  this.dispatchEvent(goog.events.EventType.RESIZE);
};


/**
 * Handle property changes on hidden ancestors.
 * @param {goog.events.BrowserEvent} e The browser event.
 * @private
 */
goog.graphics.VmlGraphics.prototype.handlePropertyChange_ = function(e) {
  var prop = e.getBrowserEvent().propertyName;
  if (prop == 'display' || prop == 'className') {
    this.handler_.unlisten(/** @type {Element} */(e.target),
        'propertychange', this.handlePropertyChange_);
    this.handleContainerResize_();
  }
};


/**
 * Changes the coordinate system position.
 * @param {number} left The coordinate system left bound.
 * @param {number} top The coordinate system top bound.
 * @override
 */
goog.graphics.VmlGraphics.prototype.setCoordOrigin = function(left, top) {
  this.coordLeft = left;
  this.coordTop = top;

  this.canvasElement.getElement().coordorigin =
      goog.graphics.VmlGraphics.toSizeCoord(this.coordLeft) + ' ' +
      goog.graphics.VmlGraphics.toSizeCoord(this.coordTop);
};


/**
 * Changes the coordinate size.
 * @param {number} coordWidth The coordinate width.
 * @param {number} coordHeight The coordinate height.
 * @override
 */
goog.graphics.VmlGraphics.prototype.setCoordSize = function(coordWidth,
                                                            coordHeight) {
  goog.graphics.VmlGraphics.superClass_.setCoordSize.apply(this, arguments);

  this.canvasElement.getElement().coordsize =
      goog.graphics.VmlGraphics.toSizeCoord(coordWidth) + ' ' +
      goog.graphics.VmlGraphics.toSizeCoord(coordHeight);
};


/**
 * Change the size of the canvas.
 * @param {number} pixelWidth The width in pixels.
 * @param {number} pixelHeight The height in pixels.
 * @override
 */
goog.graphics.VmlGraphics.prototype.setSize = function(pixelWidth,
    pixelHeight) {
  goog.style.setSize(this.getElement(), pixelWidth, pixelHeight);
};


/**
 * @return {goog.math.Size} Returns the number of pixels spanned by the surface.
 * @override
 */
goog.graphics.VmlGraphics.prototype.getPixelSize = function() {
  var el = this.getElement();
  // The following relies on the fact that the size can never be 0.
  return new goog.math.Size(el.style.pixelWidth || el.offsetWidth || 1,
      el.style.pixelHeight || el.offsetHeight || 1);
};


/**
 * Remove all drawing elements from the graphics.
 * @override
 */
goog.graphics.VmlGraphics.prototype.clear = function() {
  this.canvasElement.clear();
};


/**
 * Draw an ellipse.
 *
 * @param {number} cx Center X coordinate.
 * @param {number} cy Center Y coordinate.
 * @param {number} rx Radius length for the x-axis.
 * @param {number} ry Radius length for the y-axis.
 * @param {goog.graphics.Stroke?} stroke Stroke object describing the
 *    stroke.
 * @param {goog.graphics.Fill?} fill Fill object describing the fill.
 * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
 *     to append to. If not specified, appends to the main canvas.
 *
 * @return {goog.graphics.EllipseElement} The newly created element.
 * @override
 */
goog.graphics.VmlGraphics.prototype.drawEllipse = function(cx, cy, rx, ry,
    stroke, fill, opt_group) {
  var element = this.createVmlElement('oval');
  goog.graphics.VmlGraphics.setPositionAndSize(element, cx - rx, cy - ry,
      rx * 2, ry * 2);
  var wrapper = new goog.graphics.VmlEllipseElement(element, this,
      cx, cy, rx, ry, stroke, fill);
  this.append_(wrapper, opt_group);
  return wrapper;
};


/**
 * Draw a rectangle.
 *
 * @param {number} x X coordinate (left).
 * @param {number} y Y coordinate (top).
 * @param {number} width Width of rectangle.
 * @param {number} height Height of rectangle.
 * @param {goog.graphics.Stroke?} stroke Stroke object describing the
 *    stroke.
 * @param {goog.graphics.Fill?} fill Fill object describing the fill.
 * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
 *     to append to. If not specified, appends to the main canvas.
 *
 * @return {goog.graphics.RectElement} The newly created element.
 * @override
 */
goog.graphics.VmlGraphics.prototype.drawRect = function(x, y, width, height,
    stroke, fill, opt_group) {
  var element = this.createVmlElement('rect');
  goog.graphics.VmlGraphics.setPositionAndSize(element, x, y, width, height);
  var wrapper = new goog.graphics.VmlRectElement(element, this, stroke, fill);
  this.append_(wrapper, opt_group);
  return wrapper;
};


/**
 * Draw an image.
 *
 * @param {number} x X coordinate (left).
 * @param {number} y Y coordinate (top).
 * @param {number} width Width of image.
 * @param {number} height Height of image.
 * @param {string} src Source of the image.
 * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
 *     to append to. If not specified, appends to the main canvas.
 *
 * @return {goog.graphics.ImageElement} The newly created element.
 */
goog.graphics.VmlGraphics.prototype.drawImage = function(x, y, width, height,
    src, opt_group) {
  var element = this.createVmlElement('image');
  goog.graphics.VmlGraphics.setPositionAndSize(element, x, y, width, height);
  goog.graphics.VmlGraphics.setAttribute(element, 'src', src);
  var wrapper = new goog.graphics.VmlImageElement(element, this);
  this.append_(wrapper, opt_group);
  return wrapper;
};


/**
 * Draw a text string vertically centered on a given line.
 *
 * @param {string} text The text to draw.
 * @param {number} x1 X coordinate of start of line.
 * @param {number} y1 Y coordinate of start of line.
 * @param {number} x2 X coordinate of end of line.
 * @param {number} y2 Y coordinate of end of line.
 * @param {?string} align Horizontal alignment: left (default), center, right.
 * @param {goog.graphics.Font} font Font describing the font properties.
 * @param {goog.graphics.Stroke?} stroke Stroke object describing the stroke.
 * @param {goog.graphics.Fill?} fill Fill object describing the fill.
 * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
 *     to append to. If not specified, appends to the main canvas.
 *
 * @return {goog.graphics.TextElement} The newly created element.
 * @override
 */
goog.graphics.VmlGraphics.prototype.drawTextOnLine = function(
    text, x1, y1, x2, y2, align, font, stroke, fill, opt_group) {
  var shape = this.createFullSizeElement_('shape');

  var pathElement = this.createVmlElement('path');
  var path = 'M' + goog.graphics.VmlGraphics.toPosCoord(x1) + ',' +
             goog.graphics.VmlGraphics.toPosCoord(y1) + 'L' +
             goog.graphics.VmlGraphics.toPosCoord(x2) + ',' +
             goog.graphics.VmlGraphics.toPosCoord(y2) + 'E';
  goog.graphics.VmlGraphics.setAttribute(pathElement, 'v', path);
  goog.graphics.VmlGraphics.setAttribute(pathElement, 'textpathok', 'true');

  var textPathElement = this.createVmlElement('textpath');
  textPathElement.setAttribute('on', 'true');
  var style = textPathElement.style;
  style.fontSize = font.size * this.getPixelScaleX();
  style.fontFamily = font.family;
  if (align != null) {
    style['v-text-align'] = align;
  }
  if (font.bold) {
    style.fontWeight = 'bold';
  }
  if (font.italic) {
    style.fontStyle = 'italic';
  }
  goog.graphics.VmlGraphics.setAttribute(textPathElement, 'string', text);

  shape.appendChild(pathElement);
  shape.appendChild(textPathElement);
  var wrapper = new goog.graphics.VmlTextElement(shape, this, stroke, fill);
  this.append_(wrapper, opt_group);
  return wrapper;
};


/**
 * Draw a path.
 *
 * @param {!goog.graphics.Path} path The path object to draw.
 * @param {goog.graphics.Stroke?} stroke Stroke object describing the stroke.
 * @param {goog.graphics.Fill?} fill Fill object describing the fill.
 * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
 *     to append to. If not specified, appends to the main canvas.
 *
 * @return {goog.graphics.PathElement} The newly created element.
 * @override
 */
goog.graphics.VmlGraphics.prototype.drawPath = function(path, stroke, fill,
    opt_group) {
  var element = this.createFullSizeElement_('shape');
  goog.graphics.VmlGraphics.setAttribute(element, 'path',
      goog.graphics.VmlGraphics.getVmlPath(path));

  var wrapper = new goog.graphics.VmlPathElement(element, this, stroke, fill);
  this.append_(wrapper, opt_group);
  return wrapper;
};


/**
 * Returns a string representation of a logical path suitable for use in
 * a VML element.
 *
 * @param {goog.graphics.Path} path The logical path.
 * @return {string} The VML path representation.
 */
goog.graphics.VmlGraphics.getVmlPath = function(path) {
  var list = [];
  path.forEachSegment(function(segment, args) {
    switch (segment) {
      case goog.graphics.Path.Segment.MOVETO:
        list.push('m');
        Array.prototype.push.apply(list, goog.array.map(args,
            goog.graphics.VmlGraphics.toSizeCoord));
        break;
      case goog.graphics.Path.Segment.LINETO:
        list.push('l');
        Array.prototype.push.apply(list, goog.array.map(args,
            goog.graphics.VmlGraphics.toSizeCoord));
        break;
      case goog.graphics.Path.Segment.CURVETO:
        list.push('c');
        Array.prototype.push.apply(list, goog.array.map(args,
            goog.graphics.VmlGraphics.toSizeCoord));
        break;
      case goog.graphics.Path.Segment.CLOSE:
        list.push('x');
        break;
      case goog.graphics.Path.Segment.ARCTO:
        var toAngle = args[2] + args[3];
        var cx = goog.graphics.VmlGraphics.toSizeCoord(
            args[4] - goog.math.angleDx(toAngle, args[0]));
        var cy = goog.graphics.VmlGraphics.toSizeCoord(
            args[5] - goog.math.angleDy(toAngle, args[1]));
        var rx = goog.graphics.VmlGraphics.toSizeCoord(args[0]);
        var ry = goog.graphics.VmlGraphics.toSizeCoord(args[1]);
        // VML angles are in fd units (see http://www.w3.org/TR/NOTE-VML) and
        // are positive counter-clockwise.
        var fromAngle = Math.round(args[2] * -65536);
        var extent = Math.round(args[3] * -65536);
        list.push('ae', cx, cy, rx, ry, fromAngle, extent);
        break;
    }
  });
  return list.join(' ');
};


/**
 * Create an empty group of drawing elements.
 *
 * @param {goog.graphics.GroupElement=} opt_group The group wrapper element
 *     to append to. If not specified, appends to the main canvas.
 *
 * @return {goog.graphics.GroupElement} The newly created group.
 * @override
 */
goog.graphics.VmlGraphics.prototype.createGroup = function(opt_group) {
  var element = this.createFullSizeElement_('group');
  var parent = opt_group || this.canvasElement;
  parent.getElement().appendChild(element);
  return new goog.graphics.VmlGroupElement(element, this);
};


/**
 * Measure and return the width (in pixels) of a given text string.
 * Text measurement is needed to make sure a text can fit in the allocated
 * area. The way text length is measured is by writing it into a div that is
 * after the visible area, measure the div width, and immediatly erase the
 * written value.
 *
 * @param {string} text The text string to measure.
 * @param {goog.graphics.Font} font The font object describing the font style.
 *
 * @return {number} The width in pixels of the text strings.
 * @override
 */
goog.graphics.VmlGraphics.prototype.getTextWidth = function(text, font) {
  // TODO(arv): Implement
  return 0;
};


/** @override */
goog.graphics.VmlGraphics.prototype.enterDocument = function() {
  goog.graphics.VmlGraphics.superClass_.enterDocument.call(this);
  this.handleContainerResize_();
  this.updateGraphics_();
};


/**
 * Disposes of the component by removing event handlers, detacing DOM nodes from
 * the document body, and removing references to them.
 * @override
 * @protected
 */
goog.graphics.VmlGraphics.prototype.disposeInternal = function() {
  this.canvasElement = null;
  goog.graphics.VmlGraphics.superClass_.disposeInternal.call(this);
};