graphic.js 29 KB

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