index.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. // 绘制文字
  2. import DrawText from './methods/text'
  3. // 绘制线条
  4. import DrawLine from './methods/line'
  5. // 绘制矩形
  6. import DrawRect from './methods/rect'
  7. // 绘制圆形
  8. import DrawArc from './methods/arc'
  9. // 绘制图片
  10. import DrawImage, { getModeImage, getImageSrc, getImageInfo } from './methods/image'
  11. // 绘制三角形
  12. import DrawTriangle from './methods/triangle'
  13. // 绘制二维码
  14. import DrawQrcode from './methods/qrcode'
  15. import Canvas from './methods/canvas'
  16. // 公共绘制方法 公共工具方法
  17. import { CommonDrawMethods, CommonUtilMethods, sleep } from './utils/common'
  18. // 绘制海报方法
  19. export default class DrawPoster {
  20. constructor(options) {
  21. const {
  22. width = 375.0001, height = 0, canvasId = null, _this = null, background = {},
  23. type = 'default', unit = 'px', drawDelayTime = 200, exportImageDelayTime = 200,
  24. showDrawTips = true, drawTipsText = '绘制中...', showExportImagesTips = true, exportImageTips = '导出图片中...',
  25. fontStyle = {}, exportImageStyle = {}
  26. } = options
  27. // 画布背景参数
  28. this.background = {
  29. type: 'color',
  30. w: width,
  31. h: height,
  32. color: '#ffffff',
  33. ...background
  34. }
  35. // 画布id
  36. this.canvasId = canvasId
  37. // 当前页面vm
  38. this._this = _this
  39. // 绘制类型 default: 默认 2d: 2d绘制
  40. this.type = type
  41. // h5/APP无论设置什么都为default
  42. // #ifdef H5 || APP-PLUS
  43. this.type = 'default'
  44. // #endif
  45. // 是否是2d绘制
  46. this.is2d = this.type === '2d'
  47. // 绘制单位
  48. this.unit = unit
  49. // 绘制时的延迟时间
  50. this.drawDelayTime = drawDelayTime
  51. // 导出图片时的延迟时间
  52. this.exportImageDelayTime = exportImageDelayTime
  53. // 是否显示绘制时的提示
  54. this.showDrawTips = showDrawTips
  55. // 绘制时的提示
  56. this.drawTipsText = drawTipsText
  57. // 是否显示导出图片时的提示
  58. this.showExportImagesTips = showExportImagesTips
  59. // 导出图片时的提示
  60. this.exportImageTips = exportImageTips
  61. // 默认字体样式
  62. this.fontStyle = {
  63. size: this.type === 'default' ? 16 : 32,
  64. family: 'sans-serif',
  65. style: 'normal',
  66. variant: 'normal',
  67. weight: 'normal',
  68. ...fontStyle
  69. }
  70. // 公共方法实例
  71. this.commonUtilMethods = new CommonUtilMethods({
  72. unit,
  73. type: this.type,
  74. width,
  75. height,
  76. fontStyle: this.fontStyle,
  77. })
  78. // 设置画布宽高
  79. this.setCanvasStyle(width, height)
  80. // 导出图片时的大小/位置, 默认canvas宽高
  81. this.exportImageStyle = {
  82. width,
  83. height,
  84. x: 0,
  85. y: 0,
  86. fileType: 'png',
  87. quality: 1,
  88. ...exportImageStyle,
  89. }
  90. // 所有的callback回调
  91. this.allCallback = []
  92. // 存储所有的监听事件
  93. this.$eventsMap = new Map()
  94. // 创建绘制对象-->Context
  95. this.createContext()
  96. this.drawCallback = null
  97. }
  98. /**
  99. * 创建绘制对象
  100. */
  101. async createContext() {
  102. const { width, height, canvasId, _this, type, is2d } = this
  103. const setValue = (Context, canvas = null) => {
  104. this.Context = Context
  105. this.canvas = canvas
  106. // 公共绘制方法
  107. this.commonDrawMethods = new CommonDrawMethods(Context, type, this.commonUtilMethods)
  108. this.commonUtilMethods.Context = Context
  109. this.commonUtilMethods.canvas = canvas
  110. this.initDrawMethods()
  111. this.$emit('init')
  112. }
  113. const dpr = uni.getSystemInfoSync().pixelRatio
  114. this.dpr = dpr
  115. if (!is2d) {
  116. const Context = uni.createCanvasContext(canvasId, _this)
  117. await sleep(50)
  118. setValue(Context)
  119. } else {
  120. uni.createSelectorQuery()
  121. .select(`#${canvasId}`)
  122. .fields({ node: true, size: true })
  123. .exec(res => {
  124. const canvas = res[0].node
  125. const Context = canvas.getContext(type)
  126. canvas.width = this.commonUtilMethods.getConvertedValue(width) * dpr
  127. canvas.height = this.commonUtilMethods.getConvertedValue(height) * dpr
  128. Context.scale(dpr, dpr)
  129. setValue(Context, canvas)
  130. })
  131. }
  132. }
  133. /**
  134. * 初始化绘制方法
  135. */
  136. initDrawMethods() {
  137. const { Context, commonUtilMethods, commonDrawMethods } = this
  138. const initParams = {
  139. Context, commonUtilMethods, commonDrawMethods
  140. }
  141. // 创建canvas方法->导出图片之类的
  142. this.canvasMethods = new Canvas(this)
  143. // 绘制文字
  144. const drawText = new DrawText(initParams)
  145. // 绘制线条
  146. const drawLine = new DrawLine(initParams)
  147. // 绘制矩形
  148. const drawRect = new DrawRect(initParams)
  149. // 绘制圆形
  150. const drawArc = new DrawArc(initParams)
  151. // 绘制图片
  152. const drawImage = new DrawImage(initParams)
  153. // 绘制三角形
  154. const drawTriangle = new DrawTriangle(initParams)
  155. // 绘制二维码
  156. const drawQrcode = new DrawQrcode(initParams)
  157. // 绘制方法集合
  158. this.commonDrawMethods.drawParams = {
  159. drawText,
  160. drawLine,
  161. drawRect,
  162. drawArc,
  163. drawImage,
  164. drawTriangle,
  165. drawQrcode
  166. }
  167. this.drawText = drawText
  168. this.drawLine = drawLine
  169. this.drawRect = drawRect
  170. this.drawArc = drawArc
  171. this.drawImage = drawImage
  172. this.drawTriangle = drawTriangle
  173. this.drawQrcode = drawQrcode
  174. }
  175. /**
  176. * 设置导出图片的大小
  177. */
  178. setExportImageStyle(options) {
  179. this.exportImageStyle = {
  180. ...this.exportImageStyle,
  181. ...options
  182. }
  183. }
  184. /**
  185. * 设置背景图片样式
  186. * @param { Object } options
  187. */
  188. setBackgroundStyle(options) {
  189. this.background = {
  190. ...this.background,
  191. ...options
  192. }
  193. }
  194. /**
  195. * 设置画布宽高
  196. * @param { Number } width 宽度
  197. * @param { Number } height 高度
  198. */
  199. async setCanvasStyle(width, height) {
  200. this.width = width
  201. this.height = height
  202. this.commonUtilMethods.width = width
  203. this.commonUtilMethods.height = height
  204. this.commonUtilMethods.setCanvasStyle()
  205. // 回调
  206. this.callbackInfo = {
  207. bgObj: {
  208. width: this.background.w,
  209. height: this.background.h
  210. },
  211. ctxObj: {
  212. width,
  213. height
  214. },
  215. }
  216. this.setExportImageStyle({
  217. width,
  218. height
  219. })
  220. if (this.canvas) {
  221. const { dpr, canvas, Context, commonUtilMethods } = this
  222. // const canvasData = Context.getImageData(0, 0, canvas.width, canvas.height)
  223. canvas.width = commonUtilMethods.getConvertedValue(width) * dpr
  224. canvas.height = commonUtilMethods.getConvertedValue(height) * dpr
  225. Context.scale(dpr, dpr)
  226. // Context.putImageData(canvasData, 0, 0)
  227. await this.drawBackground(false)
  228. if (this.drawArrayFn) {
  229. await this.drawCanvas(this.drawArrayFn(this.callbackInfo), false)
  230. }
  231. }
  232. }
  233. /**
  234. * 监听绘制时/绘制完成的事件
  235. * @param { String } type
  236. * @param { Function } callback 回调
  237. */
  238. $on(type = '', callback = null) {
  239. if (!type || !callback) return
  240. if (typeof callback !== 'function') return
  241. let eventSet = this.$eventsMap.get(type)
  242. if (!eventSet) {
  243. this.$eventsMap.set(type, (eventSet = new Set()))
  244. }
  245. eventSet.add(callback)
  246. }
  247. /**
  248. * 触发用户传入的事件
  249. * @param { String } type 什么类型
  250. * @param { Array } args 其余参数
  251. */
  252. $emit(type = '', ...args) {
  253. if (!type) return
  254. const eventSet = this.$eventsMap.get(type)
  255. if (eventSet) {
  256. eventSet.forEach(event => {
  257. event.apply(this, args)
  258. })
  259. }
  260. }
  261. /**
  262. * 绘制背景
  263. * @param { Boolean } isSendEvent 是否发送绘制完成事件
  264. * @returns
  265. */
  266. drawBackground(isSendEvent = true) {
  267. const { background, width, height, Context, drawRect, drawImage, unit, commonUtilMethods } = this
  268. // console.log(Context)
  269. return new Promise(async resolve => {
  270. const { type, ...params } = background
  271. // Context.beginPath()
  272. // Context.save()
  273. // console.log({
  274. // ...params,
  275. // w: params.w ?? width,
  276. // h: params.h ?? height,
  277. // })
  278. if (!isSendEvent) {
  279. this.canvasMethods.clearCanvas()
  280. }
  281. let result = {}
  282. if (type === 'color') {
  283. result = drawRect.draw({
  284. ...params,
  285. w: params.w ?? width,
  286. h: params.h ?? height,
  287. color: params.color ?? '#ffffff'
  288. })
  289. } else if (type === 'image') {
  290. result = await drawImage.draw({
  291. ...params,
  292. w: params.w ?? width,
  293. h: params.h ?? height,
  294. })
  295. }
  296. result.style = {
  297. width: result.w + unit,
  298. height: result.h + unit,
  299. }
  300. this.background.w = result.w
  301. this.background.h = result.h
  302. if (isSendEvent) {
  303. this.$emit('background', result)
  304. }
  305. // Context.clip()
  306. // Context.restore()
  307. resolve(result)
  308. })
  309. }
  310. /**
  311. * 往所有的回调信息里面添加内容
  312. * @param { Object } params 内容
  313. */
  314. async setAllCallBack(params) {
  315. const { width, commonUtilMethods, commonDrawMethods } = this
  316. const { canvasWidth } = commonUtilMethods
  317. let {
  318. type, x = 0, y = 0, r = 0, w = canvasWidth, h = 0,
  319. lineWidth = 1, size = 0, name = '', windowAlign = 'none',
  320. drawType = 'default', mode = 'aspectFill', src = '', offsetRight = 0
  321. } = params
  322. let sx = x
  323. let sy = y
  324. let ex = x + w
  325. let ey = y + h
  326. // 文字
  327. if (type === 'text') {
  328. let {
  329. text, textIndent, lastWidth, font, line, textAlign, windowAlign, color, highlightText
  330. } = this.drawText.getDrawParams(params, false)
  331. const textArr = commonDrawMethods.computedFontTextLineHeight({
  332. x, y, w, text, textIndent, lastWidth, font, line, textAlign, windowAlign, defaultColor: color, offsetRight, highlightText
  333. })
  334. const lastText = textArr[textArr.length - 1]
  335. const firstText = textArr[0]
  336. ey = lastText.ey
  337. ex = firstText.tx + firstText.w
  338. params.textArr = commonUtilMethods.conversionUnit(textArr)
  339. params.h = ey - sy
  340. params.tw = firstText.w
  341. // if (params.name) {
  342. // console.log(params.text)
  343. // console.log(font)
  344. // console.log(textArr)
  345. // }
  346. } else if (type === 'arc') {
  347. ex = x + r * 2
  348. ey = y + r * 2
  349. w = r * 2
  350. h = r * 2
  351. } else if (type === 'line') {
  352. ey = y + lineWidth
  353. h = lineWidth
  354. } else if (type === 'qrcode') {
  355. ex = x + size
  356. ey = y + size
  357. w = size
  358. h = size
  359. } else if (type === 'image') {
  360. if (windowAlign !== 'none') {
  361. x = commonDrawMethods.computedCenterX(width, w, windowAlign, offsetRight)
  362. }
  363. const srcRes = await getImageSrc(src)
  364. if (!srcRes.success) {
  365. return srcRes
  366. }
  367. src = srcRes.src
  368. const imageInfo = await getImageInfo(src)
  369. if (!imageInfo.success) {
  370. return Promise.resolve(true)
  371. }
  372. const modeImage = getModeImage(Number(imageInfo.width), Number(imageInfo.height), commonUtilMethods.getConvertedValue(x), commonUtilMethods.getConvertedValue(y), commonUtilMethods.getConvertedValue(w), commonUtilMethods.getConvertedValue(h), mode)
  373. const { dx, dy, dw, dh, sw, sh, sx, sy } = modeImage
  374. if (mode === 'widthFix') {
  375. h = this.unit === 'rpx' ? commonUtilMethods.pxToRpx(sh) : sh
  376. ey = y + h
  377. } else if (mode === 'heightFix') {
  378. w = this.unit === 'rpx' ? commonUtilMethods.pxToRpx(sw) : sw
  379. ex = x + w
  380. }
  381. if (drawType === 'arc' && h == 0) {
  382. h = w
  383. ey = y + h
  384. }
  385. params.drawModeImage = modeImage
  386. params.drawSrc = src
  387. params.drawImageInfo = imageInfo
  388. }
  389. params.sx = sx
  390. params.sy = sy
  391. params.ex = ex
  392. params.ey = ey
  393. this.allCallback.push({
  394. sx,
  395. sy,
  396. ex,
  397. ey,
  398. w,
  399. h,
  400. name,
  401. before: params.before || {}
  402. })
  403. return Promise.resolve(true)
  404. }
  405. /**
  406. * 绘制内容
  407. * @param { Array } drawArray 绘制数组
  408. * @param { Boolean } isSendEvent 是否发送事件
  409. */
  410. drawCanvas(drawArray, isSendEvent = true) {
  411. const { Context, showDrawTips, showExportImagesTips } = this
  412. return new Promise(async resolve => {
  413. try {
  414. for (let i of drawArray) {
  415. if (i.callback && typeof i.callback === 'function' && i.type !== 'custom') {
  416. const beforeInfo = this.allCallback.length == 0 ? {} : this.allCallback[this.allCallback.length - 1]
  417. const callBackInfo = i.callback(beforeInfo, this.allCallback) || {}
  418. const { callback, ...data } = i
  419. i = { ...data, ...callBackInfo }
  420. }
  421. if (i.type !== 'custom' && i.drawType !== 'custom') {
  422. await this.setAllCallBack(i)
  423. }
  424. switch (i.type) {
  425. // 文字
  426. case 'text':
  427. this.drawText.draw(i)
  428. break
  429. // 矩形
  430. case 'rect':
  431. this.drawRect.draw(i)
  432. break
  433. // 图片
  434. case 'image':
  435. const image = await this.drawImage.draw(i)
  436. if (!image.success) {
  437. return resolve(image)
  438. }
  439. break
  440. // 圆形
  441. case 'arc':
  442. this.drawArc.draw(i)
  443. break
  444. // 三角形
  445. case 'triangle':
  446. this.drawTriangle.draw(i)
  447. break
  448. // 线条
  449. case 'line':
  450. this.drawLine.draw(i)
  451. break
  452. // 二维码
  453. case 'qrcode':
  454. await this.drawQrcode.draw(i)
  455. break
  456. // 自定义
  457. case 'custom':
  458. i.setDraw(Context, this)
  459. break
  460. }
  461. }
  462. resolve({
  463. success: true
  464. })
  465. if (!isSendEvent) return
  466. // 绘制完成触发完成事件
  467. const { width, height, } = this
  468. const contentHeight = this.allCallback.reduce((a, data) => Math.max(a, data.ey), 0)
  469. const contentWidth = this.allCallback.reduce((a, data) => Math.max(a, data.ex), 0)
  470. this.$emit('drawComplete', {
  471. width, height,
  472. contentHeight: contentHeight,
  473. contentWidth: contentWidth
  474. })
  475. } catch (e) {
  476. return resolve({
  477. success: false,
  478. msg: '绘制内容失败:' + e
  479. })
  480. } finally {
  481. if (showDrawTips || showExportImagesTips) {
  482. uni.hideLoading()
  483. }
  484. }
  485. })
  486. }
  487. /**
  488. * 绘制海报
  489. * @param { Function } drawArrayFn 绘制内容 返回一个数组
  490. */
  491. drawPoster(drawArrayFn) {
  492. const { callbackInfo, showDrawTips, drawTipsText, showExportImagesTips } = this
  493. return new Promise(async resolve => {
  494. if (!drawArrayFn || typeof drawArrayFn !== 'function') {
  495. return resolve({
  496. success: false,
  497. msg: 'drawPoster参数错误'
  498. })
  499. }
  500. if (this.drawArrayFn) {
  501. this.allCallback = []
  502. this.canvasMethods.clearCanvas()
  503. await this.drawBackground(false)
  504. }
  505. this.drawArrayFn = drawArrayFn
  506. showDrawTips && uni.showLoading({ title: drawTipsText })
  507. const result = await this.drawCanvas(drawArrayFn(callbackInfo))
  508. // 绘制内容
  509. if (!result.success) {
  510. return resolve(result)
  511. }
  512. resolve(await this.canvasMethods.canvasDraw())
  513. if (showDrawTips || showExportImagesTips) {
  514. uni.hideLoading()
  515. }
  516. })
  517. }
  518. }