graphic.js 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013
  1. var env = require("../core/env");
  2. var _vector = require("../core/vector");
  3. var applyTransform = _vector.applyTransform;
  4. var BoundingRect = require("../core/BoundingRect");
  5. var colorTool = require("../tool/color");
  6. var textContain = require("../contain/text");
  7. var textHelper = require("../graphic/helper/text");
  8. var RectText = require("../graphic/mixin/RectText");
  9. var Displayable = require("../graphic/Displayable");
  10. var ZImage = require("../graphic/Image");
  11. var Text = require("../graphic/Text");
  12. var Path = require("../graphic/Path");
  13. var PathProxy = require("../core/PathProxy");
  14. var Gradient = require("../graphic/Gradient");
  15. var vmlCore = require("./core");
  16. // http://www.w3.org/TR/NOTE-VML
  17. // TODO Use proxy like svg instead of overwrite brush methods
  18. var CMD = PathProxy.CMD;
  19. var round = Math.round;
  20. var sqrt = Math.sqrt;
  21. var abs = Math.abs;
  22. var cos = Math.cos;
  23. var sin = Math.sin;
  24. var mathMax = Math.max;
  25. if (!env.canvasSupported) {
  26. var comma = ',';
  27. var imageTransformPrefix = 'progid:DXImageTransform.Microsoft';
  28. var Z = 21600;
  29. var Z2 = Z / 2;
  30. var ZLEVEL_BASE = 100000;
  31. var Z_BASE = 1000;
  32. var initRootElStyle = function (el) {
  33. el.style.cssText = 'position:absolute;left:0;top:0;width:1px;height:1px;';
  34. el.coordsize = Z + ',' + Z;
  35. el.coordorigin = '0,0';
  36. };
  37. var encodeHtmlAttribute = function (s) {
  38. return String(s).replace(/&/g, '&').replace(/"/g, '"');
  39. };
  40. var rgb2Str = function (r, g, b) {
  41. return 'rgb(' + [r, g, b].join(',') + ')';
  42. };
  43. var append = function (parent, child) {
  44. if (child && parent && child.parentNode !== parent) {
  45. parent.appendChild(child);
  46. }
  47. };
  48. var remove = function (parent, child) {
  49. if (child && parent && child.parentNode === parent) {
  50. parent.removeChild(child);
  51. }
  52. };
  53. var getZIndex = function (zlevel, z, z2) {
  54. // z 的取值范围为 [0, 1000]
  55. return (parseFloat(zlevel) || 0) * ZLEVEL_BASE + (parseFloat(z) || 0) * Z_BASE + z2;
  56. };
  57. var parsePercent = function (value, maxValue) {
  58. if (typeof value === 'string') {
  59. if (value.lastIndexOf('%') >= 0) {
  60. return parseFloat(value) / 100 * maxValue;
  61. }
  62. return parseFloat(value);
  63. }
  64. return value;
  65. };
  66. /***************************************************
  67. * PATH
  68. **************************************************/
  69. var setColorAndOpacity = function (el, color, opacity) {
  70. var colorArr = colorTool.parse(color);
  71. opacity = +opacity;
  72. if (isNaN(opacity)) {
  73. opacity = 1;
  74. }
  75. if (colorArr) {
  76. el.color = rgb2Str(colorArr[0], colorArr[1], colorArr[2]);
  77. el.opacity = opacity * colorArr[3];
  78. }
  79. };
  80. var getColorAndAlpha = function (color) {
  81. var colorArr = colorTool.parse(color);
  82. return [rgb2Str(colorArr[0], colorArr[1], colorArr[2]), colorArr[3]];
  83. };
  84. var updateFillNode = function (el, style, zrEl) {
  85. // TODO pattern
  86. var fill = style.fill;
  87. if (fill != null) {
  88. // Modified from excanvas
  89. if (fill instanceof Gradient) {
  90. var gradientType;
  91. var angle = 0;
  92. var focus = [0, 0]; // additional offset
  93. var shift = 0; // scale factor for offset
  94. var expansion = 1;
  95. var rect = zrEl.getBoundingRect();
  96. var rectWidth = rect.width;
  97. var rectHeight = rect.height;
  98. if (fill.type === 'linear') {
  99. gradientType = 'gradient';
  100. var transform = zrEl.transform;
  101. var p0 = [fill.x * rectWidth, fill.y * rectHeight];
  102. var p1 = [fill.x2 * rectWidth, fill.y2 * rectHeight];
  103. if (transform) {
  104. applyTransform(p0, p0, transform);
  105. applyTransform(p1, p1, transform);
  106. }
  107. var dx = p1[0] - p0[0];
  108. var dy = p1[1] - p0[1];
  109. angle = Math.atan2(dx, dy) * 180 / Math.PI; // The angle should be a non-negative number.
  110. if (angle < 0) {
  111. angle += 360;
  112. } // Very small angles produce an unexpected result because they are
  113. // converted to a scientific notation string.
  114. if (angle < 1e-6) {
  115. angle = 0;
  116. }
  117. } else {
  118. gradientType = 'gradientradial';
  119. var p0 = [fill.x * rectWidth, fill.y * rectHeight];
  120. var transform = zrEl.transform;
  121. var scale = zrEl.scale;
  122. var width = rectWidth;
  123. var height = rectHeight;
  124. focus = [// Percent in bounding rect
  125. (p0[0] - rect.x) / width, (p0[1] - rect.y) / height];
  126. if (transform) {
  127. applyTransform(p0, p0, transform);
  128. }
  129. width /= scale[0] * Z;
  130. height /= scale[1] * Z;
  131. var dimension = mathMax(width, height);
  132. shift = 2 * 0 / dimension;
  133. expansion = 2 * fill.r / dimension - shift;
  134. } // We need to sort the color stops in ascending order by offset,
  135. // otherwise IE won't interpret it correctly.
  136. var stops = fill.colorStops.slice();
  137. stops.sort(function (cs1, cs2) {
  138. return cs1.offset - cs2.offset;
  139. });
  140. var length = stops.length; // Color and alpha list of first and last stop
  141. var colorAndAlphaList = [];
  142. var colors = [];
  143. for (var i = 0; i < length; i++) {
  144. var stop = stops[i];
  145. var colorAndAlpha = getColorAndAlpha(stop.color);
  146. colors.push(stop.offset * expansion + shift + ' ' + colorAndAlpha[0]);
  147. if (i === 0 || i === length - 1) {
  148. colorAndAlphaList.push(colorAndAlpha);
  149. }
  150. }
  151. if (length >= 2) {
  152. var color1 = colorAndAlphaList[0][0];
  153. var color2 = colorAndAlphaList[1][0];
  154. var opacity1 = colorAndAlphaList[0][1] * style.opacity;
  155. var opacity2 = colorAndAlphaList[1][1] * style.opacity;
  156. el.type = gradientType;
  157. el.method = 'none';
  158. el.focus = '100%';
  159. el.angle = angle;
  160. el.color = color1;
  161. el.color2 = color2;
  162. el.colors = colors.join(','); // When colors attribute is used, the meanings of opacity and o:opacity2
  163. // are reversed.
  164. el.opacity = opacity2; // FIXME g_o_:opacity ?
  165. el.opacity2 = opacity1;
  166. }
  167. if (gradientType === 'radial') {
  168. el.focusposition = focus.join(',');
  169. }
  170. } else {
  171. // FIXME Change from Gradient fill to color fill
  172. setColorAndOpacity(el, fill, style.opacity);
  173. }
  174. }
  175. };
  176. var updateStrokeNode = function (el, style) {
  177. // if (style.lineJoin != null) {
  178. // el.joinstyle = style.lineJoin;
  179. // }
  180. // if (style.miterLimit != null) {
  181. // el.miterlimit = style.miterLimit * Z;
  182. // }
  183. // if (style.lineCap != null) {
  184. // el.endcap = style.lineCap;
  185. // }
  186. if (style.lineDash != null) {
  187. el.dashstyle = style.lineDash.join(' ');
  188. }
  189. if (style.stroke != null && !(style.stroke instanceof Gradient)) {
  190. setColorAndOpacity(el, style.stroke, style.opacity);
  191. }
  192. };
  193. var updateFillAndStroke = function (vmlEl, type, style, zrEl) {
  194. var isFill = type == 'fill';
  195. var el = vmlEl.getElementsByTagName(type)[0]; // Stroke must have lineWidth
  196. if (style[type] != null && style[type] !== 'none' && (isFill || !isFill && style.lineWidth)) {
  197. vmlEl[isFill ? 'filled' : 'stroked'] = 'true'; // FIXME Remove before updating, or set `colors` will throw error
  198. if (style[type] instanceof Gradient) {
  199. remove(vmlEl, el);
  200. }
  201. if (!el) {
  202. el = vmlCore.createNode(type);
  203. }
  204. isFill ? updateFillNode(el, style, zrEl) : updateStrokeNode(el, style);
  205. append(vmlEl, el);
  206. } else {
  207. vmlEl[isFill ? 'filled' : 'stroked'] = 'false';
  208. remove(vmlEl, el);
  209. }
  210. };
  211. var points = [[], [], []];
  212. var pathDataToString = function (path, m) {
  213. var M = CMD.M;
  214. var C = CMD.C;
  215. var L = CMD.L;
  216. var A = CMD.A;
  217. var Q = CMD.Q;
  218. var str = [];
  219. var nPoint;
  220. var cmdStr;
  221. var cmd;
  222. var i;
  223. var xi;
  224. var yi;
  225. var data = path.data;
  226. var dataLength = path.len();
  227. for (i = 0; i < dataLength;) {
  228. cmd = data[i++];
  229. cmdStr = '';
  230. nPoint = 0;
  231. switch (cmd) {
  232. case M:
  233. cmdStr = ' m ';
  234. nPoint = 1;
  235. xi = data[i++];
  236. yi = data[i++];
  237. points[0][0] = xi;
  238. points[0][1] = yi;
  239. break;
  240. case L:
  241. cmdStr = ' l ';
  242. nPoint = 1;
  243. xi = data[i++];
  244. yi = data[i++];
  245. points[0][0] = xi;
  246. points[0][1] = yi;
  247. break;
  248. case Q:
  249. case C:
  250. cmdStr = ' c ';
  251. nPoint = 3;
  252. var x1 = data[i++];
  253. var y1 = data[i++];
  254. var x2 = data[i++];
  255. var y2 = data[i++];
  256. var x3;
  257. var y3;
  258. if (cmd === Q) {
  259. // Convert quadratic to cubic using degree elevation
  260. x3 = x2;
  261. y3 = y2;
  262. x2 = (x2 + 2 * x1) / 3;
  263. y2 = (y2 + 2 * y1) / 3;
  264. x1 = (xi + 2 * x1) / 3;
  265. y1 = (yi + 2 * y1) / 3;
  266. } else {
  267. x3 = data[i++];
  268. y3 = data[i++];
  269. }
  270. points[0][0] = x1;
  271. points[0][1] = y1;
  272. points[1][0] = x2;
  273. points[1][1] = y2;
  274. points[2][0] = x3;
  275. points[2][1] = y3;
  276. xi = x3;
  277. yi = y3;
  278. break;
  279. case A:
  280. var x = 0;
  281. var y = 0;
  282. var sx = 1;
  283. var sy = 1;
  284. var angle = 0;
  285. if (m) {
  286. // Extract SRT from matrix
  287. x = m[4];
  288. y = m[5];
  289. sx = sqrt(m[0] * m[0] + m[1] * m[1]);
  290. sy = sqrt(m[2] * m[2] + m[3] * m[3]);
  291. angle = Math.atan2(-m[1] / sy, m[0] / sx);
  292. }
  293. var cx = data[i++];
  294. var cy = data[i++];
  295. var rx = data[i++];
  296. var ry = data[i++];
  297. var startAngle = data[i++] + angle;
  298. var endAngle = data[i++] + startAngle + angle; // FIXME
  299. // var psi = data[i++];
  300. i++;
  301. var clockwise = data[i++];
  302. var x0 = cx + cos(startAngle) * rx;
  303. var y0 = cy + sin(startAngle) * ry;
  304. var x1 = cx + cos(endAngle) * rx;
  305. var y1 = cy + sin(endAngle) * ry;
  306. var type = clockwise ? ' wa ' : ' at ';
  307. if (Math.abs(x0 - x1) < 1e-4) {
  308. // IE won't render arches drawn counter clockwise if x0 == x1.
  309. if (Math.abs(endAngle - startAngle) > 1e-2) {
  310. // Offset x0 by 1/80 of a pixel. Use something
  311. // that can be represented in binary
  312. if (clockwise) {
  313. x0 += 270 / Z;
  314. }
  315. } else {
  316. // Avoid case draw full circle
  317. if (Math.abs(y0 - cy) < 1e-4) {
  318. if (clockwise && x0 < cx || !clockwise && x0 > cx) {
  319. y1 -= 270 / Z;
  320. } else {
  321. y1 += 270 / Z;
  322. }
  323. } else if (clockwise && y0 < cy || !clockwise && y0 > cy) {
  324. x1 += 270 / Z;
  325. } else {
  326. x1 -= 270 / Z;
  327. }
  328. }
  329. }
  330. str.push(type, round(((cx - rx) * sx + x) * Z - Z2), comma, round(((cy - ry) * sy + y) * Z - Z2), comma, round(((cx + rx) * sx + x) * Z - Z2), comma, round(((cy + ry) * sy + y) * Z - Z2), comma, round((x0 * sx + x) * Z - Z2), comma, round((y0 * sy + y) * Z - Z2), comma, round((x1 * sx + x) * Z - Z2), comma, round((y1 * sy + y) * Z - Z2));
  331. xi = x1;
  332. yi = y1;
  333. break;
  334. case CMD.R:
  335. var p0 = points[0];
  336. var p1 = points[1]; // x0, y0
  337. p0[0] = data[i++];
  338. p0[1] = data[i++]; // x1, y1
  339. p1[0] = p0[0] + data[i++];
  340. p1[1] = p0[1] + data[i++];
  341. if (m) {
  342. applyTransform(p0, p0, m);
  343. applyTransform(p1, p1, m);
  344. }
  345. p0[0] = round(p0[0] * Z - Z2);
  346. p1[0] = round(p1[0] * Z - Z2);
  347. p0[1] = round(p0[1] * Z - Z2);
  348. p1[1] = round(p1[1] * Z - Z2);
  349. str.push( // x0, y0
  350. ' m ', p0[0], comma, p0[1], // x1, y0
  351. ' l ', p1[0], comma, p0[1], // x1, y1
  352. ' l ', p1[0], comma, p1[1], // x0, y1
  353. ' l ', p0[0], comma, p1[1]);
  354. break;
  355. case CMD.Z:
  356. // FIXME Update xi, yi
  357. str.push(' x ');
  358. }
  359. if (nPoint > 0) {
  360. str.push(cmdStr);
  361. for (var k = 0; k < nPoint; k++) {
  362. var p = points[k];
  363. m && applyTransform(p, p, m); // 不 round 会非常慢
  364. str.push(round(p[0] * Z - Z2), comma, round(p[1] * Z - Z2), k < nPoint - 1 ? comma : '');
  365. }
  366. }
  367. }
  368. return str.join('');
  369. }; // Rewrite the original path method
  370. Path.prototype.brushVML = function (vmlRoot) {
  371. var style = this.style;
  372. var vmlEl = this._vmlEl;
  373. if (!vmlEl) {
  374. vmlEl = vmlCore.createNode('shape');
  375. initRootElStyle(vmlEl);
  376. this._vmlEl = vmlEl;
  377. }
  378. updateFillAndStroke(vmlEl, 'fill', style, this);
  379. updateFillAndStroke(vmlEl, 'stroke', style, this);
  380. var m = this.transform;
  381. var needTransform = m != null;
  382. var strokeEl = vmlEl.getElementsByTagName('stroke')[0];
  383. if (strokeEl) {
  384. var lineWidth = style.lineWidth; // Get the line scale.
  385. // Determinant of this.m_ means how much the area is enlarged by the
  386. // transformation. So its square root can be used as a scale factor
  387. // for width.
  388. if (needTransform && !style.strokeNoScale) {
  389. var det = m[0] * m[3] - m[1] * m[2];
  390. lineWidth *= sqrt(abs(det));
  391. }
  392. strokeEl.weight = lineWidth + 'px';
  393. }
  394. var path = this.path || (this.path = new PathProxy());
  395. if (this.__dirtyPath) {
  396. path.beginPath();
  397. this.buildPath(path, this.shape);
  398. path.toStatic();
  399. this.__dirtyPath = false;
  400. }
  401. vmlEl.path = pathDataToString(path, this.transform);
  402. vmlEl.style.zIndex = getZIndex(this.zlevel, this.z, this.z2); // Append to root
  403. append(vmlRoot, vmlEl); // Text
  404. if (style.text != null) {
  405. this.drawRectText(vmlRoot, this.getBoundingRect());
  406. } else {
  407. this.removeRectText(vmlRoot);
  408. }
  409. };
  410. Path.prototype.onRemove = function (vmlRoot) {
  411. remove(vmlRoot, this._vmlEl);
  412. this.removeRectText(vmlRoot);
  413. };
  414. Path.prototype.onAdd = function (vmlRoot) {
  415. append(vmlRoot, this._vmlEl);
  416. this.appendRectText(vmlRoot);
  417. };
  418. /***************************************************
  419. * IMAGE
  420. **************************************************/
  421. var isImage = function (img) {
  422. // FIXME img instanceof Image 如果 img 是一个字符串的时候,IE8 下会报错
  423. return typeof img === 'object' && img.tagName && img.tagName.toUpperCase() === 'IMG'; // return img instanceof Image;
  424. }; // Rewrite the original path method
  425. ZImage.prototype.brushVML = function (vmlRoot) {
  426. var style = this.style;
  427. var image = style.image; // Image original width, height
  428. var ow;
  429. var oh;
  430. if (isImage(image)) {
  431. var src = image.src;
  432. if (src === this._imageSrc) {
  433. ow = this._imageWidth;
  434. oh = this._imageHeight;
  435. } else {
  436. var imageRuntimeStyle = image.runtimeStyle;
  437. var oldRuntimeWidth = imageRuntimeStyle.width;
  438. var oldRuntimeHeight = imageRuntimeStyle.height;
  439. imageRuntimeStyle.width = 'auto';
  440. imageRuntimeStyle.height = 'auto'; // get the original size
  441. ow = image.width;
  442. oh = image.height; // and remove overides
  443. imageRuntimeStyle.width = oldRuntimeWidth;
  444. imageRuntimeStyle.height = oldRuntimeHeight; // Caching image original width, height and src
  445. this._imageSrc = src;
  446. this._imageWidth = ow;
  447. this._imageHeight = oh;
  448. }
  449. image = src;
  450. } else {
  451. if (image === this._imageSrc) {
  452. ow = this._imageWidth;
  453. oh = this._imageHeight;
  454. }
  455. }
  456. if (!image) {
  457. return;
  458. }
  459. var x = style.x || 0;
  460. var y = style.y || 0;
  461. var dw = style.width;
  462. var dh = style.height;
  463. var sw = style.sWidth;
  464. var sh = style.sHeight;
  465. var sx = style.sx || 0;
  466. var sy = style.sy || 0;
  467. var hasCrop = sw && sh;
  468. var vmlEl = this._vmlEl;
  469. if (!vmlEl) {
  470. // FIXME 使用 group 在 left, top 都不是 0 的时候就无法显示了。
  471. // vmlEl = vmlCore.createNode('group');
  472. vmlEl = vmlCore.doc.createElement('div');
  473. initRootElStyle(vmlEl);
  474. this._vmlEl = vmlEl;
  475. }
  476. var vmlElStyle = vmlEl.style;
  477. var hasRotation = false;
  478. var m;
  479. var scaleX = 1;
  480. var scaleY = 1;
  481. if (this.transform) {
  482. m = this.transform;
  483. scaleX = sqrt(m[0] * m[0] + m[1] * m[1]);
  484. scaleY = sqrt(m[2] * m[2] + m[3] * m[3]);
  485. hasRotation = m[1] || m[2];
  486. }
  487. if (hasRotation) {
  488. // If filters are necessary (rotation exists), create them
  489. // filters are bog-slow, so only create them if abbsolutely necessary
  490. // The following check doesn't account for skews (which don't exist
  491. // in the canvas spec (yet) anyway.
  492. // From excanvas
  493. var p0 = [x, y];
  494. var p1 = [x + dw, y];
  495. var p2 = [x, y + dh];
  496. var p3 = [x + dw, y + dh];
  497. applyTransform(p0, p0, m);
  498. applyTransform(p1, p1, m);
  499. applyTransform(p2, p2, m);
  500. applyTransform(p3, p3, m);
  501. var maxX = mathMax(p0[0], p1[0], p2[0], p3[0]);
  502. var maxY = mathMax(p0[1], p1[1], p2[1], p3[1]);
  503. var transformFilter = [];
  504. transformFilter.push('M11=', m[0] / scaleX, comma, 'M12=', m[2] / scaleY, comma, 'M21=', m[1] / scaleX, comma, 'M22=', m[3] / scaleY, comma, 'Dx=', round(x * scaleX + m[4]), comma, 'Dy=', round(y * scaleY + m[5]));
  505. vmlElStyle.padding = '0 ' + round(maxX) + 'px ' + round(maxY) + 'px 0'; // FIXME DXImageTransform 在 IE11 的兼容模式下不起作用
  506. vmlElStyle.filter = imageTransformPrefix + '.Matrix(' + transformFilter.join('') + ', SizingMethod=clip)';
  507. } else {
  508. if (m) {
  509. x = x * scaleX + m[4];
  510. y = y * scaleY + m[5];
  511. }
  512. vmlElStyle.filter = '';
  513. vmlElStyle.left = round(x) + 'px';
  514. vmlElStyle.top = round(y) + 'px';
  515. }
  516. var imageEl = this._imageEl;
  517. var cropEl = this._cropEl;
  518. if (!imageEl) {
  519. imageEl = vmlCore.doc.createElement('div');
  520. this._imageEl = imageEl;
  521. }
  522. var imageELStyle = imageEl.style;
  523. if (hasCrop) {
  524. // Needs know image original width and height
  525. if (!(ow && oh)) {
  526. var tmpImage = new Image();
  527. var self = this;
  528. tmpImage.onload = function () {
  529. tmpImage.onload = null;
  530. ow = tmpImage.width;
  531. oh = tmpImage.height; // Adjust image width and height to fit the ratio destinationSize / sourceSize
  532. imageELStyle.width = round(scaleX * ow * dw / sw) + 'px';
  533. imageELStyle.height = round(scaleY * oh * dh / sh) + 'px'; // Caching image original width, height and src
  534. self._imageWidth = ow;
  535. self._imageHeight = oh;
  536. self._imageSrc = image;
  537. };
  538. tmpImage.src = image;
  539. } else {
  540. imageELStyle.width = round(scaleX * ow * dw / sw) + 'px';
  541. imageELStyle.height = round(scaleY * oh * dh / sh) + 'px';
  542. }
  543. if (!cropEl) {
  544. cropEl = vmlCore.doc.createElement('div');
  545. cropEl.style.overflow = 'hidden';
  546. this._cropEl = cropEl;
  547. }
  548. var cropElStyle = cropEl.style;
  549. cropElStyle.width = round((dw + sx * dw / sw) * scaleX);
  550. cropElStyle.height = round((dh + sy * dh / sh) * scaleY);
  551. cropElStyle.filter = imageTransformPrefix + '.Matrix(Dx=' + -sx * dw / sw * scaleX + ',Dy=' + -sy * dh / sh * scaleY + ')';
  552. if (!cropEl.parentNode) {
  553. vmlEl.appendChild(cropEl);
  554. }
  555. if (imageEl.parentNode != cropEl) {
  556. cropEl.appendChild(imageEl);
  557. }
  558. } else {
  559. imageELStyle.width = round(scaleX * dw) + 'px';
  560. imageELStyle.height = round(scaleY * dh) + 'px';
  561. vmlEl.appendChild(imageEl);
  562. if (cropEl && cropEl.parentNode) {
  563. vmlEl.removeChild(cropEl);
  564. this._cropEl = null;
  565. }
  566. }
  567. var filterStr = '';
  568. var alpha = style.opacity;
  569. if (alpha < 1) {
  570. filterStr += '.Alpha(opacity=' + round(alpha * 100) + ') ';
  571. }
  572. filterStr += imageTransformPrefix + '.AlphaImageLoader(src=' + image + ', SizingMethod=scale)';
  573. imageELStyle.filter = filterStr;
  574. vmlEl.style.zIndex = getZIndex(this.zlevel, this.z, this.z2); // Append to root
  575. append(vmlRoot, vmlEl); // Text
  576. if (style.text != null) {
  577. this.drawRectText(vmlRoot, this.getBoundingRect());
  578. }
  579. };
  580. ZImage.prototype.onRemove = function (vmlRoot) {
  581. remove(vmlRoot, this._vmlEl);
  582. this._vmlEl = null;
  583. this._cropEl = null;
  584. this._imageEl = null;
  585. this.removeRectText(vmlRoot);
  586. };
  587. ZImage.prototype.onAdd = function (vmlRoot) {
  588. append(vmlRoot, this._vmlEl);
  589. this.appendRectText(vmlRoot);
  590. };
  591. /***************************************************
  592. * TEXT
  593. **************************************************/
  594. var DEFAULT_STYLE_NORMAL = 'normal';
  595. var fontStyleCache = {};
  596. var fontStyleCacheCount = 0;
  597. var MAX_FONT_CACHE_SIZE = 100;
  598. var fontEl = document.createElement('div');
  599. var getFontStyle = function (fontString) {
  600. var fontStyle = fontStyleCache[fontString];
  601. if (!fontStyle) {
  602. // Clear cache
  603. if (fontStyleCacheCount > MAX_FONT_CACHE_SIZE) {
  604. fontStyleCacheCount = 0;
  605. fontStyleCache = {};
  606. }
  607. var style = fontEl.style;
  608. var fontFamily;
  609. try {
  610. style.font = fontString;
  611. fontFamily = style.fontFamily.split(',')[0];
  612. } catch (e) {}
  613. fontStyle = {
  614. style: style.fontStyle || DEFAULT_STYLE_NORMAL,
  615. variant: style.fontVariant || DEFAULT_STYLE_NORMAL,
  616. weight: style.fontWeight || DEFAULT_STYLE_NORMAL,
  617. size: parseFloat(style.fontSize || 12) | 0,
  618. family: fontFamily || 'Microsoft YaHei'
  619. };
  620. fontStyleCache[fontString] = fontStyle;
  621. fontStyleCacheCount++;
  622. }
  623. return fontStyle;
  624. };
  625. var textMeasureEl; // Overwrite measure text method
  626. textContain.$override('measureText', function (text, textFont) {
  627. var doc = vmlCore.doc;
  628. if (!textMeasureEl) {
  629. textMeasureEl = doc.createElement('div');
  630. textMeasureEl.style.cssText = 'position:absolute;top:-20000px;left:0;' + 'padding:0;margin:0;border:none;white-space:pre;';
  631. vmlCore.doc.body.appendChild(textMeasureEl);
  632. }
  633. try {
  634. textMeasureEl.style.font = textFont;
  635. } catch (ex) {// Ignore failures to set to invalid font.
  636. }
  637. textMeasureEl.innerHTML = ''; // Don't use innerHTML or innerText because they allow markup/whitespace.
  638. textMeasureEl.appendChild(doc.createTextNode(text));
  639. return {
  640. width: textMeasureEl.offsetWidth
  641. };
  642. });
  643. var tmpRect = new BoundingRect();
  644. var drawRectText = function (vmlRoot, rect, textRect, fromTextEl) {
  645. var style = this.style; // Optimize, avoid normalize every time.
  646. this.__dirty && textHelper.normalizeTextStyle(style, true);
  647. var text = style.text; // Convert to string
  648. text != null && (text += '');
  649. if (!text) {
  650. return;
  651. } // Convert rich text to plain text. Rich text is not supported in
  652. // IE8-, but tags in rich text template will be removed.
  653. if (style.rich) {
  654. var contentBlock = textContain.parseRichText(text, style);
  655. text = [];
  656. for (var i = 0; i < contentBlock.lines.length; i++) {
  657. var tokens = contentBlock.lines[i].tokens;
  658. var textLine = [];
  659. for (var j = 0; j < tokens.length; j++) {
  660. textLine.push(tokens[j].text);
  661. }
  662. text.push(textLine.join(''));
  663. }
  664. text = text.join('\n');
  665. }
  666. var x;
  667. var y;
  668. var align = style.textAlign;
  669. var verticalAlign = style.textVerticalAlign;
  670. var fontStyle = getFontStyle(style.font); // FIXME encodeHtmlAttribute ?
  671. var font = fontStyle.style + ' ' + fontStyle.variant + ' ' + fontStyle.weight + ' ' + fontStyle.size + 'px "' + fontStyle.family + '"';
  672. textRect = textRect || textContain.getBoundingRect(text, font, align, verticalAlign); // Transform rect to view space
  673. var m = this.transform; // Ignore transform for text in other element
  674. if (m && !fromTextEl) {
  675. tmpRect.copy(rect);
  676. tmpRect.applyTransform(m);
  677. rect = tmpRect;
  678. }
  679. if (!fromTextEl) {
  680. var textPosition = style.textPosition;
  681. var distance = style.textDistance; // Text position represented by coord
  682. if (textPosition instanceof Array) {
  683. x = rect.x + parsePercent(textPosition[0], rect.width);
  684. y = rect.y + parsePercent(textPosition[1], rect.height);
  685. align = align || 'left';
  686. } else {
  687. var res = textContain.adjustTextPositionOnRect(textPosition, rect, distance);
  688. x = res.x;
  689. y = res.y; // Default align and baseline when has textPosition
  690. align = align || res.textAlign;
  691. verticalAlign = verticalAlign || res.textVerticalAlign;
  692. }
  693. } else {
  694. x = rect.x;
  695. y = rect.y;
  696. }
  697. x = textContain.adjustTextX(x, textRect.width, align);
  698. y = textContain.adjustTextY(y, textRect.height, verticalAlign); // Force baseline 'middle'
  699. y += textRect.height / 2; // var fontSize = fontStyle.size;
  700. // 1.75 is an arbitrary number, as there is no info about the text baseline
  701. // switch (baseline) {
  702. // case 'hanging':
  703. // case 'top':
  704. // y += fontSize / 1.75;
  705. // break;
  706. // case 'middle':
  707. // break;
  708. // default:
  709. // // case null:
  710. // // case 'alphabetic':
  711. // // case 'ideographic':
  712. // // case 'bottom':
  713. // y -= fontSize / 2.25;
  714. // break;
  715. // }
  716. // switch (align) {
  717. // case 'left':
  718. // break;
  719. // case 'center':
  720. // x -= textRect.width / 2;
  721. // break;
  722. // case 'right':
  723. // x -= textRect.width;
  724. // break;
  725. // case 'end':
  726. // align = elementStyle.direction == 'ltr' ? 'right' : 'left';
  727. // break;
  728. // case 'start':
  729. // align = elementStyle.direction == 'rtl' ? 'right' : 'left';
  730. // break;
  731. // default:
  732. // align = 'left';
  733. // }
  734. var createNode = vmlCore.createNode;
  735. var textVmlEl = this._textVmlEl;
  736. var pathEl;
  737. var textPathEl;
  738. var skewEl;
  739. if (!textVmlEl) {
  740. textVmlEl = createNode('line');
  741. pathEl = createNode('path');
  742. textPathEl = createNode('textpath');
  743. skewEl = createNode('skew'); // FIXME Why here is not cammel case
  744. // Align 'center' seems wrong
  745. textPathEl.style['v-text-align'] = 'left';
  746. initRootElStyle(textVmlEl);
  747. pathEl.textpathok = true;
  748. textPathEl.on = true;
  749. textVmlEl.from = '0 0';
  750. textVmlEl.to = '1000 0.05';
  751. append(textVmlEl, skewEl);
  752. append(textVmlEl, pathEl);
  753. append(textVmlEl, textPathEl);
  754. this._textVmlEl = textVmlEl;
  755. } else {
  756. // 这里是在前面 appendChild 保证顺序的前提下
  757. skewEl = textVmlEl.firstChild;
  758. pathEl = skewEl.nextSibling;
  759. textPathEl = pathEl.nextSibling;
  760. }
  761. var coords = [x, y];
  762. var textVmlElStyle = textVmlEl.style; // Ignore transform for text in other element
  763. if (m && fromTextEl) {
  764. applyTransform(coords, coords, m);
  765. skewEl.on = true;
  766. skewEl.matrix = m[0].toFixed(3) + comma + m[2].toFixed(3) + comma + m[1].toFixed(3) + comma + m[3].toFixed(3) + ',0,0'; // Text position
  767. skewEl.offset = (round(coords[0]) || 0) + ',' + (round(coords[1]) || 0); // Left top point as origin
  768. skewEl.origin = '0 0';
  769. textVmlElStyle.left = '0px';
  770. textVmlElStyle.top = '0px';
  771. } else {
  772. skewEl.on = false;
  773. textVmlElStyle.left = round(x) + 'px';
  774. textVmlElStyle.top = round(y) + 'px';
  775. }
  776. textPathEl.string = encodeHtmlAttribute(text); // TODO
  777. try {
  778. textPathEl.style.font = font;
  779. } // Error font format
  780. catch (e) {}
  781. updateFillAndStroke(textVmlEl, 'fill', {
  782. fill: style.textFill,
  783. opacity: style.opacity
  784. }, this);
  785. updateFillAndStroke(textVmlEl, 'stroke', {
  786. stroke: style.textStroke,
  787. opacity: style.opacity,
  788. lineDash: style.lineDash
  789. }, this);
  790. textVmlEl.style.zIndex = getZIndex(this.zlevel, this.z, this.z2); // Attached to root
  791. append(vmlRoot, textVmlEl);
  792. };
  793. var removeRectText = function (vmlRoot) {
  794. remove(vmlRoot, this._textVmlEl);
  795. this._textVmlEl = null;
  796. };
  797. var appendRectText = function (vmlRoot) {
  798. append(vmlRoot, this._textVmlEl);
  799. };
  800. var list = [RectText, Displayable, ZImage, Path, Text]; // In case Displayable has been mixed in RectText
  801. for (var i = 0; i < list.length; i++) {
  802. var proto = list[i].prototype;
  803. proto.drawRectText = drawRectText;
  804. proto.removeRectText = removeRectText;
  805. proto.appendRectText = appendRectText;
  806. }
  807. Text.prototype.brushVML = function (vmlRoot) {
  808. var style = this.style;
  809. if (style.text != null) {
  810. this.drawRectText(vmlRoot, {
  811. x: style.x || 0,
  812. y: style.y || 0,
  813. width: 0,
  814. height: 0
  815. }, this.getBoundingRect(), true);
  816. } else {
  817. this.removeRectText(vmlRoot);
  818. }
  819. };
  820. Text.prototype.onRemove = function (vmlRoot) {
  821. this.removeRectText(vmlRoot);
  822. };
  823. Text.prototype.onAdd = function (vmlRoot) {
  824. this.appendRectText(vmlRoot);
  825. };
  826. }