<template>
	<view class="canvas">
		<view ref="Content" class="theContent">
			<slot></slot>
		</view>
		<canvas canvas-id="myCanvas" id="myCanvas" @onReady="onCanvasReady"
			:style="{ width: width + 'px', height: height + 'px' }"></canvas>
	</view>
</template>
<!-- 
list参数说明：
 图片渲染：
 type: 'image',
 x: X轴位置,
 y: Y轴位置,
 path: 图片路径,
 width: 图片宽度,
 height: 图片高度,
 rotate: 旋转角度
 mode:'center' 居中自适应
 shape: 形状，默认无，可选值：circle 圆形 如果是number,则生成圆角矩形
 area: {x,y,width,height}  // 绘制范围，超出该范围会被剪裁掉 该属性与shape暂时无法同时使用，area存在时，shape失效
矩形渲染：
 type: 'square',
 x: X轴位置,
 y: Y轴位置,
 width: 图片宽度,
 height: 图片高度,
 rotate: 旋转角度
 shape: 形状，默认无，可选值：circle 圆形 如果是number,则生成圆角矩形
 fillStyle:填充的背景样式
 area: {x,y,width,height}  // 绘制范围，超出该范围会被剪裁掉 该属性与shape暂时无法同时使用，area存在时，shape失效
 文字渲染：
 type: 'text',
 x: X轴位置,
 y: Y轴位置,
 text: 文本内容,
 size: 字体大小,
 textBaseline： 基线 默认top  可选值：'top'、'bottom'、'middle'、'normal'
 color: 颜色
 多行文字渲染：
 type: 'textarea',
 x: X轴位置,
 y: Y轴位置,
 width：换行的宽度
 height: 高度，溢出会展示“...”
 lineSpace: 行间距
 text: 文本内容,
 size: 字体大小,
 textBaseline： 基线 默认top  可选值：'top'、'bottom'、'middle'、'normal'
 color: 颜色
 -->
