index.js 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533
  1. module.exports = (function() {
  2. var __MODS__ = {};
  3. var __DEFINE__ = function(modId, func, req) { var m = { exports: {}, _tempexports: {} }; __MODS__[modId] = { status: 0, func: func, req: req, m: m }; };
  4. var __REQUIRE__ = function(modId, source) { if(!__MODS__[modId]) return require(source); if(!__MODS__[modId].status) { var m = __MODS__[modId].m; m._exports = m._tempexports; var desp = Object.getOwnPropertyDescriptor(m, "exports"); if (desp && desp.configurable) Object.defineProperty(m, "exports", { set: function (val) { if(typeof val === "object" && val !== m._exports) { m._exports.__proto__ = val.__proto__; Object.keys(val).forEach(function (k) { m._exports[k] = val[k]; }); } m._tempexports = val }, get: function () { return m._tempexports; } }); __MODS__[modId].status = 1; __MODS__[modId].func(__MODS__[modId].req, m, m.exports); } return __MODS__[modId].m.exports; };
  5. var __REQUIRE_WILDCARD__ = function(obj) { if(obj && obj.__esModule) { return obj; } else { var newObj = {}; if(obj != null) { for(var k in obj) { if (Object.prototype.hasOwnProperty.call(obj, k)) newObj[k] = obj[k]; } } newObj.default = obj; return newObj; } };
  6. var __REQUIRE_DEFAULT__ = function(obj) { return obj && obj.__esModule ? obj.default : obj; };
  7. __DEFINE__(1687671528395, function(require, module, exports) {
  8. var __TEMP__ = require('./src/index');var Wxml2Canvas = __REQUIRE_DEFAULT__(__TEMP__);
  9. if (!exports.__esModule) Object.defineProperty(exports, "__esModule", { value: true });exports.default = Wxml2Canvas;
  10. }, function(modId) {var map = {"./src/index":1687671528396}; return __REQUIRE__(map[modId], modId); })
  11. __DEFINE__(1687671528396, function(require, module, exports) {
  12. var __TEMP__ = require('./util');var Util = __REQUIRE_DEFAULT__(__TEMP__);
  13. const imageMode = ['scaleToFill', 'aspectFit', 'aspectFill', 'widthFix', 'top', 'bottom', 'center', 'left', 'right', 'top left', 'top right', 'bottom left', 'bottom right']
  14. class Wxml2Canvas {
  15. constructor (options = {}) {
  16. this.device = wx.getSystemInfoSync && wx.getSystemInfoSync() || {};
  17. if (!options.zoom) {
  18. this.zoom = this.device.windowWidth / 375;
  19. } else {
  20. this.zoom = options.zoom || 1;
  21. }
  22. this.element = options.element;
  23. this.object = options.obj;
  24. this.width = options.width * this.zoom || 0;
  25. this.height = options.height * this.zoom || 0;
  26. this.destZoom = options.destZoom || 3;
  27. this.destWidth = this.width * this.destZoom;
  28. this.destHeight = this.height * this.destZoom;
  29. this.translateX = options.translateX * this.zoom || 0;
  30. this.translateY = options.translateY * this.zoom || 0;
  31. this.gradientBackground = options.gradientBackground || null;
  32. this.background = options.background || '#ffffff';
  33. this.finishDraw = options.finish || function finish(params) {}
  34. this.errorHandler = options.error || function error(params) {}
  35. this.progress = options.progress || function progress(params) {}
  36. this.textAlign = options.textAlign || 'left';
  37. this.fullText = options.fullText || false;
  38. this.font = options.font || '14px PingFang SC';
  39. this._init();
  40. }
  41. draw (data = {}, that) {
  42. let self = this;
  43. this.data = data;
  44. this.fef = that;
  45. this.progress(10);
  46. this._preloadImage(data.list).then((result) => {
  47. this.progress(30);
  48. self._draw();
  49. }).catch((res) => {
  50. self.errorHandler(res);
  51. })
  52. }
  53. measureWidth (text, font) {
  54. if(font) {
  55. this.ctx.font = font;
  56. }
  57. let res = this.ctx.measureText(text) || {};
  58. return res.width || 0;
  59. }
  60. _init () {
  61. this.progressPercent = 0; // 绘制进度百分比
  62. this.data = null;
  63. this.ref = null;
  64. this.allPic = [];
  65. this.screenList = [];
  66. this.asyncList = [];
  67. this.imgUrl = '';
  68. this.progressPercent = 0;
  69. this.distance = 0;
  70. this.progress(0);
  71. this.ctx = wx.createCanvasContext(this.element, this.obj);
  72. this.ctx.font = this.font;
  73. this.ctx.setTextBaseline('top');
  74. this.ctx.setStrokeStyle('white');
  75. this.debug = this.device.platform === 'devtools' ? true : false;
  76. this._drawBakcground();
  77. }
  78. _drawBakcground () {
  79. if (this.gradientBackground) {
  80. let line = this.gradientBackground.line || [0, 0, 0, this.height];
  81. let color = this.gradientBackground.color || ['#fff', '#fff'];
  82. let style = { fill: { line, color } }
  83. this._drawRectToCanvas(0, 0, this.width, this.height, style);
  84. } else {
  85. let style = { fill: this.background }
  86. this._drawRectToCanvas(0, 0, this.width, this.height, style);
  87. }
  88. }
  89. _draw () {
  90. let self = this;
  91. let list = this.data.list || [];
  92. let index = 0;
  93. let all = [];
  94. let count = 0;
  95. list.forEach(item => {
  96. if(item.type === 'wxml') {
  97. count += 3;
  98. } else {
  99. count += 1;
  100. }
  101. })
  102. this.distance = 60 / (count || 1); // 进度条的间距
  103. this.progressPercent = 30;
  104. this.asyncList = list.filter( item => item.delay == true );
  105. list = list.filter( item => item.delay != true );
  106. drawList(list);
  107. Promise.all(all).then(results => {
  108. index = 0;
  109. drawList(self.asyncList, true);
  110. Promise.all(all).then(results => {
  111. self.progress(90);
  112. self._saveCanvasToImage();
  113. });
  114. }).catch (e => {
  115. console.log(e)
  116. self.errorHandler(e);
  117. });
  118. function drawList(list = [], noDelay) {
  119. list.forEach((item, i) => {
  120. all[index++] = new Promise((resolve, reject) => {
  121. let attr = item.style;
  122. item.progress = self.distance;
  123. if (noDelay) {
  124. item.delay = 0;
  125. }
  126. if (item.type === 'radius-image') {
  127. self._drawCircle(item, attr, resolve, reject, 'image');
  128. } else if (item.type === 'text') {
  129. self._drawText(item, attr, resolve, reject);
  130. } else if (item.type === 'line') {
  131. self._drawLine(item, attr, resolve, reject);
  132. } else if (item.type === 'circle') {
  133. self._drawCircle(item, attr, resolve, reject);
  134. } else if (item.type === 'rect') {
  135. self._drawRect(item, attr, resolve, reject);
  136. } else if (item.type === 'image') {
  137. self._drawRect(item, attr, resolve, reject, 'image');
  138. } else if (item.type === 'wxml') {
  139. self._drawWxml(item, attr, resolve, reject);
  140. }else {
  141. resolve();
  142. }
  143. });
  144. });
  145. }
  146. }
  147. _saveCanvasToImage () {
  148. let self = this;
  149. // 延时保存有两个原因,一个是等待绘制delay的元素,另一个是安卓上样式会错乱
  150. setTimeout(() => {
  151. self.progress(95);
  152. let obj = {
  153. x: 0,
  154. y: 0,
  155. width: self.width,
  156. height: self.height,
  157. canvasId: self.element,
  158. success: function (res) {
  159. self.progress(100);
  160. self.imgUrl = res.tempFilePath;
  161. self.finishDraw(self.imgUrl);
  162. },
  163. fail: function (res) {
  164. self.errorHandler({errcode: 1000, errmsg: 'save canvas error', e: res});
  165. }
  166. }
  167. if(self.destZoom !== 3) {
  168. obj.destWidth = self.destWidth;
  169. obj.destHeight = self.destHeight;
  170. }
  171. wx.canvasToTempFilePath(obj, self.object);
  172. }, self.device.system.indexOf('iOS') === -1 ? 300 : 100);
  173. }
  174. _preloadImage (list = []) {
  175. let self = this;
  176. let all = [];
  177. let count = 0;
  178. list.forEach((item, i) => {
  179. if (item.url && self._findPicIndex(item.url) === -1) {
  180. // 避免重复下载同一图片
  181. self.allPic.push({
  182. url: item.url,
  183. local: ''
  184. });
  185. all[count++] = new Promise((resolve, reject) => {
  186. // 非http(s)域名的就不下载了
  187. if (!/^http/.test(item.url) || /^http:\/\/(tmp)|(usr)\//.test(item.url) || /^http:\/\/127\.0\.0\.1/.test(item.url)) {
  188. if(item.isBase64) {
  189. let fileManager = wx.getFileSystemManager();
  190. fileManager.writeFile({
  191. filePath: item.url,
  192. data: item.isBase64.replace(/data:image\/(.*);base64,/, ''),
  193. encoding: 'base64',
  194. success (res) {
  195. imageInfo(item.url);
  196. },
  197. fail (res) {
  198. reject(res);
  199. },
  200. })
  201. }else {
  202. imageInfo(item.url);
  203. }
  204. function imageInfo (url) {
  205. wx.getImageInfo({
  206. src: url,
  207. success (res) {
  208. let index = self._findPicIndex(url);
  209. if(index > -1) {
  210. self.allPic[index].local = url;
  211. self.allPic[index].width = res.width;
  212. self.allPic[index].height = res.height;
  213. }
  214. resolve({ tempFilePath: url });
  215. },
  216. fail (res) {
  217. reject(res);
  218. }
  219. })
  220. }
  221. } else {
  222. wx.downloadFile({
  223. url: item.url.replace(/^https?/, 'https'),
  224. success: function (res) {
  225. wx.getImageInfo({
  226. src: res.tempFilePath,
  227. success (img) {
  228. let index = self._findPicIndex(item.url);
  229. if (index > -1) {
  230. self.allPic[index].local = res.tempFilePath;
  231. self.allPic[index].width = img.width;
  232. self.allPic[index].height = img.height;
  233. }
  234. resolve(res);
  235. },
  236. fail (res) {
  237. reject(res);
  238. }
  239. })
  240. },
  241. fail: (res) => {
  242. reject({errcode: 1001, errmsg: 'download pic error'});
  243. }
  244. })
  245. }
  246. })
  247. }
  248. });
  249. return Promise.all(all).then(results => {
  250. return new Promise(resolve => { resolve() })
  251. }).catch((results) => {
  252. return new Promise((resolve, reject) => { reject(results) })
  253. })
  254. }
  255. _findPicIndex (url) {
  256. let index = this.allPic.findIndex(pic => pic.url === url);
  257. return index;
  258. }
  259. _drawRect (item, style, resolve, reject, isImage, isWxml) {
  260. let zoom = this.zoom;
  261. let leftOffset = 0;
  262. let topOffset = 0;
  263. let width = style.width;
  264. let height = style.height;
  265. let imgWidth = style.width;
  266. let imgHeight = style.height;
  267. let mode = null;
  268. try {
  269. item.x = this._resetPositionX(item, style);
  270. item.y = this._resetPositionY(item, style);
  271. let url;
  272. if(isImage) {
  273. let index = this._findPicIndex(item.url);
  274. if(index > -1) {
  275. url = this.allPic[index].local
  276. imgWidth = this.allPic[index].width
  277. imgHeight = this.allPic[index].height
  278. }else {
  279. url = item.url;
  280. }
  281. }
  282. style.padding = style.padding || [];
  283. if(isWxml === 'inline-wxml') {
  284. item.x = item.x + (style.padding[3] && style.padding[3] || 0)
  285. item.y = item.y + (style.padding[0] && style.padding[0] || 0)
  286. }
  287. leftOffset = item.x + style.width + (style.padding[1] && style.padding[1] || 0);
  288. if(!isWxml) {
  289. width = width * zoom;
  290. height = height * zoom;
  291. }
  292. if(style.dataset && style.dataset.mode && imageMode.indexOf(style.dataset.mode) > -1) {
  293. mode = {
  294. type: style.dataset.mode,
  295. width: imgWidth,
  296. height: imgHeight
  297. };
  298. }
  299. this._drawRectToCanvas(item.x, item.y, width, height, style, url, mode);
  300. this._updateProgress(item.progress);
  301. if(resolve) {
  302. resolve();
  303. }else {
  304. return {
  305. leftOffset,
  306. topOffset
  307. }
  308. }
  309. } catch (e) {
  310. reject && reject({ errcode: (isImage ? 1003 : 1002), errmsg: (isImage ? 'drawImage error' : 'drawRect error'), e });
  311. }
  312. }
  313. _drawRectToCanvas (x, y, width, height, style, url, mode) {
  314. let { fill, border, boxShadow } = style;
  315. this.ctx.save();
  316. this._drawBoxShadow(boxShadow, (res) => {
  317. // 真机上填充渐变色时,没有阴影,先画个相等大小的纯色矩形来实现阴影
  318. if(fill && typeof fill !== 'string' && !this.debug) {
  319. this.ctx.setFillStyle(res.color || '#ffffff');
  320. this.ctx.fillRect(x, y, width, height);
  321. }
  322. });
  323. if(url) {
  324. // 开发者工具有bug,先不裁剪
  325. if(mode) {
  326. this._resetImageByMode(url, x, y, width, height, mode);
  327. }else {
  328. this.ctx.drawImage(url, x, y, width, height)
  329. }
  330. }else {
  331. this._setFill(fill, () => {
  332. this.ctx.fillRect(x, y, width, height);
  333. });
  334. }
  335. this._drawBorder(border, style, (border) => {
  336. let fixBorder = border.width;
  337. this.ctx.strokeRect(x - fixBorder / 2, y - fixBorder / 2, width + fixBorder, height + fixBorder);
  338. });
  339. this.ctx.draw(true);
  340. this.ctx.restore();
  341. }
  342. _resetImageByMode (url, x, y, width, height, mode) {
  343. let self = this;
  344. let offsetX = 0;
  345. let offsetY = 0;
  346. let imgWidth = mode.width;
  347. let imgHeight = mode.height;
  348. switch (mode.type) {
  349. case 'scaleToFill':
  350. imgWidth = width;
  351. imgHeight = height;
  352. self.ctx.drawImage(url, x, y, width, height)
  353. break;
  354. case 'widthFix':
  355. height = width / ((imgWidth || 1) / (imgHeight || 1))
  356. self.ctx.drawImage(url, x, y, width, height)
  357. break;
  358. case 'aspectFit':
  359. if(imgWidth > imgHeight) {
  360. let realHeight = width / ((imgWidth || 1) / (imgHeight || 1))
  361. offsetY = -(height - realHeight) / 2
  362. imgWidth = width;
  363. imgHeight = realHeight;
  364. }else {
  365. let realWidth = height / ((imgHeight || 1) / (imgWidth || 1))
  366. offsetX = -(width - realWidth) / 2
  367. imgWidth = realWidth;
  368. imgHeight = height;
  369. }
  370. _clip();
  371. break;
  372. case 'aspectFill':
  373. if(imgWidth > imgHeight) {
  374. let realWidth = imgWidth / ((imgHeight || 1) / (height || 1))
  375. offsetX = (realWidth - width) / 2
  376. imgWidth = realWidth;
  377. imgHeight = height;
  378. }else {
  379. let realHeight = imgHeight / ((imgWidth || 1) / (width || 1))
  380. offsetY = (realHeight - height) / 2
  381. imgWidth = width;
  382. imgHeight = realHeight;
  383. }
  384. _clip();
  385. break;
  386. case 'top left':
  387. _clip();
  388. break;
  389. case 'top':
  390. offsetX = (mode.width - width) / 2;
  391. _clip();
  392. break;
  393. case 'top right':
  394. offsetX = (mode.width - width);
  395. _clip();
  396. break;
  397. case 'left':
  398. offsetY = (mode.height - height) / 2;
  399. _clip();
  400. break;
  401. case 'center':
  402. offsetX = (mode.width - width) / 2;
  403. offsetY = (mode.height - height) / 2;
  404. _clip();
  405. break;
  406. case 'right':
  407. offsetX = (mode.width - width);
  408. offsetY = (mode.height - height) / 2;
  409. _clip();
  410. break;
  411. case 'bottom left':
  412. offsetY = (mode.height - height)
  413. _clip();
  414. break;
  415. case 'bottom':
  416. offsetX = (mode.width - width) / 2;
  417. offsetY = (mode.height - height)
  418. _clip();
  419. break;
  420. case 'bottom right':
  421. offsetX = (mode.width - width);
  422. offsetY = (mode.height - height)
  423. _clip();
  424. break;
  425. default:
  426. imgWidth = width;
  427. imgHeight = height;
  428. break;
  429. }
  430. function _clip () {
  431. self.ctx.save();
  432. self.ctx.beginPath()
  433. self.ctx.rect(x, y, width, height)
  434. self.ctx.clip();
  435. self.ctx.drawImage(url, x - offsetX, y - offsetY, imgWidth, imgHeight)
  436. self.ctx.closePath();
  437. self.ctx.restore();
  438. }
  439. }
  440. _drawText (item, style, resolve, reject, type, isWxml) {
  441. let zoom = this.zoom;
  442. let leftOffset = 0;
  443. let topOffset = 0;
  444. try {
  445. style.fontSize = this._parseNumber(style.fontSize);
  446. let fontSize = Math.ceil((style.fontSize || 14) * zoom)
  447. this.ctx.setTextBaseline('top');
  448. this.ctx.font = (`${style.fontWeight ? (style.fontWeight) : 'normal'} ${ fontSize }px ${ style.fontFamily || 'PingFang SC' }`);
  449. this.ctx.setFillStyle(style.color || '#454545');
  450. let text = item.text || '';
  451. let textWidth = Math.floor(this.measureWidth(text, style.font || this.ctx.font));
  452. let lineHeight = this._getLineHeight(style);
  453. let textHeight = Math.ceil(textWidth / (style.width || textWidth)) * lineHeight;
  454. let width = Math.ceil((style.width || textWidth) * (!isWxml ? zoom : 1));
  455. let whiteSpace = style.whiteSpace || 'wrap';
  456. let x = 0;
  457. let y = 0;
  458. if(typeof style.padding === 'string') {
  459. style.padding = Util.transferPadding(style.padding);
  460. }
  461. item.x = this._resetPositionX(item, style);
  462. item.y = this._resetPositionY(item, style, textHeight);
  463. this._drawBoxShadow(style.boxShadow);
  464. if(style.background || style.border) {
  465. this._drawTextBackgroud(item, style, textWidth, textHeight, isWxml);
  466. }
  467. // 行内文本
  468. if(type === 'inline-text') {
  469. width = item.maxWidth;
  470. if(item.leftOffset + textWidth > width) {
  471. // 如果上一个行内元素换行了,这个元素要继续在后面补足一行
  472. let lineNum = Math.max(Math.floor(textWidth / width), 1);
  473. let length = text.length;
  474. let singleLength = Math.floor(length / lineNum);
  475. let widthOffset = item.leftOffset ? item.leftOffset - item.originX : 0;
  476. let { endIndex: currentIndex, single, singleWidth } = this._getTextSingleLine(text, width, singleLength, 0, widthOffset)
  477. x = this._resetTextPositionX(item, style, singleWidth);
  478. y = this._resetTextPositionY(item, style);
  479. this.ctx.fillText(single, x, y);
  480. leftOffset = x + singleWidth;
  481. topOffset = y;
  482. // 去除第一行补的内容,然后重置
  483. text = text.substring(currentIndex, text.length);
  484. currentIndex = 0;
  485. lineNum = Math.max(Math.floor(textWidth / width), 1);
  486. textWidth = Math.floor(this.measureWidth(text, style.font || this.ctx.font));
  487. item.x = item.originX; // 还原换行后的x
  488. for (let i = 0; i < lineNum; i++) {
  489. let { endIndex, single, singleWidth } = this._getTextSingleLine(text, width, singleLength, currentIndex);
  490. currentIndex = endIndex;
  491. if(single) {
  492. x = this._resetTextPositionX(item, style, singleWidth, width);
  493. y = this._resetTextPositionY(item, style, i + 1);
  494. this.ctx.fillText(single, x, y);
  495. if(i === lineNum - 1) {
  496. leftOffset = x + singleWidth;
  497. topOffset = lineHeight * lineNum;
  498. }
  499. }
  500. }
  501. let last = text.substring(currentIndex, length);
  502. let lastWidth = this.measureWidth(last);
  503. if(last) {
  504. x = this._resetTextPositionX(item, style, lastWidth, width);
  505. y = this._resetTextPositionY(item, style, lineNum + 1);
  506. this.ctx.fillText(last, x, y);
  507. leftOffset = x + lastWidth;
  508. topOffset = lineHeight * (lineNum + 1);
  509. }
  510. }else {
  511. x = this._resetTextPositionX(item, style, textWidth, width);
  512. y = this._resetTextPositionY(item, style);
  513. this.ctx.fillText(item.text, x, y);
  514. leftOffset = x + textWidth;
  515. topOffset = lineHeight;
  516. }
  517. }else {
  518. // block文本,如果文本长度超过宽度换行
  519. if (width && textWidth > width && whiteSpace !== 'nowrap') {
  520. let lineNum = Math.max(Math.floor(textWidth / width), 1);
  521. let length = text.length;
  522. let singleLength = Math.floor(length / lineNum);
  523. let currentIndex = 0;
  524. // lineClamp参数限制最多行数
  525. if (style.lineClamp && lineNum + 1 > style.lineClamp) {
  526. lineNum = style.lineClamp - 1;
  527. }
  528. for (let i = 0; i < lineNum; i++) {
  529. let { endIndex, single, singleWidth } = this._getTextSingleLine(text, width, singleLength, currentIndex);
  530. currentIndex = endIndex;
  531. x = this._resetTextPositionX(item, style, singleWidth, width);
  532. y = this._resetTextPositionY(item, style, i);
  533. this.ctx.fillText(single, x, y);
  534. }
  535. // 换行后剩余的文字,超过一行则截断增加省略号
  536. let last = text.substring(currentIndex, length);
  537. let lastWidth = this.measureWidth(last);
  538. if(lastWidth > width) {
  539. let { single, singleWidth } = this._getTextSingleLine(last, width, singleLength);
  540. lastWidth = singleWidth;
  541. last = single.substring(0, single.length - 1) + '...';
  542. }
  543. x = this._resetTextPositionX(item, style, lastWidth, width);
  544. y = this._resetTextPositionY(item, style, lineNum);
  545. this.ctx.fillText(last, x, y);
  546. }else {
  547. x = this._resetTextPositionX(item, style, textWidth, width);
  548. y = this._resetTextPositionY(item, style);
  549. this.ctx.fillText(item.text, x, y);
  550. }
  551. }
  552. this.ctx.draw(true);
  553. this._updateProgress(item.progress);
  554. if(resolve) {
  555. resolve();
  556. }else {
  557. return {
  558. leftOffset,
  559. topOffset
  560. }
  561. }
  562. } catch(e) {
  563. reject && reject({ errcode: 1004, errmsg: 'drawText error', e: e });
  564. }
  565. }
  566. _drawTextBackgroud (item, style, textWidth, textHeight, isWxml) {
  567. if(!style.width) return;
  568. let zoom = isWxml ? 1 : this.zoom;
  569. let width = style.width || textWidth;
  570. let height = style.height || textHeight;
  571. let rectStyle = {
  572. fill: style.background,
  573. border: style.border
  574. }
  575. style.padding = style.padding || [0, 0, 0, 0];
  576. width += (style.padding[1] || 0) + (style.padding[3] || 0);
  577. height += (style.padding[0] || 0) + (style.padding[2] || 0);
  578. width = width * zoom
  579. height = height * zoom
  580. this._drawRectToCanvas(item.x, item.y, width, height, rectStyle);
  581. }
  582. _drawCircle (item, style, resolve, reject, isImage, isWxml) {
  583. let zoom = this.zoom;
  584. let r = style.r;
  585. try {
  586. item.x = this._resetPositionX(item, style);
  587. item.y = this._resetPositionY(item, style);
  588. let url;
  589. if(isImage) {
  590. let index = this._findPicIndex(item.url);
  591. if (index > -1) {
  592. url = this.allPic[index].local;
  593. } else {
  594. url = item.url;
  595. }
  596. }
  597. if(!isWxml) {
  598. r = r * zoom;
  599. }
  600. this._drawCircleToCanvas(item.x, item.y, r, style, url);
  601. this._updateProgress(item.progress);
  602. resolve && resolve();
  603. } catch (e) {
  604. reject && reject({ errcode: (isImage ? 1006 : 1005), errmsg: (isImage ? 'drawCircleImage error' : 'drawCircle error'), e });
  605. }
  606. }
  607. _drawCircleToCanvas (x, y, r, style, url) {
  608. let { fill, border, boxShadow } = style;
  609. this.ctx.save();
  610. this._drawBoxShadow(boxShadow, (res) => {
  611. // 真机上填充渐变色时,没有阴影,先画个相等大小的纯色矩形来实现阴影
  612. if((fill && typeof fill !== 'string') || (url && res.color)) {
  613. this.ctx.setFillStyle(res.color || '#ffffff');
  614. this.ctx.beginPath();
  615. this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI);
  616. this.ctx.closePath();
  617. this.ctx.fill();
  618. }
  619. });
  620. if(url) {
  621. this.ctx.save();
  622. this.ctx.beginPath();
  623. this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI);
  624. this.ctx.clip();
  625. this.ctx.drawImage(url, x, y, r * 2, r * 2);
  626. this.ctx.closePath();
  627. this.ctx.restore();
  628. }else {
  629. this._setFill(fill, () => {
  630. this.ctx.beginPath();
  631. this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI);
  632. this.ctx.closePath();
  633. this.ctx.fill();
  634. });
  635. }
  636. this._drawBorder(border, style, (border) => {
  637. this.ctx.beginPath()
  638. this.ctx.arc(x + r, y + r, r + border.width / 2, 0, 2 * Math.PI)
  639. this.ctx.stroke()
  640. this.ctx.closePath();
  641. });
  642. this.ctx.draw(true);
  643. this.ctx.restore();
  644. }
  645. _drawLine (item, style, resolve, reject, isWxml) {
  646. let zoom = this.zoom;
  647. try {
  648. let x1 = item.x * zoom + this.translateX;
  649. let y1 = item.y * zoom + this.translateY;
  650. let x2 = item.x2 * zoom + this.translateX;
  651. let y2 = item.y2 * zoom + this.translateY;
  652. this._drawLineToCanvas(x1, y1, x2, y2, style);
  653. this._updateProgress(item.progress);
  654. resolve && resolve();
  655. } catch (e) {
  656. reject && reject({ errcode: 1007, errmsg: 'drawLine error', e });
  657. }
  658. }
  659. _drawLineToCanvas (x1, y1, x2, y2, style) {
  660. let { stroke, dash, boxShadow } = style;
  661. this.ctx.save();
  662. if(stroke) {
  663. this._setStroke(stroke);
  664. }
  665. this._drawBoxShadow(boxShadow);
  666. if(dash) {
  667. let dash = [style.dash[0] || 5, style.dash[1] || 5];
  668. let offset = style.dash[2] || 0;
  669. this.ctx.setLineDash(dash, offset || 0);
  670. }
  671. this.ctx.moveTo(x1, y1);
  672. this.ctx.setLineWidth((style.width || 1) * this.zoom);
  673. this.ctx.lineTo(x2, y2);
  674. this.ctx.stroke();
  675. this.ctx.draw(true);
  676. this.ctx.restore();
  677. }
  678. // 废弃,合并到_drawRect
  679. _drawImage (item, style, resolve, reject, isWxml) {
  680. let zoom = this.zoom;
  681. try {
  682. item.x = this._resetPositionX(item, style);
  683. item.y = this._resetPositionY(item, style);
  684. item.x = item.x + (style.padding[3] || 0);
  685. item.y = item.y + (style.padding[0] || 0);
  686. let index = this._findPicIndex(item.url);
  687. let url = index > -1 ? this.allPic[index].local : item.url;
  688. this._drawImageToCanvas(url, item.x, item.y, style.width * zoom, style.height * zoom, style);
  689. this._updateProgress(item.progress);
  690. resolve && resolve();
  691. } catch (e) {
  692. reject && reject({ errcode: 1012, errmsg: 'drawRect error', e });
  693. }
  694. }
  695. // 废弃,合并到_drawRect
  696. _drawImageToCanvas (url, x, y, width, height, style) {
  697. let { fill, border, boxShadow } = style;
  698. this.ctx.save();
  699. this._drawBoxShadow(boxShadow);
  700. this.ctx.drawImage(url, x, y, width, height);
  701. this._drawBorder(border, style, (border) => {
  702. let fixBorder = border.width;
  703. this.ctx.strokeRect(x - fixBorder / 2, y - fixBorder / 2, width + fixBorder, height + fixBorder);
  704. });
  705. this.ctx.draw(true);
  706. this.ctx.restore();
  707. }
  708. _drawWxml (item, style, resolve, reject) {
  709. let self = this;
  710. let all = [];
  711. try {
  712. this._getWxml(item, style).then((results) => {
  713. // 上 -> 下
  714. let sorted = self._sortListByTop(results[0]);
  715. let count = 0;
  716. let progress = 0;
  717. Object.keys(sorted).forEach(item => {
  718. count += sorted[item].length;
  719. })
  720. progress = this.distance * 3 / (count || 1);
  721. all = this._drawWxmlBlock(item, sorted, all, progress, results[1]);
  722. all = this._drawWxmlInline(item, sorted, all, progress, results[1]);
  723. Promise.all(all).then(results => {
  724. resolve && resolve();
  725. }).catch (e => {
  726. reject && reject(e);
  727. });
  728. });
  729. } catch (e) {
  730. reject && reject({ errcode: 1008, errmsg: 'drawWxml error' });
  731. }
  732. }
  733. _drawWxmlBlock (item, sorted, all, progress, results) {
  734. let self = this;
  735. // 用来限定位置范围,取相对位置
  736. let limitLeft = (results ? results.left : 0);
  737. let limitTop = (results ? results.top : 0);
  738. Object.keys(sorted).forEach((top, topIndex) => {
  739. // 左 -> 右
  740. let list = sorted[top].sort((a, b) => {
  741. return (a.left - b.left);
  742. });
  743. list = list.filter(sub => sub.dataset.type && sub.dataset.type.indexOf('inline') === -1);
  744. list.forEach((sub, index) => {
  745. all[index] = new Promise((resolve2, reject2) => {
  746. sub = self._transferWxmlStyle(sub, item, limitLeft, limitTop);
  747. sub.progress = progress;
  748. let type = sub.dataset.type;
  749. if(sub.dataset.delay) {
  750. setTimeout(() => {
  751. drawWxmlItem();
  752. }, sub.dataset.delay)
  753. } else {
  754. drawWxmlItem();
  755. }
  756. function drawWxmlItem () {
  757. if (type === 'text') {
  758. self._drawWxmlText(sub, resolve2, reject2);
  759. } else if (type === 'image') {
  760. self._drawWxmlImage(sub, resolve2, reject2);
  761. } else if (type === 'radius-image') {
  762. self._drawWxmlCircleImage(sub, resolve2, reject2);
  763. } else if (type === 'background-image') {
  764. self._drawWxmlBackgroundImage(sub, resolve2, reject2);
  765. }
  766. }
  767. });
  768. });
  769. });
  770. return all;
  771. }
  772. _drawWxmlInline (item, sorted, all, progress, results) {
  773. let self = this;
  774. let topOffset = 0;
  775. let leftOffset = 0;
  776. let lastTop = 0;
  777. let limitLeft = (results ? results.left : 0);
  778. let limitTop = (results ? results.top : 0);
  779. let p = new Promise((resolve2, reject2) => {
  780. let maxWidth = 0;
  781. let minLeft = Infinity;
  782. let maxRight = 0;
  783. // 找出同一top下的最小left和最大right,得到最大的宽度,用于换行
  784. Object.keys(sorted).forEach(top => {
  785. let inlineList = sorted[top].filter(sub => sub.dataset.type && sub.dataset.type.indexOf('inline') > -1);
  786. inlineList.forEach(sub => {
  787. if(sub.left < minLeft) {
  788. minLeft = sub.left
  789. }
  790. if(sub.right > maxRight) {
  791. maxRight = sub.right;
  792. }
  793. })
  794. });
  795. maxWidth = Math.ceil((maxRight - minLeft) || self.width);
  796. Object.keys(sorted).forEach((top, topIndex) => {
  797. // 左 -> 右
  798. let list = sorted[top].sort((a, b) => {
  799. return (a.left - b.left);
  800. });
  801. // 换行的行内元素left放到后面,version2.0.6后无法获取高度,改用bottom值来判断是否换行了
  802. let position = -1;
  803. for(let i = 0, len = list.length; i < len; i++) {
  804. if(list[i] && list[i + 1]) {
  805. if(list[i].bottom > list[i + 1].bottom) {
  806. position = i;
  807. break;
  808. }
  809. }
  810. }
  811. if(position > -1) {
  812. list.push(list.splice(position, 1)[0]);
  813. }
  814. let inlineList = list.filter(sub => sub.dataset.type && sub.dataset.type.indexOf('inline') > -1);
  815. let originLeft = (inlineList[0] ? inlineList[0].left : 0);
  816. // 换行后和top不相等时,认为是换行了,要清除左边距;当左偏移量大于最大宽度时,也要清除左边距; 当左偏移小于左边距时,也要清除
  817. if (Math.abs(topOffset + lastTop - top) > 2 || leftOffset - originLeft - limitLeft >= maxWidth || leftOffset <= originLeft - limitLeft - 2) {
  818. leftOffset = 0;
  819. }
  820. lastTop = +top;
  821. topOffset = 0;
  822. inlineList.forEach((sub, index) => {
  823. sub = self._transferWxmlStyle(sub, item, limitLeft, limitTop);
  824. sub.progress = progress;
  825. let type = sub.dataset.type;
  826. if (type === 'inline-text') {
  827. let drawRes = self._drawWxmlInlineText(sub, leftOffset, maxWidth);
  828. leftOffset = drawRes.leftOffset;
  829. topOffset = drawRes.topOffset;
  830. } else if (type === 'inline-image') {
  831. let drawRes = self._drawWxmlImage(sub) || {};
  832. leftOffset = drawRes.leftOffset || 0;
  833. topOffset = drawRes.topOffset || 0;
  834. }
  835. });
  836. });
  837. resolve2();
  838. })
  839. all.push(p);
  840. return all;
  841. }
  842. _drawWxmlInlineText (sub, leftOffset = 0, maxWidth) {
  843. let text = sub.dataset.text || '';
  844. if(sub.dataset.maxlength && text.length > sub.dataset.maxlength) {
  845. text = text.substring(0, sub.dataset.maxlength) + '...';
  846. }
  847. let textData = {
  848. text,
  849. originX: sub.left,
  850. x: leftOffset ? leftOffset : sub.left,
  851. y: sub.top,
  852. progress: sub.progress,
  853. leftOffset: leftOffset,
  854. maxWidth: maxWidth // 行内元素的最大宽度,取决于limit的宽度
  855. }
  856. if (sub.backgroundColor !== 'rgba(0, 0, 0, 0)') {
  857. sub.background = sub.backgroundColor;
  858. }else {
  859. sub.background = 'rgba(0, 0, 0, 0)';
  860. }
  861. if(sub.dataset.background) {
  862. sub.background = sub.dataset.background;
  863. }
  864. let res = this._drawText(textData, sub, null, null, 'inline-text', 'wxml');
  865. return res
  866. }
  867. _drawWxmlText (sub, resolve, reject) {
  868. let text = sub.dataset.text || '';
  869. if(sub.dataset.maxlength && text.length > sub.dataset.maxlength) {
  870. text = text.substring(0, sub.dataset.maxlength) + '...';
  871. }
  872. let textData = {
  873. text,
  874. x: sub.left,
  875. y: sub.top,
  876. progress: sub.progress
  877. }
  878. if (sub.backgroundColor !== 'rgba(0, 0, 0, 0)') {
  879. sub.background = sub.backgroundColor;
  880. }else {
  881. sub.background = 'rgba(0, 0, 0, 0)';
  882. }
  883. if(sub.dataset.background) {
  884. sub.background = sub.dataset.background;
  885. }
  886. this._drawText(textData, sub, resolve, reject, 'text', 'wxml');
  887. }
  888. _drawWxmlImage (sub, resolve, reject) {
  889. let imageData = {
  890. url: sub.dataset.url,
  891. x: sub.left,
  892. y: sub.top,
  893. progress: sub.progress
  894. }
  895. let res = this._drawRect(imageData, sub, resolve, reject, 'image', 'inline-wxml');
  896. return res
  897. }
  898. _drawWxmlCircleImage (sub, resolve, reject) {
  899. let imageData = {
  900. url: sub.dataset.url,
  901. x: sub.left,
  902. y: sub.top,
  903. progress: sub.progress
  904. }
  905. sub.r = sub.width / 2;
  906. this._drawCircle(imageData, sub, resolve, reject, true, 'wxml');
  907. }
  908. _drawWxmlBackgroundImage (sub, resolve, reject) {
  909. let url = sub.dataset.url;
  910. let index = this._findPicIndex(url);
  911. url = index > -1 ? this.allPic[index].local : url;
  912. let size = sub.backgroundSize.replace(/px/g, '').split(' ');
  913. let imageData = {
  914. url: url,
  915. x: sub.left,
  916. y: sub.top,
  917. progress: sub.progress
  918. }
  919. this._drawRect(imageData, sub, resolve, reject, 'image', 'wxml');
  920. }
  921. _getWxml (item, style) {
  922. let self = this;
  923. let query;
  924. if(this.obj) {
  925. query = wx.createSelectorQuery().in(this.obj);
  926. }else {
  927. query = wx.createSelectorQuery();
  928. }
  929. let p1 = new Promise((resolve, reject) => {
  930. // 会触发两次,要限制
  931. let count = 0;
  932. query.selectAll(`${item.class}`).fields({
  933. dataset: true,
  934. size: true,
  935. rect: true,
  936. computedStyle: ['width', 'height', 'font', 'fontSize', 'fontFamily', 'fontWeight', 'fontStyle', 'textAlign',
  937. 'color', 'lineHeight', 'border', 'borderColor', 'borderStyle', 'borderWidth', 'verticalAlign', 'boxShadow',
  938. 'background', 'backgroundColor', 'backgroundImage', 'backgroundPosition', 'backgroundSize', 'paddingLeft', 'paddingTop',
  939. 'paddingRight', 'paddingBottom'
  940. ]
  941. }, (res) => {
  942. if(count++ === 0) {
  943. let formated = self._formatImage(res);
  944. let list = formated.list;
  945. res = formated.res;
  946. self._preloadImage(list).then(result => {
  947. resolve(res);
  948. }).catch((res) => {
  949. reject && reject({ errcode: 1009, errmsg: 'drawWxml preLoadImage error' });
  950. });
  951. }
  952. }).exec();
  953. });
  954. let p2 = new Promise((resolve, reject) => {
  955. if (!item.limit) {
  956. resolve({ top: 0, width: self.width / self.zoom });
  957. }
  958. query.select(`${item.limit}`).fields({
  959. dataset: true,
  960. size: true,
  961. rect: true,
  962. }, (res) => {
  963. resolve(res);
  964. }).exec();
  965. });
  966. return Promise.all([p1, p2]);
  967. }
  968. _getLineHeight (style) {
  969. let zoom = this.zoom;
  970. if(style.dataset && style.dataset.type) {
  971. zoom = 1;
  972. }
  973. let lineHeight;
  974. if(!isNaN(style.lineHeight) && style.lineHeight > style.fontSize) {
  975. lineHeight = style.lineHeight;
  976. }else {
  977. style.lineHeight = (style.lineHeight || '') + '';
  978. lineHeight = +style.lineHeight.replace('px', '');
  979. lineHeight = lineHeight ? lineHeight : (style.fontSize || 14) * 1.2;
  980. }
  981. return lineHeight * zoom;
  982. }
  983. _formatImage (res = []) {
  984. let list = [];
  985. res.forEach((item, index) => {
  986. let dataset = item.dataset;
  987. let uid = Util.getUid();
  988. let filename = `${wx.env.USER_DATA_PATH}/${uid}.png`;
  989. if ((dataset.type === "image" || dataset.type === "radius-image") && dataset.url) {
  990. let sub = {
  991. url: dataset.base64 ? filename : dataset.url,
  992. isBase64: dataset.base64 ? dataset.url : false
  993. }
  994. res[index].dataset = Object.assign(res[index].dataset, sub);
  995. list.push(sub)
  996. } else if (dataset.type === 'background-image' && item.backgroundImage.indexOf('url') > -1) {
  997. let url = item.backgroundImage.replace(/url\((\"|\')?/, '').replace(/(\"|\')?\)$/, '');
  998. let sub = {
  999. url: dataset.base64 ? filename : url,
  1000. isBase64: dataset.base64 ? url : false
  1001. }
  1002. res[index].dataset = Object.assign(res[index].dataset, sub);
  1003. list.push(sub)
  1004. }
  1005. });
  1006. return { list, res };
  1007. }
  1008. _updateProgress (distance) {
  1009. this.progressPercent += distance;
  1010. this.progress(this.progressPercent);
  1011. }
  1012. _sortListByTop (list = []) {
  1013. let sorted = {};
  1014. // 粗略地认为2px相差的元素在同一行
  1015. list.forEach((item, index) => {
  1016. let top = item.top;
  1017. if (!sorted[top]) {
  1018. if (sorted[top - 2]) {
  1019. top = top - 2;
  1020. }else if (sorted[top - 1]) {
  1021. top = top - 1;
  1022. } else if (sorted[top + 1]) {
  1023. top = top + 1;
  1024. } else if (sorted[top + 2]) {
  1025. top = top + 2;
  1026. } else {
  1027. sorted[top] = [];
  1028. }
  1029. }
  1030. sorted[top].push(item);
  1031. });
  1032. return sorted;
  1033. }
  1034. _parseNumber (number) {
  1035. return isNaN(number) ? +(number || '').replace('px', '') : number;
  1036. }
  1037. _transferWxmlStyle (sub, item, limitLeft, limitTop) {
  1038. let leftFix = (+sub.dataset.left || 0);
  1039. let topFix = (+sub.dataset.top || 0);
  1040. sub.width = this._parseNumber(sub.width);
  1041. sub.height = this._parseNumber(sub.height);
  1042. sub.left = this._parseNumber(sub.left) - limitLeft + (leftFix + (item.x || 0)) * this.zoom;
  1043. sub.top = this._parseNumber(sub.top) - limitTop + (topFix + (item.y || 0)) * this.zoom;
  1044. let padding = sub.dataset.padding || '0 0 0 0';
  1045. if (typeof padding === 'string') {
  1046. padding = Util.transferPadding(padding);
  1047. }
  1048. let paddingTop = Number(sub.paddingTop.replace('px', '')) + Number(padding[0]);
  1049. let paddingRight = Number(sub.paddingRight.replace('px', '')) + Number(padding[1]);
  1050. let paddingBottom = Number(sub.paddingBottom.replace('px', '')) + Number(padding[2]);
  1051. let paddingLeft = Number(sub.paddingLeft.replace('px', '')) + Number(padding[3]);
  1052. sub.padding = [paddingTop, paddingRight, paddingBottom, paddingLeft];
  1053. return sub;
  1054. }
  1055. /**
  1056. * 支持负值绘制,从右边计算
  1057. * @param {*} item
  1058. * @param {*} style
  1059. */
  1060. _resetPositionX (item, style) {
  1061. let zoom = this.zoom;
  1062. let x = 0;
  1063. if(style.dataset && style.dataset.type) {
  1064. zoom = 1;
  1065. }
  1066. // 通过wxml获取的不需要重置坐标
  1067. if (item.x < 0 && item.type) {
  1068. x = this.width + item.x * zoom - style.width * zoom;
  1069. } else {
  1070. x = item.x * zoom;
  1071. }
  1072. if (parseInt(style.borderWidth)) {
  1073. x += parseInt(style.borderWidth)
  1074. }
  1075. return x + this.translateX;
  1076. }
  1077. /**
  1078. * 支持负值绘制,从底部计算
  1079. * @param {*} item
  1080. * @param {*} style
  1081. */
  1082. _resetPositionY (item, style, textHeight) {
  1083. let zoom = this.zoom;
  1084. let y = 0;
  1085. if(style.dataset && style.dataset.type) {
  1086. zoom = 1;
  1087. }
  1088. if (item.y < 0) {
  1089. y = this.height + item.y * zoom - (textHeight ? textHeight : style.height * zoom)
  1090. } else {
  1091. y = item.y * zoom;
  1092. }
  1093. if (parseInt(style.borderWidth)) {
  1094. y += parseInt(style.borderWidth)
  1095. }
  1096. return y + this.translateY;
  1097. }
  1098. /**
  1099. * 文字的padding、text-align
  1100. * @param {*} item
  1101. * @param {*} style
  1102. * @param {*} textWidth
  1103. */
  1104. _resetTextPositionX (item, style, textWidth, width) {
  1105. let textAlign = style.textAlign || 'left';
  1106. let x = item.x;
  1107. if (textAlign === 'center') {
  1108. x = (width - textWidth) / 2 + item.x;
  1109. } else if (textAlign === 'right') {
  1110. x = width - textWidth + item.x;
  1111. }
  1112. let left = style.padding ? (style.padding[3] || 0) : 0;
  1113. return x + left + this.translateX;
  1114. }
  1115. /**
  1116. * 文字的padding、text-align
  1117. * @param {*} item
  1118. * @param {*} style
  1119. * @param {*} textWidth
  1120. */
  1121. _resetTextPositionY (item, style, lineNum = 0) {
  1122. let zoom = this.zoom;
  1123. if(style.dataset && style.dataset.type) {
  1124. zoom = 1;
  1125. }
  1126. let lineHeight = this._getLineHeight(style);
  1127. let fontSize = Math.ceil((style.fontSize || 14) * zoom)
  1128. let blockLineHeightFix = (style.dataset && style.dataset.type || '').indexOf('inline') > -1 ? 0 : (lineHeight - fontSize) / 2
  1129. let top = style.padding ? (style.padding[0] || 0) : 0;
  1130. // y + lineheight偏移 + 行数 + paddingTop + 整体画布位移
  1131. return item.y + blockLineHeightFix + lineNum * lineHeight + top + this.translateY;
  1132. }
  1133. /**
  1134. * 当文本超过宽度时,计算每一行应该绘制的文本
  1135. * @param {*} text
  1136. * @param {*} width
  1137. * @param {*} singleLength
  1138. * @param {*} currentIndex
  1139. * @param {*} widthOffset
  1140. */
  1141. _getTextSingleLine(text, width, singleLength, currentIndex = 0, widthOffset = 0) {
  1142. let offset = 0;
  1143. let endIndex = currentIndex + singleLength + offset;
  1144. let single = text.substring(currentIndex, endIndex);
  1145. let singleWidth = this.measureWidth(single);
  1146. while (Math.round(widthOffset + singleWidth) > width) {
  1147. offset--;
  1148. endIndex = currentIndex + singleLength + offset;
  1149. single = text.substring(currentIndex, endIndex);
  1150. singleWidth = this.measureWidth(single);
  1151. }
  1152. return {
  1153. endIndex,
  1154. single,
  1155. singleWidth
  1156. }
  1157. }
  1158. _drawBorder (border, style, callback) {
  1159. let zoom = this.zoom;
  1160. if(style.dataset && style.dataset.type) {
  1161. zoom = 1;
  1162. }
  1163. border = Util.transferBorder(border);
  1164. if (border && border.width) {
  1165. // 空白阴影,清空掉边框的阴影
  1166. this._drawBoxShadow();
  1167. if (border) {
  1168. this.ctx.setLineWidth(border.width * zoom);
  1169. if (border.style === 'dashed') {
  1170. let dash = style.dash || [5, 5, 0];
  1171. let offset = dash[2] || 0;
  1172. let array = [dash[0] || 5, dash[1] || 5];
  1173. this.ctx.setLineDash(array, offset);
  1174. }
  1175. this.ctx.setStrokeStyle(border.color);
  1176. }
  1177. callback && callback(border);
  1178. }
  1179. }
  1180. _drawBoxShadow (boxShadow, callback) {
  1181. boxShadow = Util.transferBoxShadow(boxShadow);
  1182. if (boxShadow) {
  1183. this.ctx.setShadow(boxShadow.offsetX, boxShadow.offsetY, boxShadow.blur, boxShadow.color);
  1184. }else {
  1185. this.ctx.setShadow(0, 0, 0, '#ffffff');
  1186. }
  1187. callback && callback(boxShadow || {});
  1188. }
  1189. _setFill (fill, callback) {
  1190. if(fill) {
  1191. if (typeof fill === 'string') {
  1192. this.ctx.setFillStyle(fill);
  1193. } else {
  1194. let line = fill.line;
  1195. let color = fill.color;
  1196. let grd = this.ctx.createLinearGradient(line[0], line[1], line[2], line[3]);
  1197. grd.addColorStop(0, color[0]);
  1198. grd.addColorStop(1, color[1]);
  1199. this.ctx.setFillStyle(grd);
  1200. }
  1201. callback && callback();
  1202. }
  1203. }
  1204. _setStroke (stroke, callback) {
  1205. if(stroke) {
  1206. if (typeof stroke === 'string') {
  1207. this.ctx.setStrokeStyle(stroke);
  1208. } else {
  1209. let line = stroke.line;
  1210. let color = stroke.color;
  1211. let grd = this.ctx.createLinearGradient(line[0], line[1], line[2], line[3]);
  1212. grd.addColorStop(0, color[0]);
  1213. grd.addColorStop(1, color[1]);
  1214. this.ctx.setStrokeStyle(grd);
  1215. }
  1216. callback && callback();
  1217. }
  1218. }
  1219. }
  1220. if (!exports.__esModule) Object.defineProperty(exports, "__esModule", { value: true });exports.default = Wxml2Canvas;
  1221. }, function(modId) { var map = {"./util":1687671528397}; return __REQUIRE__(map[modId], modId); })
  1222. __DEFINE__(1687671528397, function(require, module, exports) {
  1223. /**
  1224. * 获取字符的长度,full为true时,一个汉字算两个长度
  1225. * @param {String} str
  1226. * @param {Boolean} full
  1227. */
  1228. function getTextLength (str, full) {
  1229. let len = 0;
  1230. for (let i = 0; i < str.length; i++) {
  1231. let c = str.charCodeAt(i);
  1232. //单字节加1
  1233. if ((c >= 0x0001 && c <= 0x007e) || (0xff60 <= c && c <= 0xff9f)) {
  1234. len++;
  1235. }
  1236. else {
  1237. len += (full ? 2 : 1);
  1238. }
  1239. }
  1240. return len;
  1241. }
  1242. /**
  1243. * rgba(255, 255, 255, 1) => #ffffff
  1244. * @param {String} color
  1245. */
  1246. function transferColor (color = '') {
  1247. let res = '#';
  1248. color = color.replace(/^rgba?\(/, '').replace(/\)$/, '');
  1249. color = color.split(', ');
  1250. color.length > 3 ? color.length = 3 : '';
  1251. for(let item of color) {
  1252. item = parseInt(item || 0);
  1253. if(item < 10) {
  1254. res += ('0' + item)
  1255. }else {
  1256. res += (item.toString(16))
  1257. }
  1258. }
  1259. return res;
  1260. }
  1261. function transferBorder (border = '') {
  1262. let res = border.match(/(\w+)px\s(\w+)\s(.*)/);
  1263. let obj = {};
  1264. if(res) {
  1265. obj = {
  1266. width: +res[1],
  1267. style: res[2],
  1268. color: res[3]
  1269. }
  1270. }
  1271. return res ? obj : null;
  1272. }
  1273. /**
  1274. * 内边距,依次为上右下左
  1275. * @param {*} padding
  1276. */
  1277. function transferPadding (padding = '0 0 0 0') {
  1278. padding = padding.split(' ');
  1279. for(let i = 0, len = padding.length; i < len; i++) {
  1280. padding[i] = +padding[i].replace('px', '');
  1281. }
  1282. return padding;
  1283. }
  1284. /**
  1285. * type1: 0, 25, 17, rgba(0, 0, 0, 0.3)
  1286. * type2: rgba(0, 0, 0, 0.3) 0px 25px 17px 0px => (0, 25, 17, rgba(0, 0, 0, 0.3))
  1287. * @param {*} shadow
  1288. */
  1289. function transferBoxShadow(shadow = '', type) {
  1290. if(!shadow || shadow === 'none') return;
  1291. let color;
  1292. let split;
  1293. split = shadow.match(/(\w+)\s(\w+)\s(\w+)\s(rgb.*)/);
  1294. if (split) {
  1295. split.shift();
  1296. shadow = split;
  1297. color = split[3] || '#ffffff';
  1298. } else {
  1299. split = shadow.split(') ');
  1300. color = split[0] + ')'
  1301. shadow = split[1].split('px ');
  1302. }
  1303. return {
  1304. offsetX: +shadow[0] || 0,
  1305. offsetY: +shadow[1] || 0,
  1306. blur: +shadow[2] || 0,
  1307. color
  1308. }
  1309. }
  1310. function getUid(prefix) {
  1311. prefix = prefix || '';
  1312. return (
  1313. prefix +
  1314. 'xxyxxyxx'.replace(/[xy]/g, c => {
  1315. let r = (Math.random() * 16) | 0;
  1316. let v = c === 'x' ? r : (r & 0x3) | 0x8;
  1317. return v.toString(16);
  1318. })
  1319. );
  1320. }
  1321. if (!exports.__esModule) Object.defineProperty(exports, "__esModule", { value: true });exports.default = {
  1322. getTextLength,
  1323. transferBorder,
  1324. transferColor,
  1325. transferPadding,
  1326. transferBoxShadow,
  1327. getUid
  1328. };
  1329. }, function(modId) { var map = {}; return __REQUIRE__(map[modId], modId); })
  1330. return __REQUIRE__(1687671528395);
  1331. })()
  1332. //miniprogram-npm-outsideDeps=[]
  1333. //# sourceMappingURL=index.js.map