graphic.js 35 KB

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