<script>
	let timer = null;
	export default {
		name: "Poster",
		props: {
			// 绘制队列
			list: {
				type: Array,
				required: true,
			},
			// 海报宽度(默认设备宽度放大两倍) 建议都放大两倍
			width: {
				type: [Number, String],
				default: uni.getSystemInfoSync().windowWidth * 2,
			},
			// 海报高度(默认设备高度放大两倍)
			height: {
				type: [Number, String],
				default: uni.getSystemInfoSync().windowHeight * 2,
			},
			//背景颜色
			backgroundColor: {
				type: String,
				default: "rgba(0,0,0,0)",
			},
		},
		data() {
			return {
				posterUrl: "",
				renderList: [],
				ctx: null, //画布上下文
				counter: -1, //计数器
				drawPathQueue: [], //画图路径队列
			};
		},
		watch: {
			drawPathQueue(newVal, oldVal) {
				// 绘制单行文字
				const fillText = (textOptions) => {
					this.ctx.setFillStyle(textOptions.color);
					this.ctx.setFontSize(textOptions.size);
					this.ctx.setTextBaseline(textOptions.textBaseline || "top");
					this.ctx.fillText(textOptions.text, textOptions.x, textOptions.y);
				};
				// 绘制段落
				const fillParagraph = (textOptions) => {
					this.ctx.setFontSize(textOptions.size);
					let tempOptions = JSON.parse(JSON.stringify(textOptions));
					// 如果没有指定行间距则设置默认值
					tempOptions.lineSpace = tempOptions.lineSpace ? tempOptions.lineSpace : 10;
					// 获取字符串
					let str = textOptions.text;
					// 计算指定高度可以输出的最大行数
					let lineCount = Math.floor((tempOptions.height + tempOptions.lineSpace) / (tempOptions.size +
						tempOptions.lineSpace));
					// 初始化单行宽度
					let lineWidth = 0;
					let lastSubStrIndex = 0; //每次开始截取的字符串的索引

					// 构建一个打印数组
					let strArr = str.split("");
					let drawArr = [];
					let text = "";
					while (strArr.length) {
						let word = strArr.shift();
						text += word;
						let textWidth = this.ctx.measureText(text).width;
						if (textWidth > textOptions.width) {
							// 因为超出宽度 所以要截取掉最后一个字符
							text = text.substr(0, text.length - 1);
							drawArr.push(text);
							text = "";
							// 最后一个字还给strArr
							strArr.unshift(word);
						} else if (!strArr.length) {
							drawArr.push(text);
						}
					}

					if (drawArr.length > lineCount) {
						// 超出最大行数
						drawArr.length = lineCount;
						let pointWidth = this.ctx.measureText("...").width;
						let wordWidth = 0;
						let wordArr = drawArr[drawArr.length - 1].split("");
						let words = "";
						while (pointWidth > wordWidth) {
							words += wordArr.pop();
							wordWidth = this.ctx.measureText(words).width;
						}
						drawArr[drawArr.length - 1] = wordArr.join("") + "...";
					}
					// 打印
					for (let i = 0; i < drawArr.length; i++) {
						let _h = i > 0 ? tempOptions.size + tempOptions.lineSpace : 0;
						tempOptions.y = tempOptions.y + _h; // y的位置
						tempOptions.text = drawArr[i]; // 绘制的文本
						fillText(tempOptions);
					}
				};
				const roundRect = (x, y, w, h, r) => {
					// 开始绘制
					this.ctx.beginPath();
					// 因为边缘描边存在锯齿，最好指定使用 transparent 填充
					// 这里是使用 fill 还是 stroke都可以，二选一即可
					this.ctx.setFillStyle("transparent");
					// ctx.setStrokeStyle('transparent')
					// 左上角
					this.ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
					// border-top
					this.ctx.moveTo(x + r, y);
					this.ctx.lineTo(x + w - r, y);
					this.ctx.lineTo(x + w, y + r);
					// 右上角
					this.ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2);

					// border-right
					this.ctx.lineTo(x + w, y + h - r);
					this.ctx.lineTo(x + w - r, y + h);
					// 右下角
					this.ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5);

					// border-bottom
					this.ctx.lineTo(x + r, y + h);
					this.ctx.lineTo(x, y + h - r);
					// 左下角
					this.ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI);
					// border-left
					this.ctx.lineTo(x, y + r);
					this.ctx.lineTo(x + r, y);
					// 这里是使用 fill 还是 stroke都可以，二选一即可，但是需要与上面对应
					this.ctx.fill();
					// ctx.stroke()
					this.ctx.closePath();
					// 剪切
					this.ctx.clip();
				};
				// 图片自适应
				const adaptiveImg = (img, x, y, mode) => {
					const {
						imgW: w,
						imgH: h,
						width: dWidth,
						height: dHeight
					} = img;
					let dw = dWidth / w; //canvas与图片的宽比
					let dh = dHeight / h; //canvas与图片的高比
					// 裁剪图片中间部分
					if ((w > dWidth && h > dHeight) || (w < dWidth && h < dHeight)) {
						if (dw > dh) {
							this.ctx.drawImage(img.path, 0, (h - dHeight / dw) / 2, w, dHeight / dw, x, y, dWidth,
								dHeight);
						} else {
							this.ctx.drawImage(img.path, (w - dWidth / dh) / 2, 0, dWidth / dh, h, x, y, dWidth,
								dHeight);
						}
					} else {
						// 拉伸图片
						if (w < dWidth) {
							this.ctx.drawImage(img.path, 0, (h - dHeight / dw) / 2, w, dHeight / dw, x, y, dWidth,
								dHeight);
						} else {
							this.ctx.drawImage(img.path, (w - dWidth / dh) / 2, 0, dWidth / dh, h, x, y, dWidth,
								dHeight);
						}
					}
					// 裁剪图片中间部分
					// this.ctx.drawImage(img.path, sx, sy, sWidth, sHeight, x, y, dWidth, dHeight);
					// this.ctx.drawImage(img.path, x, y, temp.dWidth, temp.dHeight);
				};
				// 绘制背景
				this.ctx.setFillStyle(this.backgroundColor);
				this.ctx.fillRect(0, 0, this.width, this.height);
				/* 所有元素入队则开始绘制 */
				if (newVal.length === this.renderList.length) {
					if (newVal.length == 0) {
						this.$emit("on-error", {
							msg: "数据为空",
						});
						return;
					}
					try {
						// console.log('生成的队列：' + JSON.stringify(newVal));
						console.log("开始绘制...");
						for (let i = 0; i < this.drawPathQueue.length; i++) {
							for (let j = 0; j < this.drawPathQueue.length; j++) {
								let current = this.drawPathQueue[j];
								/* 按顺序绘制 */
								if (current.index === i) {
									/* 文本绘制 */
									if (current.type === "text") {
										console.log("开始绘制...text");
										fillText(current);
										this.counter--;
									}
									/* 多行文本 */
									if (current.type === "textarea") {
										console.log("开始绘制...textarea");
										fillParagraph(current);
										this.counter--;
									}
									/* 多行文本 */
									if (current.type === "square") {
										console.log("开始绘制...square");
										this.ctx.save(); // 保存上下文，绘制后恢复
										this.ctx.beginPath(); //开始绘制
										//画好了圆 剪切  原始画布中剪切任意形状和尺寸。一旦剪切了某个区域，则所有之后的绘图都会被限制在被剪切的区域内 这也是我们要save上下文的原因
										// 设置旋转中心
										let offsetX = current.x + Number(current.width) / 2;
										let offsetY = current.y + Number(current.height) / 2;
										this.ctx.translate(offsetX, offsetY);
										let degrees = current.rotate ? Number(current.rotate) % 360 : 0;
										this.ctx.rotate((degrees * Math.PI) / 180);
										this.ctx.fillStyle = current.fillStyle;
										this.ctx.fillRect(current.x - offsetX, current.y - offsetY, current.width, current
											.height);
										this.ctx.closePath();
										this.ctx.restore(); // 恢复之前保存的上下文
										this.counter--;
									}
									/* 图片绘制 */
									if (current.type === "image") {
										console.log("开始绘制...image");
										if (current.area) {
											// 绘制绘图区域
											this.ctx.save();
											this.ctx.beginPath(); //开始绘制
											this.ctx.rect(current.area.x, current.area.y, current.area.width, current.area
												.height);
											this.ctx.clip();
											// 设置旋转中心
											let offsetX = current.x + Number(current.width) / 2;
											let offsetY = current.y + Number(current.height) / 2;
											this.ctx.translate(offsetX, offsetY);
											let degrees = current.rotate ? Number(current.rotate) % 360 : 0;
											this.ctx.rotate((degrees * Math.PI) / 180);
											this.ctx.drawImage(current.path, current.x - offsetX, current.y - offsetY,
												current.width, current.height);
											this.ctx.closePath();
											this.ctx.restore(); // 恢复之前保存的上下文
										} else if (current.shape == "circle") {
											this.ctx.save(); // 保存上下文，绘制后恢复
											this.ctx.beginPath(); //开始绘制
											//先画个圆   前两个参数确定了圆心 （x,y） 坐标  第三个参数是圆的半径  四参数是绘图方向  默认是false，即顺时针
											let width = current.width / 2 + current.x;
											let height = current.height / 2 + current.y;
											let r = current.width / 2;
											this.ctx.arc(width, height, r, 0, Math.PI * 2);
											this.ctx.lineTo(current.x, current.y);
											this.ctx.fill();
											//画好了圆 剪切  原始画布中剪切任意形状和尺寸。一旦剪切了某个区域，则所有之后的绘图都会被限制在被剪切的区域内 这也是我们要save上下文的原因
											this.ctx.clip();
											// 设置旋转中心
											let offsetX = current.x + Number(current.width) / 2;
											let offsetY = current.y + Number(current.height) / 2;
											this.ctx.translate(offsetX, offsetY);
											let degrees = current.rotate ? Number(current.rotate) % 360 : 0;
											this.ctx.rotate((degrees * Math.PI) / 180);
											current.mode ?
												adaptiveImg(current, current.x - offsetX, current.y - offsetY, current
													.mode) :
												this.ctx.drawImage(current.path, current.x - offsetX, current.y - offsetY,
													current.width, current.height);
											this.ctx.closePath();
											this.ctx.restore(); // 恢复之前保存的上下文
										} else if (typeof current.shape == "number") {
											this.ctx.save(); // 保存上下文，绘制后恢复
											this.ctx.beginPath(); //开始绘制
											roundRect(current.x, current.y, current.width, current.height, current.shape);
											//画好了圆 剪切 原始画布中剪切任意形状和尺寸。一旦剪切了某个区域，则所有之后的绘图都会被限制在被剪切的区域内 这也是我们要save上下文的原因
											// 设置旋转中心
											let offsetX = current.x + Number(current.width) / 2;
											let offsetY = current.y + Number(current.height) / 2;
											this.ctx.translate(offsetX, offsetY);
											let degrees = current.rotate ? Number(current.rotate) % 360 : 0;
											this.ctx.rotate((degrees * Math.PI) / 180);
											current.mode ?
												adaptiveImg(current, current.x - offsetX, current.y - offsetY, current
													.mode) :
												this.ctx.drawImage(current.path, current.x - offsetX, current.y - offsetY,
													current.width, current.height);
											this.ctx.closePath();
											this.ctx.restore(); // 恢复之前保存的上下文
										} else {
											this.ctx.drawImage(current.path, current.x, current.y, current.width, current
												.height);
										}
										this.counter--;
									}
								}
							}
						}
					} catch (err) {
						console.log(err);
						this.$emit("on-error", err);
					}
				}
			},
			counter(newVal, oldVal) {
				if (newVal === 0) {
					this.ctx.draw(false, (draw) => {
						uni.canvasToTempFilePath({
								canvasId: "myCanvas",
								success: (res) => {
									// 在H5平台下，tempFilePath 为 base64
									console.log("图片已保存至本地：", res.tempFilePath);
									this.posterUrl = res.tempFilePath;
									this.$emit("on-success", res.tempFilePath);
								},
								fail: (error) => {
									this.$emit("on-error", error);
								},
							},
							this
						);
					});
				}
			},
		},
		mounted() {
			// this.generateImg();
			// console.log('mounted');
		},
		methods: {
			onCanvasReady() {
				// #ifdef MP-ALIPAY
				const query = my.createSelectorQuery();
				query
					.select("#myCanvas")
					.node()
					.exec((res) => {
						const canvas = res[0].node;
						const ctx = canvas.getContext("2d");
					});
				// #endif
			},
			/**
			 * @param {*} elClass 元素名称
			 * @param {*} slot 是否采用slot方式
			 * @param {*} startX  X偏移
			 * @param {*} startY Y偏移
			 * @return {*}
			 */
			createForElRect(elClass = "Poster", slot = true, startX = 0, startY = 0) {
				uni
					.createSelectorQuery()
					.selectAll("." + elClass)
					.fields({
							dataset: true,
							size: true,
							rect: true,
							value: true,
							scrollOffset: true,
							properties: ["src", "mode"],
							computedStyle: ["margin", "padding", "backgroundColor", "fontSize", "color", "fontWeight",
								"borderRadius"
							],
							context: true,
						},
						(res) => {
							let list = [];
							const sys = uni.getSystemInfoSync();
							let multiple = sys.windowWidth / this.width;
							res.forEach((val, index) => {
								let src = val.src || val.dataset.enode || "";
								let type = val.src ? "image" : val.dataset.etype || "text";
								let text = val.dataset.enode || "";
								let size = val.fontSize.replace("px", "") || 13;
								let shape = val.borderRadius == "50%" ? "circle" : val.borderRadius.replace("px",
									"") * 2;
								let x = (startX + val.left - (slot ? sys.screenWidth : 0)) / multiple;
								let y = (startY + val.top) / multiple;
								y = (startY + val.top - (slot ? 50 : 0)) / multiple;
								// #ifdef H5
								y = (startY + val.top) / multiple;
								// #endif
								list.push({
									type: type,
									shape,
									text,
									mode: "center",
									x,
									y,
									path: src,
									width: val.width / multiple,
									height: val.height / multiple,
									size: size / multiple,
									color: val.color,
								});
							});
							let canvas = uni.createCanvasContext("myCanvas", this);
							this.ctx = canvas;
							this.renderList = [...this.list, ...list];
							this.generateImg();
						}
					)
					.exec();
			},
			create() {
				let canvas = uni.createCanvasContext("myCanvas", this);
				this.ctx = canvas;
				this.renderList = this.list;
				this.generateImg();
			},
			async generateImg() {
				console.log("generateimg");
				this.counter = this.renderList.length;
				this.drawPathQueue = [];
				const getImgInfo = async (current) => {
					// console.log("current", current);
					return new Promise((resolve, reject) => {
						uni.getImageInfo ?
							uni.getImageInfo({
								src: current.path,
								success: (res) => {
									current.path = res.path;
									current.imgW = res.width;
									current.imgH = res.height;
									// this.drawPathQueue.push(current);
									resolve(current);
								},
							}) :
							resolve(current);
					});
				};
				const delayedLog = async (v, i) => {
					let current = this.renderList[i];
					current.index = i;
					/* 如果是文本直接放入队列 */
					if (current.type === "text" || current.type === "textarea" || current.type === "square") {
						// this.drawPathQueue.push(current);
						return current;
					} else {
						return await getImgInfo(current);
						/* 图片需获取本地缓存path放入队列 */
					}
				};
				const processArray = async (array) => {
					// map array to promises
					const promises = array.map((v, i) => delayedLog(v, i));
					// wait until all promises are resolved
					const allData = await Promise.all(promises);
					console.log("Done!", allData);
					return allData;
				};
				this.drawPathQueue = await processArray(this.renderList);
			},
			saveImg() {
				uni.canvasToTempFilePath({
						canvasId: "myCanvas",
						success: (res) => {
							// 在H5平台下，tempFilePath 为 base64
							uni.saveImageToPhotosAlbum({
								filePath: res.tempFilePath,
								success: () => {
									console.log("save success");
								},
							});
						},
					},
					this
				);
			},
		},
	};
</script>

<style lang="scss" scoped>
	.canvas {
		position: fixed;
		top: 100rpx;
		left: 750rpx;
		width: 100vw;
	}

	.theContent {}
</style>