Commit 5bfa8ba818fe83f4a82d6c2fa5ee80555137c914

Authored by taiyuan
1 parent 5a685934

组合购

packageB/components/painter/lib/downloader.js 0 → 100644
  1 +/**
  2 + * LRU 文件存储,使用该 downloader 可以让下载的文件存储在本地,下次进入小程序后可以直接使用
  3 + * 详细设计文档可查看 https://juejin.im/post/5b42d3ede51d4519277b6ce3
  4 + */
  5 +const util = require('./util');
  6 +
  7 +const SAVED_FILES_KEY = 'savedFiles';
  8 +const KEY_TOTAL_SIZE = 'totalSize';
  9 +const KEY_PATH = 'path';
  10 +const KEY_TIME = 'time';
  11 +const KEY_SIZE = 'size';
  12 +
  13 +// 可存储总共为 6M,目前小程序可允许的最大本地存储为 10M
  14 +let MAX_SPACE_IN_B = 6 * 1024 * 1024;
  15 +let savedFiles = {};
  16 +
  17 +export default class Dowloader {
  18 + constructor() {
  19 + // app 如果设置了最大存储空间,则使用 app 中的
  20 + if (getApp().PAINTER_MAX_LRU_SPACE) {
  21 + MAX_SPACE_IN_B = getApp().PAINTER_MAX_LRU_SPACE;
  22 + }
  23 + wx.getStorage({
  24 + key: SAVED_FILES_KEY,
  25 + success: function (res) {
  26 + if (res.data) {
  27 + savedFiles = res.data;
  28 + }
  29 + },
  30 + });
  31 + }
  32 +
  33 + /**
  34 + * 下载文件,会用 lru 方式来缓存文件到本地
  35 + * @param {String} url 文件的 url
  36 + */
  37 + download(url, lru) {
  38 + return new Promise((resolve, reject) => {
  39 + if (!(url && util.isValidUrl(url))) {
  40 + resolve(url);
  41 + return;
  42 + }
  43 + if (!lru) {
  44 + // 无 lru 情况下直接判断 临时文件是否存在,不存在重新下载
  45 + wx.getFileInfo({
  46 + filePath: url,
  47 + success: () => {
  48 + resolve(url);
  49 + },
  50 + fail: () => {
  51 + downloadFile(url, lru).then((path) => {
  52 + resolve(path);
  53 + }, () => {
  54 + reject();
  55 + });
  56 + },
  57 + })
  58 + return
  59 + }
  60 +
  61 + const file = getFile(url);
  62 +
  63 + if (file) {
  64 + // 检查文件是否正常,不正常需要重新下载
  65 + wx.getSavedFileInfo({
  66 + filePath: file[KEY_PATH],
  67 + success: (res) => {
  68 + resolve(file[KEY_PATH]);
  69 + },
  70 + fail: (error) => {
  71 + console.error(`the file is broken, redownload it, ${JSON.stringify(error)}`);
  72 + downloadFile(url, lru).then((path) => {
  73 + resolve(path);
  74 + }, () => {
  75 + reject();
  76 + });
  77 + },
  78 + });
  79 + } else {
  80 + downloadFile(url, lru).then((path) => {
  81 + resolve(path);
  82 + }, () => {
  83 + reject();
  84 + });
  85 + }
  86 + });
  87 + }
  88 +}
  89 +
  90 +function downloadFile(url, lru) {
  91 + return new Promise((resolve, reject) => {
  92 + wx.downloadFile({
  93 + url: url,
  94 + success: function (res) {
  95 + if (res.statusCode !== 200) {
  96 + console.error(`downloadFile ${url} failed res.statusCode is not 200`);
  97 + reject();
  98 + return;
  99 + }
  100 + const {
  101 + tempFilePath
  102 + } = res;
  103 + wx.getFileInfo({
  104 + filePath: tempFilePath,
  105 + success: (tmpRes) => {
  106 + const newFileSize = tmpRes.size;
  107 + lru ? doLru(newFileSize).then(() => {
  108 + saveFile(url, newFileSize, tempFilePath).then((filePath) => {
  109 + resolve(filePath);
  110 + });
  111 + }, () => {
  112 + resolve(tempFilePath);
  113 + }) : resolve(tempFilePath);
  114 + },
  115 + fail: (error) => {
  116 + // 文件大小信息获取失败,则此文件也不要进行存储
  117 + console.error(`getFileInfo ${res.tempFilePath} failed, ${JSON.stringify(error)}`);
  118 + resolve(res.tempFilePath);
  119 + },
  120 + });
  121 + },
  122 + fail: function (error) {
  123 + console.error(`downloadFile failed, ${JSON.stringify(error)} `);
  124 + reject();
  125 + },
  126 + });
  127 + });
  128 +}
  129 +
  130 +function saveFile(key, newFileSize, tempFilePath) {
  131 + return new Promise((resolve, reject) => {
  132 + wx.saveFile({
  133 + tempFilePath: tempFilePath,
  134 + success: (fileRes) => {
  135 + const totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0;
  136 + savedFiles[key] = {};
  137 + savedFiles[key][KEY_PATH] = fileRes.savedFilePath;
  138 + savedFiles[key][KEY_TIME] = new Date().getTime();
  139 + savedFiles[key][KEY_SIZE] = newFileSize;
  140 + savedFiles['totalSize'] = newFileSize + totalSize;
  141 + wx.setStorage({
  142 + key: SAVED_FILES_KEY,
  143 + data: savedFiles,
  144 + });
  145 + resolve(fileRes.savedFilePath);
  146 + },
  147 + fail: (error) => {
  148 + console.error(`saveFile ${key} failed, then we delete all files, ${JSON.stringify(error)}`);
  149 + // 由于 saveFile 成功后,res.tempFilePath 处的文件会被移除,所以在存储未成功时,我们还是继续使用临时文件
  150 + resolve(tempFilePath);
  151 + // 如果出现错误,就直接情况本地的所有文件,因为你不知道是不是因为哪次lru的某个文件未删除成功
  152 + reset();
  153 + },
  154 + });
  155 + });
  156 +}
  157 +
  158 +/**
  159 + * 清空所有下载相关内容
  160 + */
  161 +function reset() {
  162 + wx.removeStorage({
  163 + key: SAVED_FILES_KEY,
  164 + success: () => {
  165 + wx.getSavedFileList({
  166 + success: (listRes) => {
  167 + removeFiles(listRes.fileList);
  168 + },
  169 + fail: (getError) => {
  170 + console.error(`getSavedFileList failed, ${JSON.stringify(getError)}`);
  171 + },
  172 + });
  173 + },
  174 + });
  175 +}
  176 +
  177 +function doLru(size) {
  178 + if (size > MAX_SPACE_IN_B) {
  179 + return Promise.reject()
  180 + }
  181 + return new Promise((resolve, reject) => {
  182 + let totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0;
  183 +
  184 + if (size + totalSize <= MAX_SPACE_IN_B) {
  185 + resolve();
  186 + return;
  187 + }
  188 + // 如果加上新文件后大小超过最大限制,则进行 lru
  189 + const pathsShouldDelete = [];
  190 + // 按照最后一次的访问时间,从小到大排序
  191 + const allFiles = JSON.parse(JSON.stringify(savedFiles));
  192 + delete allFiles[KEY_TOTAL_SIZE];
  193 + const sortedKeys = Object.keys(allFiles).sort((a, b) => {
  194 + return allFiles[a][KEY_TIME] - allFiles[b][KEY_TIME];
  195 + });
  196 +
  197 + for (const sortedKey of sortedKeys) {
  198 + totalSize -= savedFiles[sortedKey].size;
  199 + pathsShouldDelete.push(savedFiles[sortedKey][KEY_PATH]);
  200 + delete savedFiles[sortedKey];
  201 + if (totalSize + size < MAX_SPACE_IN_B) {
  202 + break;
  203 + }
  204 + }
  205 +
  206 + savedFiles['totalSize'] = totalSize;
  207 +
  208 + wx.setStorage({
  209 + key: SAVED_FILES_KEY,
  210 + data: savedFiles,
  211 + success: () => {
  212 + // 保证 storage 中不会存在不存在的文件数据
  213 + if (pathsShouldDelete.length > 0) {
  214 + removeFiles(pathsShouldDelete);
  215 + }
  216 + resolve();
  217 + },
  218 + fail: (error) => {
  219 + console.error(`doLru setStorage failed, ${JSON.stringify(error)}`);
  220 + reject();
  221 + },
  222 + });
  223 + });
  224 +}
  225 +
  226 +function removeFiles(pathsShouldDelete) {
  227 + for (const pathDel of pathsShouldDelete) {
  228 + let delPath = pathDel;
  229 + if (typeof pathDel === 'object') {
  230 + delPath = pathDel.filePath;
  231 + }
  232 + wx.removeSavedFile({
  233 + filePath: delPath,
  234 + fail: (error) => {
  235 + console.error(`removeSavedFile ${pathDel} failed, ${JSON.stringify(error)}`);
  236 + },
  237 + });
  238 + }
  239 +}
  240 +
  241 +function getFile(key) {
  242 + if (!savedFiles[key]) {
  243 + return;
  244 + }
  245 + savedFiles[key]['time'] = new Date().getTime();
  246 + wx.setStorage({
  247 + key: SAVED_FILES_KEY,
  248 + data: savedFiles,
  249 + });
  250 + return savedFiles[key];
  251 +}
0 252 \ No newline at end of file
... ...
packageB/components/painter/lib/gradient.js 0 → 100644
  1 +/* eslint-disable */
  2 +// 当ctx传入当前文件,const grd = ctx.createCircularGradient() 和
  3 +// const grd = this.ctx.createLinearGradient() 无效,因此只能分开处理
  4 +// 先分析,在外部创建grd,再传入使用就可以
  5 +
  6 +!(function () {
  7 +
  8 + var api = {
  9 + isGradient: function(bg) {
  10 + if (bg && (bg.startsWith('linear') || bg.startsWith('radial'))) {
  11 + return true;
  12 + }
  13 + return false;
  14 + },
  15 +
  16 + doGradient: function(bg, width, height, ctx) {
  17 + if (bg.startsWith('linear')) {
  18 + linearEffect(width, height, bg, ctx);
  19 + } else if (bg.startsWith('radial')) {
  20 + radialEffect(width, height, bg, ctx);
  21 + }
  22 + },
  23 + }
  24 +
  25 + function analizeGrad(string) {
  26 + const colorPercents = string.substring(0, string.length - 1).split("%,");
  27 + const colors = [];
  28 + const percents = [];
  29 + for (let colorPercent of colorPercents) {
  30 + colors.push(colorPercent.substring(0, colorPercent.lastIndexOf(" ")).trim());
  31 + percents.push(colorPercent.substring(colorPercent.lastIndexOf(" "), colorPercent.length) / 100);
  32 + }
  33 + return {colors: colors, percents: percents};
  34 + }
  35 +
  36 + function radialEffect(width, height, bg, ctx) {
  37 + const colorPer = analizeGrad(bg.match(/radial-gradient\((.+)\)/)[1]);
  38 + const grd = ctx.createRadialGradient(0, 0, 0, 0, 0, width < height ? height / 2 : width / 2);
  39 + for (let i = 0; i < colorPer.colors.length; i++) {
  40 + grd.addColorStop(colorPer.percents[i], colorPer.colors[i]);
  41 + }
  42 + ctx.fillStyle = grd;
  43 + //ctx.fillRect(-(width / 2), -(height / 2), width, height);
  44 + }
  45 +
  46 + function analizeLinear(bg, width, height) {
  47 + const direction = bg.match(/([-]?\d{1,3})deg/);
  48 + const dir = direction && direction[1] ? parseFloat(direction[1]) : 0;
  49 + let coordinate;
  50 + switch (dir) {
  51 + case 0: coordinate = [0, -height / 2, 0, height / 2]; break;
  52 + case 90: coordinate = [width / 2, 0, -width / 2, 0]; break;
  53 + case -90: coordinate = [-width / 2, 0, width / 2, 0]; break;
  54 + case 180: coordinate = [0, height / 2, 0, -height / 2]; break;
  55 + case -180: coordinate = [0, -height / 2, 0, height / 2]; break;
  56 + default:
  57 + let x1 = 0;
  58 + let y1 = 0;
  59 + let x2 = 0;
  60 + let y2 = 0;
  61 + if (direction[1] > 0 && direction[1] < 90) {
  62 + x1 = (width / 2) - ((width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (90 - direction[1]) * Math.PI * 2 / 360) / 2;
  63 + y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
  64 + x2 = -x1;
  65 + y1 = -y2;
  66 + } else if (direction[1] > -180 && direction[1] < -90) {
  67 + x1 = -(width / 2) + ((width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (90 - direction[1]) * Math.PI * 2 / 360) / 2;
  68 + y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
  69 + x2 = -x1;
  70 + y1 = -y2;
  71 + } else if (direction[1] > 90 && direction[1] < 180) {
  72 + x1 = (width / 2) + (-(width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (90 - direction[1]) * Math.PI * 2 / 360) / 2;
  73 + y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
  74 + x2 = -x1;
  75 + y1 = -y2;
  76 + } else {
  77 + x1 = -(width / 2) - (-(width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (90 - direction[1]) * Math.PI * 2 / 360) / 2;
  78 + y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
  79 + x2 = -x1;
  80 + y1 = -y2;
  81 + }
  82 + coordinate = [x1, y1, x2, y2];
  83 + break;
  84 + }
  85 + return coordinate;
  86 + }
  87 +
  88 + function linearEffect(width, height, bg, ctx) {
  89 + const param = analizeLinear(bg, width, height);
  90 + const grd = ctx.createLinearGradient(param[0], param[1], param[2], param[3]);
  91 + const content = bg.match(/linear-gradient\((.+)\)/)[1];
  92 + const colorPer = analizeGrad(content.substring(content.indexOf(',') + 1));
  93 + for (let i = 0; i < colorPer.colors.length; i++) {
  94 + grd.addColorStop(colorPer.percents[i], colorPer.colors[i]);
  95 + }
  96 + ctx.fillStyle = grd
  97 + //ctx.fillRect(-(width / 2), -(height / 2), width, height);
  98 + }
  99 +
  100 + module.exports = { api }
  101 +
  102 +})();
... ...
packageB/components/painter/lib/pen.js 0 → 100644
  1 +const QR = require('./qrcode.js');
  2 +const GD = require('./gradient.js');
  3 +
  4 +export default class Painter {
  5 + constructor(ctx, data) {
  6 + this.ctx = ctx;
  7 + this.data = data;
  8 + this.globalWidth = {};
  9 + this.globalHeight = {};
  10 + }
  11 +
  12 + isMoving = false
  13 + movingCache = {}
  14 + paint(callback, isMoving, movingCache) {
  15 + this.style = {
  16 + width: this.data.width.toPx(),
  17 + height: this.data.height.toPx(),
  18 + };
  19 + if (isMoving) {
  20 + this.isMoving = true
  21 + this.movingCache = movingCache
  22 + }
  23 + this._background();
  24 + for (const view of this.data.views) {
  25 + this._drawAbsolute(view);
  26 + }
  27 + this.ctx.draw(false, () => {
  28 + callback && callback(this.callbackInfo);
  29 + });
  30 + }
  31 +
  32 + _background() {
  33 + this.ctx.save();
  34 + const {
  35 + width,
  36 + height,
  37 + } = this.style;
  38 + const bg = this.data.background;
  39 + this.ctx.translate(width / 2, height / 2);
  40 +
  41 + this._doClip(this.data.borderRadius, width, height);
  42 + if (!bg) {
  43 + // 如果未设置背景,则默认使用透明色
  44 + this.ctx.fillStyle = 'transparent';
  45 + this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
  46 + } else if (bg.startsWith('#') || bg.startsWith('rgba') || bg.toLowerCase() === 'transparent') {
  47 + // 背景填充颜色
  48 + this.ctx.fillStyle = bg;
  49 + this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
  50 + } else if (GD.api.isGradient(bg)) {
  51 + GD.api.doGradient(bg, width, height, this.ctx);
  52 + this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
  53 + } else {
  54 + // 背景填充图片
  55 + this.ctx.drawImage(bg, -(width / 2), -(height / 2), width, height);
  56 + }
  57 + this.ctx.restore();
  58 + }
  59 +
  60 + _drawAbsolute(view) {
  61 + if (!(view && view.type)) {
  62 + // 过滤无效 view
  63 + return
  64 + }
  65 + // 证明 css 为数组形式,需要合并
  66 + if (view.css && view.css.length) {
  67 + /* eslint-disable no-param-reassign */
  68 + view.css = Object.assign(...view.css);
  69 + }
  70 + switch (view.type) {
  71 + case 'image':
  72 + this._drawAbsImage(view);
  73 + break;
  74 + case 'text':
  75 + this._fillAbsText(view);
  76 + break;
  77 + case 'rect':
  78 + this._drawAbsRect(view);
  79 + break;
  80 + case 'qrcode':
  81 + this._drawQRCode(view);
  82 + break;
  83 + default:
  84 + break;
  85 + }
  86 + }
  87 +
  88 + _border({
  89 + borderRadius = 0,
  90 + width,
  91 + height,
  92 + borderWidth = 0,
  93 + borderStyle = 'solid'
  94 + }) {
  95 + let r1 = 0,
  96 + r2 = 0,
  97 + r3 = 0,
  98 + r4 = 0
  99 + const minSize = Math.min(width, height);
  100 + if (borderRadius) {
  101 + const border = borderRadius.split(/\s+/)
  102 + if (border.length === 4) {
  103 + r1 = Math.min(border[0].toPx(false, minSize), width / 2, height / 2);
  104 + r2 = Math.min(border[1].toPx(false, minSize), width / 2, height / 2);
  105 + r3 = Math.min(border[2].toPx(false, minSize), width / 2, height / 2);
  106 + r4 = Math.min(border[3].toPx(false, minSize), width / 2, height / 2);
  107 + } else {
  108 + r1 = r2 = r3 = r4 = Math.min(borderRadius && borderRadius.toPx(false, minSize), width / 2, height / 2);
  109 + }
  110 + }
  111 + const lineWidth = borderWidth && borderWidth.toPx(false, minSize);
  112 + this.ctx.lineWidth = lineWidth;
  113 + if (borderStyle === 'dashed') {
  114 + this.ctx.setLineDash([lineWidth * 4 / 3, lineWidth * 4 / 3]);
  115 + // this.ctx.lineDashOffset = 2 * lineWidth
  116 + } else if (borderStyle === 'dotted') {
  117 + this.ctx.setLineDash([lineWidth, lineWidth]);
  118 + }
  119 + const notSolid = borderStyle !== 'solid'
  120 + this.ctx.beginPath();
  121 +
  122 + notSolid && r1 === 0 && this.ctx.moveTo(-width / 2 - lineWidth, -height / 2 - lineWidth / 2) // 顶边虚线规避重叠规则
  123 + r1 !== 0 && this.ctx.arc(-width / 2 + r1, -height / 2 + r1, r1 + lineWidth / 2, 1 * Math.PI, 1.5 * Math.PI); //左上角圆弧
  124 + this.ctx.lineTo(r2 === 0 ? notSolid ? width / 2 : width / 2 + lineWidth / 2 : width / 2 - r2, -height / 2 - lineWidth / 2); // 顶边线
  125 +
  126 + notSolid && r2 === 0 && this.ctx.moveTo(width / 2 + lineWidth / 2, -height / 2 - lineWidth) // 右边虚线规避重叠规则
  127 + r2 !== 0 && this.ctx.arc(width / 2 - r2, -height / 2 + r2, r2 + lineWidth / 2, 1.5 * Math.PI, 2 * Math.PI); // 右上角圆弧
  128 + this.ctx.lineTo(width / 2 + lineWidth / 2, r3 === 0 ? notSolid ? height / 2 : height / 2 + lineWidth / 2 : height / 2 - r3); // 右边线
  129 +
  130 + notSolid && r3 === 0 && this.ctx.moveTo(width / 2 + lineWidth, height / 2 + lineWidth / 2) // 底边虚线规避重叠规则
  131 + r3 !== 0 && this.ctx.arc(width / 2 - r3, height / 2 - r3, r3 + lineWidth / 2, 0, 0.5 * Math.PI); // 右下角圆弧
  132 + this.ctx.lineTo(r4 === 0 ? notSolid ? -width / 2 : -width / 2 - lineWidth / 2 : -width / 2 + r4, height / 2 + lineWidth / 2); // 底边线
  133 +
  134 + notSolid && r4 === 0 && this.ctx.moveTo(-width / 2 - lineWidth / 2, height / 2 + lineWidth) // 左边虚线规避重叠规则
  135 + r4 !== 0 && this.ctx.arc(-width / 2 + r4, height / 2 - r4, r4 + lineWidth / 2, 0.5 * Math.PI, 1 * Math.PI); // 左下角圆弧
  136 + this.ctx.lineTo(-width / 2 - lineWidth / 2, r1 === 0 ? notSolid ? -height / 2 : -height / 2 - lineWidth / 2 : -height / 2 + r1); // 左边线
  137 + notSolid && r1 === 0 && this.ctx.moveTo(-width / 2 - lineWidth, -height / 2 - lineWidth / 2) // 顶边虚线规避重叠规则
  138 +
  139 + if (!notSolid) {
  140 + this.ctx.closePath();
  141 + }
  142 + }
  143 +
  144 + /**
  145 + * 根据 borderRadius 进行裁减
  146 + */
  147 + _doClip(borderRadius, width, height, borderStyle) {
  148 + if (borderRadius && width && height) {
  149 + // 防止在某些机型上周边有黑框现象,此处如果直接设置 fillStyle 为透明,在 Android 机型上会导致被裁减的图片也变为透明, iOS 和 IDE 上不会
  150 + // globalAlpha 在 1.9.90 起支持,低版本下无效,但把 fillStyle 设为了 white,相对默认的 black 要好点
  151 + this.ctx.globalAlpha = 0;
  152 + this.ctx.fillStyle = 'white';
  153 + this._border({
  154 + borderRadius,
  155 + width,
  156 + height,
  157 + borderStyle
  158 + })
  159 + this.ctx.fill();
  160 + // 在 ios 的 6.6.6 版本上 clip 有 bug,禁掉此类型上的 clip,也就意味着,在此版本微信的 ios 设备下无法使用 border 属性
  161 + if (!(getApp().systemInfo &&
  162 + getApp().systemInfo.version <= '6.6.6' &&
  163 + getApp().systemInfo.platform === 'ios')) {
  164 + this.ctx.clip();
  165 + }
  166 + this.ctx.globalAlpha = 1;
  167 + }
  168 + }
  169 +
  170 + /**
  171 + * 画边框
  172 + */
  173 + _doBorder(view, width, height) {
  174 + if (!view.css) {
  175 + return;
  176 + }
  177 + const {
  178 + borderRadius,
  179 + borderWidth,
  180 + borderColor,
  181 + borderStyle
  182 + } = view.css;
  183 + if (!borderWidth) {
  184 + return;
  185 + }
  186 + this.ctx.save();
  187 + this._preProcess(view, true);
  188 + this.ctx.strokeStyle = (borderColor || 'black');
  189 + this._border({
  190 + borderRadius,
  191 + width,
  192 + height,
  193 + borderWidth,
  194 + borderStyle
  195 + })
  196 + this.ctx.stroke();
  197 + this.ctx.restore();
  198 + }
  199 +
  200 + _preProcess(view, notClip) {
  201 + let width = 0;
  202 + let height;
  203 + let extra;
  204 + const paddings = this._doPaddings(view);
  205 + switch (view.type) {
  206 + case 'text': {
  207 + const textArray = view.text.split('\n');
  208 + // 处理多个连续的'\n'
  209 + for (let i = 0; i < textArray.length; ++i) {
  210 + if (textArray[i] === '') {
  211 + textArray[i] = ' ';
  212 + }
  213 + }
  214 + const fontWeight = view.css.fontWeight || '400';
  215 + const textStyle = view.css.textStyle || 'normal';
  216 + if (!view.css.fontSize) {
  217 + view.css.fontSize = '20rpx';
  218 + }
  219 + this.ctx.font = `${textStyle} ${fontWeight} ${view.css.fontSize.toPx()}px "${view.css.fontFamily || 'sans-serif'}"`;
  220 + // 计算行数
  221 + let lines = 0;
  222 + const linesArray = [];
  223 + for (let i = 0; i < textArray.length; ++i) {
  224 + const textLength = this.ctx.measureText(textArray[i]).width;
  225 + const minWidth = view.css.fontSize.toPx() + paddings[1] + paddings[3];
  226 + let partWidth = view.css.width ? view.css.width.toPx(false, this.style.width) - paddings[1] - paddings[3] : textLength;
  227 + if (partWidth < minWidth) {
  228 + partWidth = minWidth;
  229 + }
  230 + const calLines = Math.ceil(textLength / partWidth);
  231 + // 取最长的作为 width
  232 + width = partWidth > width ? partWidth : width;
  233 + lines += calLines;
  234 + linesArray[i] = calLines;
  235 + }
  236 + lines = view.css.maxLines < lines ? view.css.maxLines : lines;
  237 + const lineHeight = view.css.lineHeight ? view.css.lineHeight.toPx() : view.css.fontSize.toPx();
  238 + height = lineHeight * lines;
  239 + extra = {
  240 + lines: lines,
  241 + lineHeight: lineHeight,
  242 + textArray: textArray,
  243 + linesArray: linesArray,
  244 + };
  245 + break;
  246 + }
  247 + case 'image': {
  248 + // image的长宽设置成auto的逻辑处理
  249 + const ratio = getApp().systemInfo.pixelRatio ? getApp().systemInfo.pixelRatio : 2;
  250 + // 有css却未设置width或height,则默认为auto
  251 + if (view.css) {
  252 + if (!view.css.width) {
  253 + view.css.width = 'auto';
  254 + }
  255 + if (!view.css.height) {
  256 + view.css.height = 'auto';
  257 + }
  258 + }
  259 + if (!view.css || (view.css.width === 'auto' && view.css.height === 'auto')) {
  260 + width = Math.round(view.sWidth / ratio);
  261 + height = Math.round(view.sHeight / ratio);
  262 + } else if (view.css.width === 'auto') {
  263 + height = view.css.height.toPx(false, this.style.height);
  264 + width = view.sWidth / view.sHeight * height;
  265 + } else if (view.css.height === 'auto') {
  266 + width = view.css.width.toPx(false, this.style.width);
  267 + height = view.sHeight / view.sWidth * width;
  268 + } else {
  269 + width = view.css.width.toPx(false, this.style.width);
  270 + height = view.css.height.toPx(false, this.style.height);
  271 + }
  272 + break;
  273 + }
  274 + default:
  275 + if (!(view.css.width && view.css.height)) {
  276 + console.error('You should set width and height');
  277 + return;
  278 + }
  279 + width = view.css.width.toPx(false, this.style.width);
  280 + height = view.css.height.toPx(false, this.style.height);
  281 + break;
  282 + }
  283 + let x;
  284 + if (view.css && view.css.right) {
  285 + if (typeof view.css.right === 'string') {
  286 + x = this.style.width - view.css.right.toPx(true, this.style.width);
  287 + } else {
  288 + // 可以用数组方式,把文字长度计算进去
  289 + // [right, 文字id, 乘数(默认 1)]
  290 + const rights = view.css.right;
  291 + x = this.style.width - rights[0].toPx(true, this.style.width) - this.globalWidth[rights[1]] * (rights[2] || 1);
  292 + }
  293 + } else if (view.css && view.css.left) {
  294 + if (typeof view.css.left === 'string') {
  295 + x = view.css.left.toPx(true, this.style.width);
  296 + } else {
  297 + const lefts = view.css.left;
  298 + x = lefts[0].toPx(true, this.style.width) + this.globalWidth[lefts[1]] * (lefts[2] || 1);
  299 + }
  300 + } else {
  301 + x = 0;
  302 + }
  303 + //const y = view.css && view.css.bottom ? this.style.height - height - view.css.bottom.toPx(true) : (view.css && view.css.top ? view.css.top.toPx(true) : 0);
  304 + let y;
  305 + if (view.css && view.css.bottom) {
  306 + y = this.style.height - height - view.css.bottom.toPx(true, this.style.height);
  307 + } else {
  308 + if (view.css && view.css.top) {
  309 + if (typeof view.css.top === 'string') {
  310 + y = view.css.top.toPx(true, this.style.height);
  311 + } else {
  312 + const tops = view.css.top;
  313 + y = tops[0].toPx(true, this.style.height) + this.globalHeight[tops[1]] * (tops[2] || 1);
  314 + }
  315 + } else {
  316 + y = 0
  317 + }
  318 + }
  319 +
  320 + const angle = view.css && view.css.rotate ? this._getAngle(view.css.rotate) : 0;
  321 + // 当设置了 right 时,默认 align 用 right,反之用 left
  322 + const align = view.css && view.css.align ? view.css.align : (view.css && view.css.right ? 'right' : 'left');
  323 + const verticalAlign = view.css && view.css.verticalAlign ? view.css.verticalAlign : 'top';
  324 + // 记录绘制时的画布
  325 + let xa = 0;
  326 + switch (align) {
  327 + case 'center':
  328 + xa = x;
  329 + break;
  330 + case 'right':
  331 + xa = x - width / 2;
  332 + break;
  333 + default:
  334 + xa = x + width / 2;
  335 + break;
  336 + }
  337 + let ya = 0;
  338 + switch (verticalAlign) {
  339 + case 'center':
  340 + ya = y;
  341 + break;
  342 + case 'bottom':
  343 + ya = y - height / 2;
  344 + break;
  345 + default:
  346 + ya = y + height / 2;
  347 + break;
  348 + }
  349 + this.ctx.translate(xa, ya);
  350 + // 记录该 view 的有效点击区域
  351 + // TODO ,旋转和裁剪的判断
  352 + // 记录在真实画布上的左侧
  353 + let left = x
  354 + if (align === 'center') {
  355 + left = x - width / 2
  356 + } else if (align === 'right') {
  357 + left = x - width
  358 + }
  359 + var top = y;
  360 + if (verticalAlign === 'center') {
  361 + top = y - height / 2;
  362 + } else if (verticalAlign === 'bottom') {
  363 + top = y - height
  364 + }
  365 + if (view.rect) {
  366 + view.rect.left = left;
  367 + view.rect.top = top;
  368 + view.rect.right = left + width;
  369 + view.rect.bottom = top + height;
  370 + view.rect.x = view.css && view.css.right ? x - width : x;
  371 + view.rect.y = y;
  372 + } else {
  373 + view.rect = {
  374 + left: left,
  375 + top: top,
  376 + right: left + width,
  377 + bottom: top + height,
  378 + x: view.css && view.css.right ? x - width : x,
  379 + y: y
  380 + };
  381 + }
  382 +
  383 + view.rect.left = view.rect.left - paddings[3];
  384 + view.rect.top = view.rect.top - paddings[0];
  385 + view.rect.right = view.rect.right + paddings[1];
  386 + view.rect.bottom = view.rect.bottom + paddings[2];
  387 + if (view.type === 'text') {
  388 + view.rect.minWidth = view.css.fontSize.toPx() + paddings[1] + paddings[3];
  389 + }
  390 +
  391 + this.ctx.rotate(angle);
  392 + if (!notClip && view.css && view.css.borderRadius && view.type !== 'rect') {
  393 + this._doClip(view.css.borderRadius, width, height, view.css.borderStyle);
  394 + }
  395 + this._doShadow(view);
  396 + if (view.id) {
  397 + this.globalWidth[view.id] = width;
  398 + this.globalHeight[view.id] = height;
  399 + }
  400 + return {
  401 + width: width,
  402 + height: height,
  403 + x: x,
  404 + y: y,
  405 + extra: extra,
  406 + };
  407 + }
  408 +
  409 + _doPaddings(view) {
  410 + const {
  411 + padding,
  412 + } = view.css ? view.css : {};
  413 + let pd = [0, 0, 0, 0];
  414 + if (padding) {
  415 + const pdg = padding.split(/\s+/);
  416 + if (pdg.length === 1) {
  417 + const x = pdg[0].toPx();
  418 + pd = [x, x, x, x];
  419 + }
  420 + if (pdg.length === 2) {
  421 + const x = pdg[0].toPx();
  422 + const y = pdg[1].toPx();
  423 + pd = [x, y, x, y];
  424 + }
  425 + if (pdg.length === 3) {
  426 + const x = pdg[0].toPx();
  427 + const y = pdg[1].toPx();
  428 + const z = pdg[2].toPx();
  429 + pd = [x, y, z, y];
  430 + }
  431 + if (pdg.length === 4) {
  432 + const x = pdg[0].toPx();
  433 + const y = pdg[1].toPx();
  434 + const z = pdg[2].toPx();
  435 + const a = pdg[3].toPx();
  436 + pd = [x, y, z, a];
  437 + }
  438 + }
  439 + return pd;
  440 + }
  441 +
  442 + // 画文字的背景图片
  443 + _doBackground(view) {
  444 + this.ctx.save();
  445 + const {
  446 + width: rawWidth,
  447 + height: rawHeight,
  448 + } = this._preProcess(view, true);
  449 +
  450 + const {
  451 + background,
  452 + } = view.css;
  453 + let pd = this._doPaddings(view);
  454 + const width = rawWidth + pd[1] + pd[3];
  455 + const height = rawHeight + pd[0] + pd[2];
  456 +
  457 + this._doClip(view.css.borderRadius, width, height, view.css.borderStyle)
  458 + if (GD.api.isGradient(background)) {
  459 + GD.api.doGradient(background, width, height, this.ctx);
  460 + } else {
  461 + this.ctx.fillStyle = background;
  462 + }
  463 + this.ctx.fillRect(-(width / 2), -(height / 2), width, height);
  464 +
  465 + this.ctx.restore();
  466 + }
  467 +
  468 + _drawQRCode(view) {
  469 + this.ctx.save();
  470 + const {
  471 + width,
  472 + height,
  473 + } = this._preProcess(view);
  474 + QR.api.draw(view.content, this.ctx, -width / 2, -height / 2, width, height, view.css.background, view.css.color);
  475 + this.ctx.restore();
  476 + this._doBorder(view, width, height);
  477 + }
  478 +
  479 + _drawAbsImage(view) {
  480 + if (!view.url) {
  481 + return;
  482 + }
  483 + this.ctx.save();
  484 + const {
  485 + width,
  486 + height,
  487 + } = this._preProcess(view);
  488 + // 获得缩放到图片大小级别的裁减框
  489 + let rWidth = view.sWidth;
  490 + let rHeight = view.sHeight;
  491 + let startX = 0;
  492 + let startY = 0;
  493 + // 绘画区域比例
  494 + const cp = width / height;
  495 + // 原图比例
  496 + const op = view.sWidth / view.sHeight;
  497 + if (cp >= op) {
  498 + rHeight = rWidth / cp;
  499 + startY = Math.round((view.sHeight - rHeight) / 2);
  500 + } else {
  501 + rWidth = rHeight * cp;
  502 + startX = Math.round((view.sWidth - rWidth) / 2);
  503 + }
  504 + if (view.css && view.css.mode === 'scaleToFill') {
  505 + this.ctx.drawImage(view.url, -(width / 2), -(height / 2), width, height);
  506 + } else {
  507 + this.ctx.drawImage(view.url, startX, startY, rWidth, rHeight, -(width / 2), -(height / 2), width, height);
  508 + view.rect.startX = startX / view.sWidth;
  509 + view.rect.startY = startY / view.sHeight;
  510 + view.rect.endX = (startX + rWidth) / view.sWidth;
  511 + view.rect.endY = (startY + rHeight) / view.sHeight;
  512 + }
  513 + this.ctx.restore();
  514 + this._doBorder(view, width, height);
  515 + }
  516 +
  517 + callbackInfo = {}
  518 + _fillAbsText(view) {
  519 + if (!view.text) {
  520 + return;
  521 + }
  522 + if (view.css.background) {
  523 + // 生成背景
  524 + this._doBackground(view);
  525 + }
  526 + this.ctx.save();
  527 + const {
  528 + width,
  529 + height,
  530 + extra,
  531 + } = this._preProcess(view, view.css.background && view.css.borderRadius);
  532 + this.ctx.fillStyle = (view.css.color || 'black');
  533 + if (this.isMoving && JSON.stringify(this.movingCache) !== JSON.stringify({})) {
  534 + this.globalWidth[view.id] = this.movingCache.globalWidth
  535 + this.ctx.textAlign = view.css.textAlign ? view.css.textAlign : 'left';
  536 + for (const i of this.movingCache.lineArray) {
  537 + const {
  538 + measuredWith,
  539 + text,
  540 + x,
  541 + y,
  542 + textDecoration
  543 + } = i
  544 + if (view.css.textStyle === 'stroke') {
  545 + this.ctx.strokeText(text, x, y, measuredWith);
  546 + } else {
  547 + this.ctx.fillText(text, x, y, measuredWith);
  548 + }
  549 + if (textDecoration) {
  550 + const fontSize = view.css.fontSize.toPx();
  551 + this.ctx.lineWidth = fontSize / 13;
  552 + this.ctx.beginPath();
  553 + this.ctx.moveTo(...textDecoration.moveTo);
  554 + this.ctx.lineTo(...textDecoration.lineTo);
  555 + this.ctx.closePath();
  556 + this.ctx.strokeStyle = view.css.color;
  557 + this.ctx.stroke();
  558 + }
  559 + }
  560 + } else {
  561 + const {
  562 + lines,
  563 + lineHeight,
  564 + textArray,
  565 + linesArray,
  566 + } = extra;
  567 + // 如果设置了id,则保留 text 的长度
  568 + if (view.id) {
  569 + let textWidth = 0;
  570 + for (let i = 0; i < textArray.length; ++i) {
  571 + const _w = this.ctx.measureText(textArray[i]).width
  572 + textWidth = _w > textWidth ? _w : textWidth;
  573 + }
  574 + this.globalWidth[view.id] = width ? (textWidth < width ? textWidth : width) : textWidth;
  575 + if (!this.isMoving) {
  576 + Object.assign(this.callbackInfo, {
  577 + globalWidth: this.globalWidth[view.id]
  578 + })
  579 + }
  580 + }
  581 + let lineIndex = 0;
  582 + for (let j = 0; j < textArray.length; ++j) {
  583 + const preLineLength = Math.ceil(textArray[j].length / linesArray[j]);
  584 + let start = 0;
  585 + let alreadyCount = 0;
  586 +
  587 + for (let i = 0; i < linesArray[j]; ++i) {
  588 + // 绘制行数大于最大行数,则直接跳出循环
  589 + if (lineIndex >= lines) {
  590 + break;
  591 + }
  592 + alreadyCount = preLineLength;
  593 + let text = textArray[j].substr(start, alreadyCount);
  594 + let measuredWith = this.ctx.measureText(text).width;
  595 + // 如果测量大小小于width一个字符的大小,则进行补齐,如果测量大小超出 width,则进行减除
  596 + // 如果已经到文本末尾,也不要进行该循环
  597 + while ((start + alreadyCount <= textArray[j].length) && (width - measuredWith > view.css.fontSize.toPx() || measuredWith - width > view.css.fontSize.toPx())) {
  598 + if (measuredWith < width) {
  599 + text = textArray[j].substr(start, ++alreadyCount);
  600 + } else {
  601 + if (text.length <= 1) {
  602 + // 如果只有一个字符时,直接跳出循环
  603 + break;
  604 + }
  605 + text = textArray[j].substr(start, --alreadyCount);
  606 + // break;
  607 + }
  608 + measuredWith = this.ctx.measureText(text).width;
  609 + }
  610 + start += text.length
  611 + // 如果是最后一行了,发现还有未绘制完的内容,则加...
  612 + if (lineIndex === lines - 1 && (j < textArray.length - 1 || start < textArray[j].length)) {
  613 + while (this.ctx.measureText(`${text}...`).width > width) {
  614 + if (text.length <= 1) {
  615 + // 如果只有一个字符时,直接跳出循环
  616 + break;
  617 + }
  618 + text = text.substring(0, text.length - 1);
  619 + }
  620 + text += '...';
  621 + measuredWith = this.ctx.measureText(text).width;
  622 + }
  623 + this.ctx.textAlign = view.css.textAlign ? view.css.textAlign : 'left';
  624 + let x;
  625 + let lineX;
  626 + switch (view.css.textAlign) {
  627 + case 'center':
  628 + x = 0;
  629 + lineX = x - measuredWith / 2;
  630 + break;
  631 + case 'right':
  632 + x = (width / 2);
  633 + lineX = x - measuredWith;
  634 + break;
  635 + default:
  636 + x = -(width / 2);
  637 + lineX = x;
  638 + break;
  639 + }
  640 + const y = -(height / 2) + (lineIndex === 0 ? view.css.fontSize.toPx() : (view.css.fontSize.toPx() + lineIndex * lineHeight));
  641 + lineIndex++;
  642 + if (view.css.textStyle === 'stroke') {
  643 + this.ctx.strokeText(text, x, y, measuredWith);
  644 + } else {
  645 + this.ctx.fillText(text, x, y, measuredWith);
  646 + }
  647 + const fontSize = view.css.fontSize.toPx();
  648 + let textDecoration;
  649 + if (view.css.textDecoration) {
  650 + this.ctx.lineWidth = fontSize / 13;
  651 + this.ctx.beginPath();
  652 + if (/\bunderline\b/.test(view.css.textDecoration)) {
  653 + this.ctx.moveTo(lineX, y);
  654 + this.ctx.lineTo(lineX + measuredWith, y);
  655 + textDecoration = {
  656 + moveTo: [lineX, y],
  657 + lineTo: [lineX + measuredWith, y]
  658 + }
  659 + }
  660 + if (/\boverline\b/.test(view.css.textDecoration)) {
  661 + this.ctx.moveTo(lineX, y - fontSize);
  662 + this.ctx.lineTo(lineX + measuredWith, y - fontSize);
  663 + textDecoration = {
  664 + moveTo: [lineX, y - fontSize],
  665 + lineTo: [lineX + measuredWith, y - fontSize]
  666 + }
  667 + }
  668 + if (/\bline-through\b/.test(view.css.textDecoration)) {
  669 + this.ctx.moveTo(lineX, y - fontSize / 3);
  670 + this.ctx.lineTo(lineX + measuredWith, y - fontSize / 3);
  671 + textDecoration = {
  672 + moveTo: [lineX, y - fontSize / 3],
  673 + lineTo: [lineX + measuredWith, y - fontSize / 3]
  674 + }
  675 + }
  676 + this.ctx.closePath();
  677 + this.ctx.strokeStyle = view.css.color;
  678 + this.ctx.stroke();
  679 + }
  680 + if (!this.isMoving) {
  681 + this.callbackInfo.lineArray ? this.callbackInfo.lineArray.push({
  682 + text,
  683 + x,
  684 + y,
  685 + measuredWith,
  686 + textDecoration
  687 + }) : this.callbackInfo.lineArray = [{
  688 + text,
  689 + x,
  690 + y,
  691 + measuredWith,
  692 + textDecoration
  693 + }]
  694 + }
  695 + }
  696 + }
  697 + }
  698 + this.ctx.restore();
  699 + this._doBorder(view, width, height);
  700 + }
  701 +
  702 + _drawAbsRect(view) {
  703 + this.ctx.save();
  704 + const {
  705 + width,
  706 + height,
  707 + } = this._preProcess(view);
  708 + if (GD.api.isGradient(view.css.color)) {
  709 + GD.api.doGradient(view.css.color, width, height, this.ctx);
  710 + } else {
  711 + this.ctx.fillStyle = view.css.color;
  712 + }
  713 + const {
  714 + borderRadius,
  715 + borderStyle,
  716 + borderWidth
  717 + } = view.css
  718 + this._border({
  719 + borderRadius,
  720 + width,
  721 + height,
  722 + borderWidth,
  723 + borderStyle
  724 + })
  725 + this.ctx.fill();
  726 + this.ctx.restore();
  727 + this._doBorder(view, width, height);
  728 + }
  729 +
  730 + // shadow 支持 (x, y, blur, color), 不支持 spread
  731 + // shadow:0px 0px 10px rgba(0,0,0,0.1);
  732 + _doShadow(view) {
  733 + if (!view.css || !view.css.shadow) {
  734 + return;
  735 + }
  736 + const box = view.css.shadow.replace(/,\s+/g, ',').split(/\s+/);
  737 + if (box.length > 4) {
  738 + console.error('shadow don\'t spread option');
  739 + return;
  740 + }
  741 + this.ctx.shadowOffsetX = parseInt(box[0], 10);
  742 + this.ctx.shadowOffsetY = parseInt(box[1], 10);
  743 + this.ctx.shadowBlur = parseInt(box[2], 10);
  744 + this.ctx.shadowColor = box[3];
  745 + }
  746 +
  747 + _getAngle(angle) {
  748 + return Number(angle) * Math.PI / 180;
  749 + }
  750 +}
... ...
packageB/components/painter/lib/qrcode.js 0 → 100644
  1 +/* eslint-disable */
  2 +!(function () {
  3 +
  4 + // alignment pattern
  5 + var adelta = [
  6 + 0, 11, 15, 19, 23, 27, 31,
  7 + 16, 18, 20, 22, 24, 26, 28, 20, 22, 24, 24, 26, 28, 28, 22, 24, 24,
  8 + 26, 26, 28, 28, 24, 24, 26, 26, 26, 28, 28, 24, 26, 26, 26, 28, 28
  9 + ];
  10 +
  11 + // version block
  12 + var vpat = [
  13 + 0xc94, 0x5bc, 0xa99, 0x4d3, 0xbf6, 0x762, 0x847, 0x60d,
  14 + 0x928, 0xb78, 0x45d, 0xa17, 0x532, 0x9a6, 0x683, 0x8c9,
  15 + 0x7ec, 0xec4, 0x1e1, 0xfab, 0x08e, 0xc1a, 0x33f, 0xd75,
  16 + 0x250, 0x9d5, 0x6f0, 0x8ba, 0x79f, 0xb0b, 0x42e, 0xa64,
  17 + 0x541, 0xc69
  18 + ];
  19 +
  20 + // final format bits with mask: level << 3 | mask
  21 + var fmtword = [
  22 + 0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976, //L
  23 + 0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0, //M
  24 + 0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed, //Q
  25 + 0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b //H
  26 + ];
  27 +
  28 + // 4 per version: number of blocks 1,2; data width; ecc width
  29 + var eccblocks = [
  30 + 1, 0, 19, 7, 1, 0, 16, 10, 1, 0, 13, 13, 1, 0, 9, 17,
  31 + 1, 0, 34, 10, 1, 0, 28, 16, 1, 0, 22, 22, 1, 0, 16, 28,
  32 + 1, 0, 55, 15, 1, 0, 44, 26, 2, 0, 17, 18, 2, 0, 13, 22,
  33 + 1, 0, 80, 20, 2, 0, 32, 18, 2, 0, 24, 26, 4, 0, 9, 16,
  34 + 1, 0, 108, 26, 2, 0, 43, 24, 2, 2, 15, 18, 2, 2, 11, 22,
  35 + 2, 0, 68, 18, 4, 0, 27, 16, 4, 0, 19, 24, 4, 0, 15, 28,
  36 + 2, 0, 78, 20, 4, 0, 31, 18, 2, 4, 14, 18, 4, 1, 13, 26,
  37 + 2, 0, 97, 24, 2, 2, 38, 22, 4, 2, 18, 22, 4, 2, 14, 26,
  38 + 2, 0, 116, 30, 3, 2, 36, 22, 4, 4, 16, 20, 4, 4, 12, 24,
  39 + 2, 2, 68, 18, 4, 1, 43, 26, 6, 2, 19, 24, 6, 2, 15, 28,
  40 + 4, 0, 81, 20, 1, 4, 50, 30, 4, 4, 22, 28, 3, 8, 12, 24,
  41 + 2, 2, 92, 24, 6, 2, 36, 22, 4, 6, 20, 26, 7, 4, 14, 28,
  42 + 4, 0, 107, 26, 8, 1, 37, 22, 8, 4, 20, 24, 12, 4, 11, 22,
  43 + 3, 1, 115, 30, 4, 5, 40, 24, 11, 5, 16, 20, 11, 5, 12, 24,
  44 + 5, 1, 87, 22, 5, 5, 41, 24, 5, 7, 24, 30, 11, 7, 12, 24,
  45 + 5, 1, 98, 24, 7, 3, 45, 28, 15, 2, 19, 24, 3, 13, 15, 30,
  46 + 1, 5, 107, 28, 10, 1, 46, 28, 1, 15, 22, 28, 2, 17, 14, 28,
  47 + 5, 1, 120, 30, 9, 4, 43, 26, 17, 1, 22, 28, 2, 19, 14, 28,
  48 + 3, 4, 113, 28, 3, 11, 44, 26, 17, 4, 21, 26, 9, 16, 13, 26,
  49 + 3, 5, 107, 28, 3, 13, 41, 26, 15, 5, 24, 30, 15, 10, 15, 28,
  50 + 4, 4, 116, 28, 17, 0, 42, 26, 17, 6, 22, 28, 19, 6, 16, 30,
  51 + 2, 7, 111, 28, 17, 0, 46, 28, 7, 16, 24, 30, 34, 0, 13, 24,
  52 + 4, 5, 121, 30, 4, 14, 47, 28, 11, 14, 24, 30, 16, 14, 15, 30,
  53 + 6, 4, 117, 30, 6, 14, 45, 28, 11, 16, 24, 30, 30, 2, 16, 30,
  54 + 8, 4, 106, 26, 8, 13, 47, 28, 7, 22, 24, 30, 22, 13, 15, 30,
  55 + 10, 2, 114, 28, 19, 4, 46, 28, 28, 6, 22, 28, 33, 4, 16, 30,
  56 + 8, 4, 122, 30, 22, 3, 45, 28, 8, 26, 23, 30, 12, 28, 15, 30,
  57 + 3, 10, 117, 30, 3, 23, 45, 28, 4, 31, 24, 30, 11, 31, 15, 30,
  58 + 7, 7, 116, 30, 21, 7, 45, 28, 1, 37, 23, 30, 19, 26, 15, 30,
  59 + 5, 10, 115, 30, 19, 10, 47, 28, 15, 25, 24, 30, 23, 25, 15, 30,
  60 + 13, 3, 115, 30, 2, 29, 46, 28, 42, 1, 24, 30, 23, 28, 15, 30,
  61 + 17, 0, 115, 30, 10, 23, 46, 28, 10, 35, 24, 30, 19, 35, 15, 30,
  62 + 17, 1, 115, 30, 14, 21, 46, 28, 29, 19, 24, 30, 11, 46, 15, 30,
  63 + 13, 6, 115, 30, 14, 23, 46, 28, 44, 7, 24, 30, 59, 1, 16, 30,
  64 + 12, 7, 121, 30, 12, 26, 47, 28, 39, 14, 24, 30, 22, 41, 15, 30,
  65 + 6, 14, 121, 30, 6, 34, 47, 28, 46, 10, 24, 30, 2, 64, 15, 30,
  66 + 17, 4, 122, 30, 29, 14, 46, 28, 49, 10, 24, 30, 24, 46, 15, 30,
  67 + 4, 18, 122, 30, 13, 32, 46, 28, 48, 14, 24, 30, 42, 32, 15, 30,
  68 + 20, 4, 117, 30, 40, 7, 47, 28, 43, 22, 24, 30, 10, 67, 15, 30,
  69 + 19, 6, 118, 30, 18, 31, 47, 28, 34, 34, 24, 30, 20, 61, 15, 30
  70 + ];
  71 +
  72 + // Galois field log table
  73 + var glog = [
  74 + 0xff, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b,
  75 + 0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71,
  76 + 0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45,
  77 + 0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6,
  78 + 0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88,
  79 + 0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40,
  80 + 0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d,
  81 + 0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57,
  82 + 0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18,
  83 + 0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e,
  84 + 0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61,
  85 + 0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2,
  86 + 0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6,
  87 + 0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a,
  88 + 0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7,
  89 + 0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf
  90 + ];
  91 +
  92 + // Galios field exponent table
  93 + var gexp = [
  94 + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26,
  95 + 0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0,
  96 + 0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23,
  97 + 0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1,
  98 + 0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0,
  99 + 0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2,
  100 + 0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce,
  101 + 0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc,
  102 + 0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54,
  103 + 0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73,
  104 + 0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff,
  105 + 0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41,
  106 + 0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6,
  107 + 0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09,
  108 + 0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16,
  109 + 0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x00
  110 + ];
  111 +
  112 + // Working buffers:
  113 + // data input and ecc append, image working buffer, fixed part of image, run lengths for badness
  114 + var strinbuf = [], eccbuf = [], qrframe = [], framask = [], rlens = [];
  115 + // Control values - width is based on version, last 4 are from table.
  116 + var version, width, neccblk1, neccblk2, datablkw, eccblkwid;
  117 + var ecclevel = 2;
  118 + // set bit to indicate cell in qrframe is immutable. symmetric around diagonal
  119 + function setmask(x, y) {
  120 + var bt;
  121 + if (x > y) {
  122 + bt = x;
  123 + x = y;
  124 + y = bt;
  125 + }
  126 + // y*y = 1+3+5...
  127 + bt = y;
  128 + bt *= y;
  129 + bt += y;
  130 + bt >>= 1;
  131 + bt += x;
  132 + framask[bt] = 1;
  133 + }
  134 +
  135 + // enter alignment pattern - black to qrframe, white to mask (later black frame merged to mask)
  136 + function putalign(x, y) {
  137 + var j;
  138 +
  139 + qrframe[x + width * y] = 1;
  140 + for (j = -2; j < 2; j++) {
  141 + qrframe[(x + j) + width * (y - 2)] = 1;
  142 + qrframe[(x - 2) + width * (y + j + 1)] = 1;
  143 + qrframe[(x + 2) + width * (y + j)] = 1;
  144 + qrframe[(x + j + 1) + width * (y + 2)] = 1;
  145 + }
  146 + for (j = 0; j < 2; j++) {
  147 + setmask(x - 1, y + j);
  148 + setmask(x + 1, y - j);
  149 + setmask(x - j, y - 1);
  150 + setmask(x + j, y + 1);
  151 + }
  152 + }
  153 +
  154 + //========================================================================
  155 + // Reed Solomon error correction
  156 + // exponentiation mod N
  157 + function modnn(x) {
  158 + while (x >= 255) {
  159 + x -= 255;
  160 + x = (x >> 8) + (x & 255);
  161 + }
  162 + return x;
  163 + }
  164 +
  165 + var genpoly = [];
  166 +
  167 + // Calculate and append ECC data to data block. Block is in strinbuf, indexes to buffers given.
  168 + function appendrs(data, dlen, ecbuf, eclen) {
  169 + var i, j, fb;
  170 +
  171 + for (i = 0; i < eclen; i++)
  172 + strinbuf[ecbuf + i] = 0;
  173 + for (i = 0; i < dlen; i++) {
  174 + fb = glog[strinbuf[data + i] ^ strinbuf[ecbuf]];
  175 + if (fb != 255) /* fb term is non-zero */
  176 + for (j = 1; j < eclen; j++)
  177 + strinbuf[ecbuf + j - 1] = strinbuf[ecbuf + j] ^ gexp[modnn(fb + genpoly[eclen - j])];
  178 + else
  179 + for (j = ecbuf; j < ecbuf + eclen; j++)
  180 + strinbuf[j] = strinbuf[j + 1];
  181 + strinbuf[ecbuf + eclen - 1] = fb == 255 ? 0 : gexp[modnn(fb + genpoly[0])];
  182 + }
  183 + }
  184 +
  185 + //========================================================================
  186 + // Frame data insert following the path rules
  187 +
  188 + // check mask - since symmetrical use half.
  189 + function ismasked(x, y) {
  190 + var bt;
  191 + if (x > y) {
  192 + bt = x;
  193 + x = y;
  194 + y = bt;
  195 + }
  196 + bt = y;
  197 + bt += y * y;
  198 + bt >>= 1;
  199 + bt += x;
  200 + return framask[bt];
  201 + }
  202 +
  203 + //========================================================================
  204 + // Apply the selected mask out of the 8.
  205 + function applymask(m) {
  206 + var x, y, r3x, r3y;
  207 +
  208 + switch (m) {
  209 + case 0:
  210 + for (y = 0; y < width; y++)
  211 + for (x = 0; x < width; x++)
  212 + if (!((x + y) & 1) && !ismasked(x, y))
  213 + qrframe[x + y * width] ^= 1;
  214 + break;
  215 + case 1:
  216 + for (y = 0; y < width; y++)
  217 + for (x = 0; x < width; x++)
  218 + if (!(y & 1) && !ismasked(x, y))
  219 + qrframe[x + y * width] ^= 1;
  220 + break;
  221 + case 2:
  222 + for (y = 0; y < width; y++)
  223 + for (r3x = 0, x = 0; x < width; x++ , r3x++) {
  224 + if (r3x == 3)
  225 + r3x = 0;
  226 + if (!r3x && !ismasked(x, y))
  227 + qrframe[x + y * width] ^= 1;
  228 + }
  229 + break;
  230 + case 3:
  231 + for (r3y = 0, y = 0; y < width; y++ , r3y++) {
  232 + if (r3y == 3)
  233 + r3y = 0;
  234 + for (r3x = r3y, x = 0; x < width; x++ , r3x++) {
  235 + if (r3x == 3)
  236 + r3x = 0;
  237 + if (!r3x && !ismasked(x, y))
  238 + qrframe[x + y * width] ^= 1;
  239 + }
  240 + }
  241 + break;
  242 + case 4:
  243 + for (y = 0; y < width; y++)
  244 + for (r3x = 0, r3y = ((y >> 1) & 1), x = 0; x < width; x++ , r3x++) {
  245 + if (r3x == 3) {
  246 + r3x = 0;
  247 + r3y = !r3y;
  248 + }
  249 + if (!r3y && !ismasked(x, y))
  250 + qrframe[x + y * width] ^= 1;
  251 + }
  252 + break;
  253 + case 5:
  254 + for (r3y = 0, y = 0; y < width; y++ , r3y++) {
  255 + if (r3y == 3)
  256 + r3y = 0;
  257 + for (r3x = 0, x = 0; x < width; x++ , r3x++) {
  258 + if (r3x == 3)
  259 + r3x = 0;
  260 + if (!((x & y & 1) + !(!r3x | !r3y)) && !ismasked(x, y))
  261 + qrframe[x + y * width] ^= 1;
  262 + }
  263 + }
  264 + break;
  265 + case 6:
  266 + for (r3y = 0, y = 0; y < width; y++ , r3y++) {
  267 + if (r3y == 3)
  268 + r3y = 0;
  269 + for (r3x = 0, x = 0; x < width; x++ , r3x++) {
  270 + if (r3x == 3)
  271 + r3x = 0;
  272 + if (!(((x & y & 1) + (r3x && (r3x == r3y))) & 1) && !ismasked(x, y))
  273 + qrframe[x + y * width] ^= 1;
  274 + }
  275 + }
  276 + break;
  277 + case 7:
  278 + for (r3y = 0, y = 0; y < width; y++ , r3y++) {
  279 + if (r3y == 3)
  280 + r3y = 0;
  281 + for (r3x = 0, x = 0; x < width; x++ , r3x++) {
  282 + if (r3x == 3)
  283 + r3x = 0;
  284 + if (!(((r3x && (r3x == r3y)) + ((x + y) & 1)) & 1) && !ismasked(x, y))
  285 + qrframe[x + y * width] ^= 1;
  286 + }
  287 + }
  288 + break;
  289 + }
  290 + return;
  291 + }
  292 +
  293 + // Badness coefficients.
  294 + var N1 = 3, N2 = 3, N3 = 40, N4 = 10;
  295 +
  296 + // Using the table of the length of each run, calculate the amount of bad image
  297 + // - long runs or those that look like finders; called twice, once each for X and Y
  298 + function badruns(length) {
  299 + var i;
  300 + var runsbad = 0;
  301 + for (i = 0; i <= length; i++)
  302 + if (rlens[i] >= 5)
  303 + runsbad += N1 + rlens[i] - 5;
  304 + // BwBBBwB as in finder
  305 + for (i = 3; i < length - 1; i += 2)
  306 + if (rlens[i - 2] == rlens[i + 2]
  307 + && rlens[i + 2] == rlens[i - 1]
  308 + && rlens[i - 1] == rlens[i + 1]
  309 + && rlens[i - 1] * 3 == rlens[i]
  310 + // white around the black pattern? Not part of spec
  311 + && (rlens[i - 3] == 0 // beginning
  312 + || i + 3 > length // end
  313 + || rlens[i - 3] * 3 >= rlens[i] * 4 || rlens[i + 3] * 3 >= rlens[i] * 4)
  314 + )
  315 + runsbad += N3;
  316 + return runsbad;
  317 + }
  318 +
  319 + // Calculate how bad the masked image is - blocks, imbalance, runs, or finders.
  320 + function badcheck() {
  321 + var x, y, h, b, b1;
  322 + var thisbad = 0;
  323 + var bw = 0;
  324 +
  325 + // blocks of same color.
  326 + for (y = 0; y < width - 1; y++)
  327 + for (x = 0; x < width - 1; x++)
  328 + if ((qrframe[x + width * y] && qrframe[(x + 1) + width * y]
  329 + && qrframe[x + width * (y + 1)] && qrframe[(x + 1) + width * (y + 1)]) // all black
  330 + || !(qrframe[x + width * y] || qrframe[(x + 1) + width * y]
  331 + || qrframe[x + width * (y + 1)] || qrframe[(x + 1) + width * (y + 1)])) // all white
  332 + thisbad += N2;
  333 +
  334 + // X runs
  335 + for (y = 0; y < width; y++) {
  336 + rlens[0] = 0;
  337 + for (h = b = x = 0; x < width; x++) {
  338 + if ((b1 = qrframe[x + width * y]) == b)
  339 + rlens[h]++;
  340 + else
  341 + rlens[++h] = 1;
  342 + b = b1;
  343 + bw += b ? 1 : -1;
  344 + }
  345 + thisbad += badruns(h);
  346 + }
  347 +
  348 + // black/white imbalance
  349 + if (bw < 0)
  350 + bw = -bw;
  351 +
  352 + var big = bw;
  353 + var count = 0;
  354 + big += big << 2;
  355 + big <<= 1;
  356 + while (big > width * width)
  357 + big -= width * width, count++;
  358 + thisbad += count * N4;
  359 +
  360 + // Y runs
  361 + for (x = 0; x < width; x++) {
  362 + rlens[0] = 0;
  363 + for (h = b = y = 0; y < width; y++) {
  364 + if ((b1 = qrframe[x + width * y]) == b)
  365 + rlens[h]++;
  366 + else
  367 + rlens[++h] = 1;
  368 + b = b1;
  369 + }
  370 + thisbad += badruns(h);
  371 + }
  372 + return thisbad;
  373 + }
  374 +
  375 + function genframe(instring) {
  376 + var x, y, k, t, v, i, j, m;
  377 +
  378 + // find the smallest version that fits the string
  379 + t = instring.length;
  380 + version = 0;
  381 + do {
  382 + version++;
  383 + k = (ecclevel - 1) * 4 + (version - 1) * 16;
  384 + neccblk1 = eccblocks[k++];
  385 + neccblk2 = eccblocks[k++];
  386 + datablkw = eccblocks[k++];
  387 + eccblkwid = eccblocks[k];
  388 + k = datablkw * (neccblk1 + neccblk2) + neccblk2 - 3 + (version <= 9);
  389 + if (t <= k)
  390 + break;
  391 + } while (version < 40);
  392 +
  393 + // FIXME - insure that it fits insted of being truncated
  394 + width = 17 + 4 * version;
  395 +
  396 + // allocate, clear and setup data structures
  397 + v = datablkw + (datablkw + eccblkwid) * (neccblk1 + neccblk2) + neccblk2;
  398 + for (t = 0; t < v; t++)
  399 + eccbuf[t] = 0;
  400 + strinbuf = instring.slice(0);
  401 +
  402 + for (t = 0; t < width * width; t++)
  403 + qrframe[t] = 0;
  404 +
  405 + for (t = 0; t < (width * (width + 1) + 1) / 2; t++)
  406 + framask[t] = 0;
  407 +
  408 + // insert finders - black to frame, white to mask
  409 + for (t = 0; t < 3; t++) {
  410 + k = 0;
  411 + y = 0;
  412 + if (t == 1)
  413 + k = (width - 7);
  414 + if (t == 2)
  415 + y = (width - 7);
  416 + qrframe[(y + 3) + width * (k + 3)] = 1;
  417 + for (x = 0; x < 6; x++) {
  418 + qrframe[(y + x) + width * k] = 1;
  419 + qrframe[y + width * (k + x + 1)] = 1;
  420 + qrframe[(y + 6) + width * (k + x)] = 1;
  421 + qrframe[(y + x + 1) + width * (k + 6)] = 1;
  422 + }
  423 + for (x = 1; x < 5; x++) {
  424 + setmask(y + x, k + 1);
  425 + setmask(y + 1, k + x + 1);
  426 + setmask(y + 5, k + x);
  427 + setmask(y + x + 1, k + 5);
  428 + }
  429 + for (x = 2; x < 4; x++) {
  430 + qrframe[(y + x) + width * (k + 2)] = 1;
  431 + qrframe[(y + 2) + width * (k + x + 1)] = 1;
  432 + qrframe[(y + 4) + width * (k + x)] = 1;
  433 + qrframe[(y + x + 1) + width * (k + 4)] = 1;
  434 + }
  435 + }
  436 +
  437 + // alignment blocks
  438 + if (version > 1) {
  439 + t = adelta[version];
  440 + y = width - 7;
  441 + for (; ;) {
  442 + x = width - 7;
  443 + while (x > t - 3) {
  444 + putalign(x, y);
  445 + if (x < t)
  446 + break;
  447 + x -= t;
  448 + }
  449 + if (y <= t + 9)
  450 + break;
  451 + y -= t;
  452 + putalign(6, y);
  453 + putalign(y, 6);
  454 + }
  455 + }
  456 +
  457 + // single black
  458 + qrframe[8 + width * (width - 8)] = 1;
  459 +
  460 + // timing gap - mask only
  461 + for (y = 0; y < 7; y++) {
  462 + setmask(7, y);
  463 + setmask(width - 8, y);
  464 + setmask(7, y + width - 7);
  465 + }
  466 + for (x = 0; x < 8; x++) {
  467 + setmask(x, 7);
  468 + setmask(x + width - 8, 7);
  469 + setmask(x, width - 8);
  470 + }
  471 +
  472 + // reserve mask-format area
  473 + for (x = 0; x < 9; x++)
  474 + setmask(x, 8);
  475 + for (x = 0; x < 8; x++) {
  476 + setmask(x + width - 8, 8);
  477 + setmask(8, x);
  478 + }
  479 + for (y = 0; y < 7; y++)
  480 + setmask(8, y + width - 7);
  481 +
  482 + // timing row/col
  483 + for (x = 0; x < width - 14; x++)
  484 + if (x & 1) {
  485 + setmask(8 + x, 6);
  486 + setmask(6, 8 + x);
  487 + }
  488 + else {
  489 + qrframe[(8 + x) + width * 6] = 1;
  490 + qrframe[6 + width * (8 + x)] = 1;
  491 + }
  492 +
  493 + // version block
  494 + if (version > 6) {
  495 + t = vpat[version - 7];
  496 + k = 17;
  497 + for (x = 0; x < 6; x++)
  498 + for (y = 0; y < 3; y++ , k--)
  499 + if (1 & (k > 11 ? version >> (k - 12) : t >> k)) {
  500 + qrframe[(5 - x) + width * (2 - y + width - 11)] = 1;
  501 + qrframe[(2 - y + width - 11) + width * (5 - x)] = 1;
  502 + }
  503 + else {
  504 + setmask(5 - x, 2 - y + width - 11);
  505 + setmask(2 - y + width - 11, 5 - x);
  506 + }
  507 + }
  508 +
  509 + // sync mask bits - only set above for white spaces, so add in black bits
  510 + for (y = 0; y < width; y++)
  511 + for (x = 0; x <= y; x++)
  512 + if (qrframe[x + width * y])
  513 + setmask(x, y);
  514 +
  515 + // convert string to bitstream
  516 + // 8 bit data to QR-coded 8 bit data (numeric or alphanum, or kanji not supported)
  517 + v = strinbuf.length;
  518 +
  519 + // string to array
  520 + for (i = 0; i < v; i++)
  521 + eccbuf[i] = strinbuf.charCodeAt(i);
  522 + strinbuf = eccbuf.slice(0);
  523 +
  524 + // calculate max string length
  525 + x = datablkw * (neccblk1 + neccblk2) + neccblk2;
  526 + if (v >= x - 2) {
  527 + v = x - 2;
  528 + if (version > 9)
  529 + v--;
  530 + }
  531 +
  532 + // shift and repack to insert length prefix
  533 + i = v;
  534 + if (version > 9) {
  535 + strinbuf[i + 2] = 0;
  536 + strinbuf[i + 3] = 0;
  537 + while (i--) {
  538 + t = strinbuf[i];
  539 + strinbuf[i + 3] |= 255 & (t << 4);
  540 + strinbuf[i + 2] = t >> 4;
  541 + }
  542 + strinbuf[2] |= 255 & (v << 4);
  543 + strinbuf[1] = v >> 4;
  544 + strinbuf[0] = 0x40 | (v >> 12);
  545 + }
  546 + else {
  547 + strinbuf[i + 1] = 0;
  548 + strinbuf[i + 2] = 0;
  549 + while (i--) {
  550 + t = strinbuf[i];
  551 + strinbuf[i + 2] |= 255 & (t << 4);
  552 + strinbuf[i + 1] = t >> 4;
  553 + }
  554 + strinbuf[1] |= 255 & (v << 4);
  555 + strinbuf[0] = 0x40 | (v >> 4);
  556 + }
  557 + // fill to end with pad pattern
  558 + i = v + 3 - (version < 10);
  559 + while (i < x) {
  560 + strinbuf[i++] = 0xec;
  561 + // buffer has room if (i == x) break;
  562 + strinbuf[i++] = 0x11;
  563 + }
  564 +
  565 + // calculate and append ECC
  566 +
  567 + // calculate generator polynomial
  568 + genpoly[0] = 1;
  569 + for (i = 0; i < eccblkwid; i++) {
  570 + genpoly[i + 1] = 1;
  571 + for (j = i; j > 0; j--)
  572 + genpoly[j] = genpoly[j]
  573 + ? genpoly[j - 1] ^ gexp[modnn(glog[genpoly[j]] + i)] : genpoly[j - 1];
  574 + genpoly[0] = gexp[modnn(glog[genpoly[0]] + i)];
  575 + }
  576 + for (i = 0; i <= eccblkwid; i++)
  577 + genpoly[i] = glog[genpoly[i]]; // use logs for genpoly[] to save calc step
  578 +
  579 + // append ecc to data buffer
  580 + k = x;
  581 + y = 0;
  582 + for (i = 0; i < neccblk1; i++) {
  583 + appendrs(y, datablkw, k, eccblkwid);
  584 + y += datablkw;
  585 + k += eccblkwid;
  586 + }
  587 + for (i = 0; i < neccblk2; i++) {
  588 + appendrs(y, datablkw + 1, k, eccblkwid);
  589 + y += datablkw + 1;
  590 + k += eccblkwid;
  591 + }
  592 + // interleave blocks
  593 + y = 0;
  594 + for (i = 0; i < datablkw; i++) {
  595 + for (j = 0; j < neccblk1; j++)
  596 + eccbuf[y++] = strinbuf[i + j * datablkw];
  597 + for (j = 0; j < neccblk2; j++)
  598 + eccbuf[y++] = strinbuf[(neccblk1 * datablkw) + i + (j * (datablkw + 1))];
  599 + }
  600 + for (j = 0; j < neccblk2; j++)
  601 + eccbuf[y++] = strinbuf[(neccblk1 * datablkw) + i + (j * (datablkw + 1))];
  602 + for (i = 0; i < eccblkwid; i++)
  603 + for (j = 0; j < neccblk1 + neccblk2; j++)
  604 + eccbuf[y++] = strinbuf[x + i + j * eccblkwid];
  605 + strinbuf = eccbuf;
  606 +
  607 + // pack bits into frame avoiding masked area.
  608 + x = y = width - 1;
  609 + k = v = 1; // up, minus
  610 + /* inteleaved data and ecc codes */
  611 + m = (datablkw + eccblkwid) * (neccblk1 + neccblk2) + neccblk2;
  612 + for (i = 0; i < m; i++) {
  613 + t = strinbuf[i];
  614 + for (j = 0; j < 8; j++ , t <<= 1) {
  615 + if (0x80 & t)
  616 + qrframe[x + width * y] = 1;
  617 + do { // find next fill position
  618 + if (v)
  619 + x--;
  620 + else {
  621 + x++;
  622 + if (k) {
  623 + if (y != 0)
  624 + y--;
  625 + else {
  626 + x -= 2;
  627 + k = !k;
  628 + if (x == 6) {
  629 + x--;
  630 + y = 9;
  631 + }
  632 + }
  633 + }
  634 + else {
  635 + if (y != width - 1)
  636 + y++;
  637 + else {
  638 + x -= 2;
  639 + k = !k;
  640 + if (x == 6) {
  641 + x--;
  642 + y -= 8;
  643 + }
  644 + }
  645 + }
  646 + }
  647 + v = !v;
  648 + } while (ismasked(x, y));
  649 + }
  650 + }
  651 +
  652 + // save pre-mask copy of frame
  653 + strinbuf = qrframe.slice(0);
  654 + t = 0; // best
  655 + y = 30000; // demerit
  656 + // for instead of while since in original arduino code
  657 + // if an early mask was "good enough" it wouldn't try for a better one
  658 + // since they get more complex and take longer.
  659 + for (k = 0; k < 8; k++) {
  660 + applymask(k); // returns black-white imbalance
  661 + x = badcheck();
  662 + if (x < y) { // current mask better than previous best?
  663 + y = x;
  664 + t = k;
  665 + }
  666 + if (t == 7)
  667 + break; // don't increment i to a void redoing mask
  668 + qrframe = strinbuf.slice(0); // reset for next pass
  669 + }
  670 + if (t != k) // redo best mask - none good enough, last wasn't t
  671 + applymask(t);
  672 +
  673 + // add in final mask/ecclevel bytes
  674 + y = fmtword[t + ((ecclevel - 1) << 3)];
  675 + // low byte
  676 + for (k = 0; k < 8; k++ , y >>= 1)
  677 + if (y & 1) {
  678 + qrframe[(width - 1 - k) + width * 8] = 1;
  679 + if (k < 6)
  680 + qrframe[8 + width * k] = 1;
  681 + else
  682 + qrframe[8 + width * (k + 1)] = 1;
  683 + }
  684 + // high byte
  685 + for (k = 0; k < 7; k++ , y >>= 1)
  686 + if (y & 1) {
  687 + qrframe[8 + width * (width - 7 + k)] = 1;
  688 + if (k)
  689 + qrframe[(6 - k) + width * 8] = 1;
  690 + else
  691 + qrframe[7 + width * 8] = 1;
  692 + }
  693 + return qrframe;
  694 + }
  695 +
  696 +
  697 +
  698 +
  699 + var _canvas = null;
  700 +
  701 + var api = {
  702 +
  703 + get ecclevel() {
  704 + return ecclevel;
  705 + },
  706 +
  707 + set ecclevel(val) {
  708 + ecclevel = val;
  709 + },
  710 +
  711 + get size() {
  712 + return _size;
  713 + },
  714 +
  715 + set size(val) {
  716 + _size = val
  717 + },
  718 +
  719 + get canvas() {
  720 + return _canvas;
  721 + },
  722 +
  723 + set canvas(el) {
  724 + _canvas = el;
  725 + },
  726 +
  727 + getFrame: function (string) {
  728 + return genframe(string);
  729 + },
  730 + //这里的utf16to8(str)是对Text中的字符串进行转码,让其支持中文
  731 + utf16to8: function (str) {
  732 + var out, i, len, c;
  733 +
  734 + out = "";
  735 + len = str.length;
  736 + for (i = 0; i < len; i++) {
  737 + c = str.charCodeAt(i);
  738 + if ((c >= 0x0001) && (c <= 0x007F)) {
  739 + out += str.charAt(i);
  740 + } else if (c > 0x07FF) {
  741 + out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
  742 + out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
  743 + out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
  744 + } else {
  745 + out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
  746 + out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
  747 + }
  748 + }
  749 + return out;
  750 + },
  751 + /**
  752 + * 新增$this参数,传入组件的this,兼容在组件中生成
  753 + * @param bg 目前只能设置颜色值
  754 + */
  755 + draw: function (str, ctx, startX, startY, cavW, cavH, bg, color, $this, ecc) {
  756 + var that = this;
  757 + ecclevel = ecc || ecclevel;
  758 + if (!ctx) {
  759 + console.warn('No canvas provided to draw QR code in!')
  760 + return;
  761 + }
  762 + var size = Math.min(cavW, cavH);
  763 + str = that.utf16to8(str);//增加中文显示
  764 +
  765 + var frame = that.getFrame(str);
  766 + var px = size / width;
  767 + if (bg) {
  768 + ctx.fillStyle = bg;
  769 + ctx.fillRect(startX, startY, cavW, cavW);
  770 + }
  771 + ctx.fillStyle = color || 'black';
  772 + for (var i = 0; i < width; i++) {
  773 + for (var j = 0; j < width; j++) {
  774 + if (frame[j * width + i]) {
  775 + ctx.fillRect(startX + px * i, startY + px * j, px, px);
  776 + }
  777 + }
  778 + }
  779 + }
  780 + }
  781 + module.exports = { api }
  782 + // exports.draw = api;
  783 +
  784 +})();
0 785 \ No newline at end of file
... ...
packageB/components/painter/lib/util.js 0 → 100644
  1 +
  2 +function isValidUrl(url) {
  3 + return /(ht|f)tp(s?):\/\/([^ \\/]*\.)+[^ \\/]*(:[0-9]+)?\/?/.test(url);
  4 +}
  5 +
  6 +/**
  7 + * 深度对比两个对象是否一致
  8 + * from: https://github.com/epoberezkin/fast-deep-equal
  9 + * @param {Object} a 对象a
  10 + * @param {Object} b 对象b
  11 + * @return {Boolean} 是否相同
  12 + */
  13 +/* eslint-disable */
  14 +function equal(a, b) {
  15 + if (a === b) return true;
  16 +
  17 + if (a && b && typeof a == 'object' && typeof b == 'object') {
  18 + var arrA = Array.isArray(a)
  19 + , arrB = Array.isArray(b)
  20 + , i
  21 + , length
  22 + , key;
  23 +
  24 + if (arrA && arrB) {
  25 + length = a.length;
  26 + if (length != b.length) return false;
  27 + for (i = length; i-- !== 0;)
  28 + if (!equal(a[i], b[i])) return false;
  29 + return true;
  30 + }
  31 +
  32 + if (arrA != arrB) return false;
  33 +
  34 + var dateA = a instanceof Date
  35 + , dateB = b instanceof Date;
  36 + if (dateA != dateB) return false;
  37 + if (dateA && dateB) return a.getTime() == b.getTime();
  38 +
  39 + var regexpA = a instanceof RegExp
  40 + , regexpB = b instanceof RegExp;
  41 + if (regexpA != regexpB) return false;
  42 + if (regexpA && regexpB) return a.toString() == b.toString();
  43 +
  44 + var keys = Object.keys(a);
  45 + length = keys.length;
  46 +
  47 + if (length !== Object.keys(b).length)
  48 + return false;
  49 +
  50 + for (i = length; i-- !== 0;)
  51 + if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
  52 +
  53 + for (i = length; i-- !== 0;) {
  54 + key = keys[i];
  55 + if (!equal(a[key], b[key])) return false;
  56 + }
  57 +
  58 + return true;
  59 + }
  60 +
  61 + return a!==a && b!==b;
  62 +}
  63 +
  64 +module.exports = {
  65 + isValidUrl,
  66 + equal
  67 +};
  68 +
... ...
packageB/components/painter/lib/wx-canvas.js 0 → 100644
  1 +// @ts-check
  2 +export default class WxCanvas {
  3 + ctx;
  4 + type;
  5 + canvasId;
  6 + canvasNode;
  7 + stepList = [];
  8 + canvasPrototype = {};
  9 +
  10 + constructor(type, ctx, canvasId, isNew, canvasNode) {
  11 + this.ctx = ctx;
  12 + this.canvasId = canvasId;
  13 + this.type = type;
  14 + if (isNew) {
  15 + this.canvasNode = canvasNode || {};
  16 + }
  17 + }
  18 +
  19 + set width(w) {
  20 + if (this.canvasNode) this.canvasNode.width = w;
  21 + }
  22 +
  23 + get width() {
  24 + if (this.canvasNode) return this.canvasNode.width;
  25 + return 0;
  26 + }
  27 +
  28 + set height(h) {
  29 + if (this.canvasNode) this.canvasNode.height = h;
  30 + }
  31 +
  32 + get height() {
  33 + if (this.canvasNode) return this.canvasNode.height;
  34 + return 0;
  35 + }
  36 +
  37 + set lineWidth(args) {
  38 + this.canvasPrototype.lineWidth = args;
  39 + this.stepList.push({
  40 + action: "lineWidth",
  41 + args,
  42 + actionType: "set",
  43 + });
  44 + }
  45 +
  46 + get lineWidth() {
  47 + return this.canvasPrototype.lineWidth;
  48 + }
  49 +
  50 + set lineCap(args) {
  51 + this.canvasPrototype.lineCap = args;
  52 + this.stepList.push({
  53 + action: "lineCap",
  54 + args,
  55 + actionType: "set",
  56 + });
  57 + }
  58 +
  59 + get lineCap() {
  60 + return this.canvasPrototype.lineCap;
  61 + }
  62 +
  63 + set lineJoin(args) {
  64 + this.canvasPrototype.lineJoin = args;
  65 + this.stepList.push({
  66 + action: "lineJoin",
  67 + args,
  68 + actionType: "set",
  69 + });
  70 + }
  71 +
  72 + get lineJoin() {
  73 + return this.canvasPrototype.lineJoin;
  74 + }
  75 +
  76 + set miterLimit(args) {
  77 + this.canvasPrototype.miterLimit = args;
  78 + this.stepList.push({
  79 + action: "miterLimit",
  80 + args,
  81 + actionType: "set",
  82 + });
  83 + }
  84 +
  85 + get miterLimit() {
  86 + return this.canvasPrototype.miterLimit;
  87 + }
  88 +
  89 + set lineDashOffset(args) {
  90 + this.canvasPrototype.lineDashOffset = args;
  91 + this.stepList.push({
  92 + action: "lineDashOffset",
  93 + args,
  94 + actionType: "set",
  95 + });
  96 + }
  97 +
  98 + get lineDashOffset() {
  99 + return this.canvasPrototype.lineDashOffset;
  100 + }
  101 +
  102 + set font(args) {
  103 + this.canvasPrototype.font = args;
  104 + this.ctx.font = args;
  105 + this.stepList.push({
  106 + action: "font",
  107 + args,
  108 + actionType: "set",
  109 + });
  110 + }
  111 +
  112 + get font() {
  113 + return this.canvasPrototype.font;
  114 + }
  115 +
  116 + set textAlign(args) {
  117 + this.canvasPrototype.textAlign = args;
  118 + this.stepList.push({
  119 + action: "textAlign",
  120 + args,
  121 + actionType: "set",
  122 + });
  123 + }
  124 +
  125 + get textAlign() {
  126 + return this.canvasPrototype.textAlign;
  127 + }
  128 +
  129 + set textBaseline(args) {
  130 + this.canvasPrototype.textBaseline = args;
  131 + this.stepList.push({
  132 + action: "textBaseline",
  133 + args,
  134 + actionType: "set",
  135 + });
  136 + }
  137 +
  138 + get textBaseline() {
  139 + return this.canvasPrototype.textBaseline;
  140 + }
  141 +
  142 + set fillStyle(args) {
  143 + this.canvasPrototype.fillStyle = args;
  144 + this.stepList.push({
  145 + action: "fillStyle",
  146 + args,
  147 + actionType: "set",
  148 + });
  149 + }
  150 +
  151 + get fillStyle() {
  152 + return this.canvasPrototype.fillStyle;
  153 + }
  154 +
  155 + set strokeStyle(args) {
  156 + this.canvasPrototype.strokeStyle = args;
  157 + this.stepList.push({
  158 + action: "strokeStyle",
  159 + args,
  160 + actionType: "set",
  161 + });
  162 + }
  163 +
  164 + get strokeStyle() {
  165 + return this.canvasPrototype.strokeStyle;
  166 + }
  167 +
  168 + set globalAlpha(args) {
  169 + this.canvasPrototype.globalAlpha = args;
  170 + this.stepList.push({
  171 + action: "globalAlpha",
  172 + args,
  173 + actionType: "set",
  174 + });
  175 + }
  176 +
  177 + get globalAlpha() {
  178 + return this.canvasPrototype.globalAlpha;
  179 + }
  180 +
  181 + set globalCompositeOperation(args) {
  182 + this.canvasPrototype.globalCompositeOperation = args;
  183 + this.stepList.push({
  184 + action: "globalCompositeOperation",
  185 + args,
  186 + actionType: "set",
  187 + });
  188 + }
  189 +
  190 + get globalCompositeOperation() {
  191 + return this.canvasPrototype.globalCompositeOperation;
  192 + }
  193 +
  194 + set shadowColor(args) {
  195 + this.canvasPrototype.shadowColor = args;
  196 + this.stepList.push({
  197 + action: "shadowColor",
  198 + args,
  199 + actionType: "set",
  200 + });
  201 + }
  202 +
  203 + get shadowColor() {
  204 + return this.canvasPrototype.shadowColor;
  205 + }
  206 +
  207 + set shadowOffsetX(args) {
  208 + this.canvasPrototype.shadowOffsetX = args;
  209 + this.stepList.push({
  210 + action: "shadowOffsetX",
  211 + args,
  212 + actionType: "set",
  213 + });
  214 + }
  215 +
  216 + get shadowOffsetX() {
  217 + return this.canvasPrototype.shadowOffsetX;
  218 + }
  219 +
  220 + set shadowOffsetY(args) {
  221 + this.canvasPrototype.shadowOffsetY = args;
  222 + this.stepList.push({
  223 + action: "shadowOffsetY",
  224 + args,
  225 + actionType: "set",
  226 + });
  227 + }
  228 +
  229 + get shadowOffsetY() {
  230 + return this.canvasPrototype.shadowOffsetY;
  231 + }
  232 +
  233 + set shadowBlur(args) {
  234 + this.canvasPrototype.shadowBlur = args;
  235 + this.stepList.push({
  236 + action: "shadowBlur",
  237 + args,
  238 + actionType: "set",
  239 + });
  240 + }
  241 +
  242 + get shadowBlur() {
  243 + return this.canvasPrototype.shadowBlur;
  244 + }
  245 +
  246 + save() {
  247 + this.stepList.push({
  248 + action: "save",
  249 + args: null,
  250 + actionType: "func",
  251 + });
  252 + }
  253 +
  254 + restore() {
  255 + this.stepList.push({
  256 + action: "restore",
  257 + args: null,
  258 + actionType: "func",
  259 + });
  260 + }
  261 +
  262 + setLineDash(...args) {
  263 + this.canvasPrototype.lineDash = args;
  264 + this.stepList.push({
  265 + action: "setLineDash",
  266 + args,
  267 + actionType: "func",
  268 + });
  269 + }
  270 +
  271 + moveTo(...args) {
  272 + this.stepList.push({
  273 + action: "moveTo",
  274 + args,
  275 + actionType: "func",
  276 + });
  277 + }
  278 +
  279 + closePath() {
  280 + this.stepList.push({
  281 + action: "closePath",
  282 + args: null,
  283 + actionType: "func",
  284 + });
  285 + }
  286 +
  287 + lineTo(...args) {
  288 + this.stepList.push({
  289 + action: "lineTo",
  290 + args,
  291 + actionType: "func",
  292 + });
  293 + }
  294 +
  295 + quadraticCurveTo(...args) {
  296 + this.stepList.push({
  297 + action: "quadraticCurveTo",
  298 + args,
  299 + actionType: "func",
  300 + });
  301 + }
  302 +
  303 + bezierCurveTo(...args) {
  304 + this.stepList.push({
  305 + action: "bezierCurveTo",
  306 + args,
  307 + actionType: "func",
  308 + });
  309 + }
  310 +
  311 + arcTo(...args) {
  312 + this.stepList.push({
  313 + action: "arcTo",
  314 + args,
  315 + actionType: "func",
  316 + });
  317 + }
  318 +
  319 + arc(...args) {
  320 + this.stepList.push({
  321 + action: "arc",
  322 + args,
  323 + actionType: "func",
  324 + });
  325 + }
  326 +
  327 + rect(...args) {
  328 + this.stepList.push({
  329 + action: "rect",
  330 + args,
  331 + actionType: "func",
  332 + });
  333 + }
  334 +
  335 + scale(...args) {
  336 + this.stepList.push({
  337 + action: "scale",
  338 + args,
  339 + actionType: "func",
  340 + });
  341 + }
  342 +
  343 + rotate(...args) {
  344 + this.stepList.push({
  345 + action: "rotate",
  346 + args,
  347 + actionType: "func",
  348 + });
  349 + }
  350 +
  351 + translate(...args) {
  352 + this.stepList.push({
  353 + action: "translate",
  354 + args,
  355 + actionType: "func",
  356 + });
  357 + }
  358 +
  359 + transform(...args) {
  360 + this.stepList.push({
  361 + action: "transform",
  362 + args,
  363 + actionType: "func",
  364 + });
  365 + }
  366 +
  367 + setTransform(...args) {
  368 + this.stepList.push({
  369 + action: "setTransform",
  370 + args,
  371 + actionType: "func",
  372 + });
  373 + }
  374 +
  375 + clearRect(...args) {
  376 + this.stepList.push({
  377 + action: "clearRect",
  378 + args,
  379 + actionType: "func",
  380 + });
  381 + }
  382 +
  383 + fillRect(...args) {
  384 + this.stepList.push({
  385 + action: "fillRect",
  386 + args,
  387 + actionType: "func",
  388 + });
  389 + }
  390 +
  391 + strokeRect(...args) {
  392 + this.stepList.push({
  393 + action: "strokeRect",
  394 + args,
  395 + actionType: "func",
  396 + });
  397 + }
  398 +
  399 + fillText(...args) {
  400 + this.stepList.push({
  401 + action: "fillText",
  402 + args,
  403 + actionType: "func",
  404 + });
  405 + }
  406 +
  407 + strokeText(...args) {
  408 + this.stepList.push({
  409 + action: "strokeText",
  410 + args,
  411 + actionType: "func",
  412 + });
  413 + }
  414 +
  415 + beginPath() {
  416 + this.stepList.push({
  417 + action: "beginPath",
  418 + args: null,
  419 + actionType: "func",
  420 + });
  421 + }
  422 +
  423 + fill() {
  424 + this.stepList.push({
  425 + action: "fill",
  426 + args: null,
  427 + actionType: "func",
  428 + });
  429 + }
  430 +
  431 + stroke() {
  432 + this.stepList.push({
  433 + action: "stroke",
  434 + args: null,
  435 + actionType: "func",
  436 + });
  437 + }
  438 +
  439 + drawFocusIfNeeded(...args) {
  440 + this.stepList.push({
  441 + action: "drawFocusIfNeeded",
  442 + args,
  443 + actionType: "func",
  444 + });
  445 + }
  446 +
  447 + clip() {
  448 + this.stepList.push({
  449 + action: "clip",
  450 + args: null,
  451 + actionType: "func",
  452 + });
  453 + }
  454 +
  455 + isPointInPath(...args) {
  456 + this.stepList.push({
  457 + action: "isPointInPath",
  458 + args,
  459 + actionType: "func",
  460 + });
  461 + }
  462 +
  463 + drawImage(...args) {
  464 + this.stepList.push({
  465 + action: "drawImage",
  466 + args,
  467 + actionType: "func",
  468 + });
  469 + }
  470 +
  471 + addHitRegion(...args) {
  472 + this.stepList.push({
  473 + action: "addHitRegion",
  474 + args,
  475 + actionType: "func",
  476 + });
  477 + }
  478 +
  479 + removeHitRegion(...args) {
  480 + this.stepList.push({
  481 + action: "removeHitRegion",
  482 + args,
  483 + actionType: "func",
  484 + });
  485 + }
  486 +
  487 + clearHitRegions(...args) {
  488 + this.stepList.push({
  489 + action: "clearHitRegions",
  490 + args,
  491 + actionType: "func",
  492 + });
  493 + }
  494 +
  495 + putImageData(...args) {
  496 + this.stepList.push({
  497 + action: "putImageData",
  498 + args,
  499 + actionType: "func",
  500 + });
  501 + }
  502 +
  503 + getLineDash() {
  504 + return this.canvasPrototype.lineDash;
  505 + }
  506 +
  507 + createLinearGradient(...args) {
  508 + return this.ctx.createLinearGradient(...args);
  509 + }
  510 +
  511 + createRadialGradient(...args) {
  512 + if (this.type === "2d") {
  513 + return this.ctx.createRadialGradient(...args);
  514 + } else {
  515 + return this.ctx.createCircularGradient(...args.slice(3, 6));
  516 + }
  517 + }
  518 +
  519 + createPattern(...args) {
  520 + return this.ctx.createPattern(...args);
  521 + }
  522 +
  523 + measureText(...args) {
  524 + return this.ctx.measureText(...args);
  525 + }
  526 +
  527 + createImageData(...args) {
  528 + return this.ctx.createImageData(...args);
  529 + }
  530 +
  531 + getImageData(...args) {
  532 + return this.ctx.getImageData(...args);
  533 + }
  534 +
  535 + async draw(reserve, func) {
  536 + const realstepList = this.stepList.slice();
  537 + this.stepList.length = 0;
  538 + if (this.type === "mina") {
  539 + if (realstepList.length > 0) {
  540 + for (const step of realstepList) {
  541 + this.implementMinaStep(step);
  542 + }
  543 + this.ctx.draw(reserve, func);
  544 + realstepList.length = 0;
  545 + }
  546 + } else if (this.type === "2d") {
  547 + if (!reserve) {
  548 + this.ctx.clearRect(0, 0, this.canvasNode.width, this.canvasNode.height);
  549 + }
  550 + if (realstepList.length > 0) {
  551 + for (const step of realstepList) {
  552 + await this.implement2DStep(step);
  553 + }
  554 + realstepList.length = 0;
  555 + }
  556 + if (func) {
  557 + func();
  558 + }
  559 + }
  560 + realstepList.length = 0;
  561 + }
  562 +
  563 + implementMinaStep(step) {
  564 + switch (step.action) {
  565 + case "textAlign": {
  566 + this.ctx.setTextAlign(step.args);
  567 + break;
  568 + }
  569 + case "textBaseline": {
  570 + this.ctx.setTextBaseline(step.args);
  571 + break;
  572 + }
  573 + default: {
  574 + if (step.actionType === "set") {
  575 + this.ctx[step.action] = step.args;
  576 + } else if (step.actionType === "func") {
  577 + if (step.args) {
  578 + this.ctx[step.action](...step.args);
  579 + } else {
  580 + this.ctx[step.action]();
  581 + }
  582 + }
  583 + break;
  584 + }
  585 + }
  586 + }
  587 +
  588 + implement2DStep(step) {
  589 + return new Promise((resolve) => {
  590 + if (step.action === "drawImage") {
  591 + const img = this.canvasNode.createImage();
  592 + img.src = step.args[0];
  593 + img.onload = () => {
  594 + this.ctx.drawImage(img, ...step.args.slice(1));
  595 + resolve();
  596 + };
  597 + } else {
  598 + if (step.actionType === "set") {
  599 + this.ctx[step.action] = step.args;
  600 + } else if (step.actionType === "func") {
  601 + if (step.args) {
  602 + this.ctx[step.action](...step.args);
  603 + } else {
  604 + this.ctx[step.action]();
  605 + }
  606 + }
  607 + resolve();
  608 + }
  609 + });
  610 + }
  611 +}
... ...
packageB/components/painter/painter.js 0 → 100644
  1 +import Pen from './lib/pen';
  2 +import Downloader from './lib/downloader';
  3 +import WxCanvas from './lib/wx-canvas';
  4 +
  5 +const util = require('./lib/util');
  6 +
  7 +const downloader = new Downloader();
  8 +
  9 +// 最大尝试的绘制次数
  10 +const MAX_PAINT_COUNT = 5;
  11 +const ACTION_DEFAULT_SIZE = 24;
  12 +const ACTION_OFFSET = '2rpx';
  13 +Component({
  14 + canvasWidthInPx: 0,
  15 + canvasHeightInPx: 0,
  16 + canvasNode: null,
  17 + paintCount: 0,
  18 + currentPalette: {},
  19 + movingCache: {},
  20 + outterDisabled: false,
  21 + isDisabled: false,
  22 + needClear: false,
  23 + /**
  24 + * 组件的属性列表
  25 + */
  26 + properties: {
  27 + use2D: {
  28 + type: Boolean,
  29 + },
  30 + customStyle: {
  31 + type: String,
  32 + },
  33 + // 运行自定义选择框和删除缩放按钮
  34 + customActionStyle: {
  35 + type: Object,
  36 + },
  37 + palette: {
  38 + type: Object,
  39 + observer: function (newVal, oldVal) {
  40 + if (this.isNeedRefresh(newVal, oldVal)) {
  41 + this.paintCount = 0;
  42 + this.startPaint();
  43 + }
  44 + },
  45 + },
  46 + dancePalette: {
  47 + type: Object,
  48 + observer: function (newVal, oldVal) {
  49 + if (!this.isEmpty(newVal) && !this.properties.use2D) {
  50 + this.initDancePalette(newVal);
  51 + }
  52 + },
  53 + },
  54 + // 缩放比,会在传入的 palette 中统一乘以该缩放比
  55 + scaleRatio: {
  56 + type: Number,
  57 + value: 1
  58 + },
  59 + widthPixels: {
  60 + type: Number,
  61 + value: 0
  62 + },
  63 + // 启用脏检查,默认 false
  64 + dirty: {
  65 + type: Boolean,
  66 + value: false,
  67 + },
  68 + LRU: {
  69 + type: Boolean,
  70 + value: true,
  71 + },
  72 + action: {
  73 + type: Object,
  74 + observer: function (newVal, oldVal) {
  75 + if (newVal && !this.isEmpty(newVal) && !this.properties.use2D) {
  76 + this.doAction(newVal, (callbackInfo) => {
  77 + this.movingCache = callbackInfo
  78 + }, false, true)
  79 + }
  80 + },
  81 + },
  82 + disableAction: {
  83 + type: Boolean,
  84 + observer: function (isDisabled) {
  85 + this.outterDisabled = isDisabled
  86 + this.isDisabled = isDisabled
  87 + }
  88 + },
  89 + clearActionBox: {
  90 + type: Boolean,
  91 + observer: function (needClear) {
  92 + if (needClear && !this.needClear) {
  93 + if (this.frontContext) {
  94 + setTimeout(() => {
  95 + this.frontContext.draw();
  96 + }, 100);
  97 + this.touchedView = {};
  98 + this.prevFindedIndex = this.findedIndex
  99 + this.findedIndex = -1;
  100 + }
  101 + }
  102 + this.needClear = needClear
  103 + }
  104 + },
  105 + },
  106 +
  107 + data: {
  108 + picURL: '',
  109 + showCanvas: true,
  110 + painterStyle: '',
  111 + },
  112 +
  113 + methods: {
  114 +
  115 + /**
  116 + * 判断一个 object 是否为 空
  117 + * @param {object} object
  118 + */
  119 + isEmpty(object) {
  120 + for (const i in object) {
  121 + return false;
  122 + }
  123 + return true;
  124 + },
  125 +
  126 + isNeedRefresh(newVal, oldVal) {
  127 + if (!newVal || this.isEmpty(newVal) || (this.data.dirty && util.equal(newVal, oldVal))) {
  128 + return false;
  129 + }
  130 + return true;
  131 + },
  132 +
  133 + getBox(rect, type) {
  134 + const boxArea = {
  135 + type: 'rect',
  136 + css: {
  137 + height: `${rect.bottom - rect.top}px`,
  138 + width: `${rect.right - rect.left}px`,
  139 + left: `${rect.left}px`,
  140 + top: `${rect.top}px`,
  141 + borderWidth: '4rpx',
  142 + borderColor: '#1A7AF8',
  143 + color: 'transparent'
  144 + }
  145 + }
  146 + if (type === 'text') {
  147 + boxArea.css = Object.assign({}, boxArea.css, {
  148 + borderStyle: 'dashed'
  149 + })
  150 + }
  151 + if (this.properties.customActionStyle && this.properties.customActionStyle.border) {
  152 + boxArea.css = Object.assign({}, boxArea.css, this.properties.customActionStyle.border)
  153 + }
  154 + Object.assign(boxArea, {
  155 + id: 'box'
  156 + })
  157 + return boxArea
  158 + },
  159 +
  160 + getScaleIcon(rect, type) {
  161 + let scaleArea = {}
  162 + const {
  163 + customActionStyle
  164 + } = this.properties
  165 + if (customActionStyle && customActionStyle.scale) {
  166 + scaleArea = {
  167 + type: 'image',
  168 + url: type === 'text' ? customActionStyle.scale.textIcon : customActionStyle.scale.imageIcon,
  169 + css: {
  170 + height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  171 + width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  172 + borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
  173 + }
  174 + }
  175 + } else {
  176 + scaleArea = {
  177 + type: 'rect',
  178 + css: {
  179 + height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  180 + width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  181 + borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
  182 + color: '#0000ff',
  183 + }
  184 + }
  185 + }
  186 + scaleArea.css = Object.assign({}, scaleArea.css, {
  187 + align: 'center',
  188 + left: `${rect.right + ACTION_OFFSET.toPx()}px`,
  189 + top: type === 'text' ? `${rect.top - ACTION_OFFSET.toPx() - scaleArea.css.height.toPx() / 2}px` : `${rect.bottom - ACTION_OFFSET.toPx() - scaleArea.css.height.toPx() / 2}px`
  190 + })
  191 + Object.assign(scaleArea, {
  192 + id: 'scale'
  193 + })
  194 + return scaleArea
  195 + },
  196 +
  197 + getDeleteIcon(rect) {
  198 + let deleteArea = {}
  199 + const {
  200 + customActionStyle
  201 + } = this.properties
  202 + if (customActionStyle && customActionStyle.scale) {
  203 + deleteArea = {
  204 + type: 'image',
  205 + url: customActionStyle.delete.icon,
  206 + css: {
  207 + height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  208 + width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  209 + borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
  210 + }
  211 + }
  212 + } else {
  213 + deleteArea = {
  214 + type: 'rect',
  215 + css: {
  216 + height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  217 + width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  218 + borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
  219 + color: '#0000ff',
  220 + }
  221 + }
  222 + }
  223 + deleteArea.css = Object.assign({}, deleteArea.css, {
  224 + align: 'center',
  225 + left: `${rect.left - ACTION_OFFSET.toPx()}px`,
  226 + top: `${rect.top - ACTION_OFFSET.toPx() - deleteArea.css.height.toPx() / 2}px`
  227 + })
  228 + Object.assign(deleteArea, {
  229 + id: 'delete'
  230 + })
  231 + return deleteArea
  232 + },
  233 +
  234 + doAction(action, callback, isMoving, overwrite) {
  235 + if (this.properties.use2D) {
  236 + return;
  237 + }
  238 + let newVal = null
  239 + if (action) {
  240 + newVal = action.view
  241 + }
  242 + if (newVal && newVal.id && this.touchedView.id !== newVal.id) {
  243 + // 带 id 的动作给撤回时使用,不带 id,表示对当前选中对象进行操作
  244 + const {
  245 + views
  246 + } = this.currentPalette;
  247 + for (let i = 0; i < views.length; i++) {
  248 + if (views[i].id === newVal.id) {
  249 + // 跨层回撤,需要重新构建三层关系
  250 + this.touchedView = views[i];
  251 + this.findedIndex = i;
  252 + this.sliceLayers();
  253 + break
  254 + }
  255 + }
  256 + }
  257 +
  258 + const doView = this.touchedView
  259 +
  260 + if (!doView || this.isEmpty(doView)) {
  261 + return
  262 + }
  263 + if (newVal && newVal.css) {
  264 + if (overwrite) {
  265 + doView.css = newVal.css
  266 + } else if (Array.isArray(doView.css) && Array.isArray(newVal.css)) {
  267 + doView.css = Object.assign({}, ...doView.css, ...newVal.css)
  268 + } else if (Array.isArray(doView.css)) {
  269 + doView.css = Object.assign({}, ...doView.css, newVal.css)
  270 + } else if (Array.isArray(newVal.css)) {
  271 + doView.css = Object.assign({}, doView.css, ...newVal.css)
  272 + } else {
  273 + doView.css = Object.assign({}, doView.css, newVal.css)
  274 + }
  275 + }
  276 + if (newVal && newVal.rect) {
  277 + doView.rect = newVal.rect;
  278 + }
  279 + if (newVal && newVal.url && doView.url && newVal.url !== doView.url) {
  280 + downloader.download(newVal.url, this.properties.LRU).then((path) => {
  281 + if (newVal.url.startsWith('https')) {
  282 + doView.originUrl = newVal.url
  283 + }
  284 + doView.url = path;
  285 + wx.getImageInfo({
  286 + src: path,
  287 + success: (res) => {
  288 + doView.sHeight = res.height
  289 + doView.sWidth = res.width
  290 + this.reDraw(doView, callback, isMoving)
  291 + },
  292 + fail: () => {
  293 + this.reDraw(doView, callback, isMoving)
  294 + }
  295 + })
  296 + }).catch((error) => {
  297 + // 未下载成功,直接绘制
  298 + console.error(error)
  299 + this.reDraw(doView, callback, isMoving)
  300 + })
  301 + } else {
  302 + (newVal && newVal.text && doView.text && newVal.text !== doView.text) && (doView.text = newVal.text);
  303 + (newVal && newVal.content && doView.content && newVal.content !== doView.content) && (doView.content = newVal.content);
  304 + this.reDraw(doView, callback, isMoving)
  305 + }
  306 + },
  307 +
  308 + reDraw(doView, callback, isMoving) {
  309 + const draw = {
  310 + width: this.currentPalette.width,
  311 + height: this.currentPalette.height,
  312 + views: this.isEmpty(doView) ? [] : [doView]
  313 + }
  314 + const pen = new Pen(this.globalContext, draw);
  315 +
  316 + if (isMoving && doView.type === 'text') {
  317 + pen.paint((callbackInfo) => {
  318 + callback && callback(callbackInfo);
  319 + this.triggerEvent('viewUpdate', {
  320 + view: this.touchedView
  321 + });
  322 + }, true, this.movingCache);
  323 + } else {
  324 + // 某些机型(华为 P20)非移动和缩放场景下,只绘制一遍会偶然性图片绘制失败
  325 + // if (!isMoving && !this.isScale) {
  326 + // pen.paint()
  327 + // }
  328 + pen.paint((callbackInfo) => {
  329 + callback && callback(callbackInfo);
  330 + this.triggerEvent('viewUpdate', {
  331 + view: this.touchedView
  332 + });
  333 + })
  334 + }
  335 +
  336 + const {
  337 + rect,
  338 + css,
  339 + type
  340 + } = doView
  341 +
  342 + this.block = {
  343 + width: this.currentPalette.width,
  344 + height: this.currentPalette.height,
  345 + views: this.isEmpty(doView) ? [] : [this.getBox(rect, doView.type)]
  346 + }
  347 + if (css && css.scalable) {
  348 + this.block.views.push(this.getScaleIcon(rect, type))
  349 + }
  350 + if (css && css.deletable) {
  351 + this.block.views.push(this.getDeleteIcon(rect))
  352 + }
  353 + const topBlock = new Pen(this.frontContext, this.block)
  354 + topBlock.paint();
  355 + },
  356 +
  357 + isInView(x, y, rect) {
  358 + return (x > rect.left &&
  359 + y > rect.top &&
  360 + x < rect.right &&
  361 + y < rect.bottom
  362 + )
  363 + },
  364 +
  365 + isInDelete(x, y) {
  366 + for (const view of this.block.views) {
  367 + if (view.id === 'delete') {
  368 + return (x > view.rect.left &&
  369 + y > view.rect.top &&
  370 + x < view.rect.right &&
  371 + y < view.rect.bottom)
  372 + }
  373 + }
  374 + return false
  375 + },
  376 +
  377 + isInScale(x, y) {
  378 + for (const view of this.block.views) {
  379 + if (view.id === 'scale') {
  380 + return (x > view.rect.left &&
  381 + y > view.rect.top &&
  382 + x < view.rect.right &&
  383 + y < view.rect.bottom)
  384 + }
  385 + }
  386 + return false
  387 + },
  388 +
  389 + touchedView: {},
  390 + findedIndex: -1,
  391 + onClick() {
  392 + const x = this.startX
  393 + const y = this.startY
  394 + const totalLayerCount = this.currentPalette.views.length
  395 + let canBeTouched = []
  396 + let isDelete = false
  397 + let deleteIndex = -1
  398 + for (let i = totalLayerCount - 1; i >= 0; i--) {
  399 + const view = this.currentPalette.views[i]
  400 + const {
  401 + rect
  402 + } = view
  403 + if (this.touchedView && this.touchedView.id && this.touchedView.id === view.id && this.isInDelete(x, y, rect)) {
  404 + canBeTouched.length = 0
  405 + deleteIndex = i
  406 + isDelete = true
  407 + break
  408 + }
  409 + if (this.isInView(x, y, rect)) {
  410 + canBeTouched.push({
  411 + view,
  412 + index: i
  413 + })
  414 + }
  415 + }
  416 + this.touchedView = {}
  417 + if (canBeTouched.length === 0) {
  418 + this.findedIndex = -1
  419 + } else {
  420 + let i = 0
  421 + const touchAble = canBeTouched.filter(item => Boolean(item.view.id))
  422 + if (touchAble.length === 0) {
  423 + this.findedIndex = canBeTouched[0].index
  424 + } else {
  425 + for (i = 0; i < touchAble.length; i++) {
  426 + if (this.findedIndex === touchAble[i].index) {
  427 + i++
  428 + break
  429 + }
  430 + }
  431 + if (i === touchAble.length) {
  432 + i = 0
  433 + }
  434 + this.touchedView = touchAble[i].view
  435 + this.findedIndex = touchAble[i].index
  436 + this.triggerEvent('viewClicked', {
  437 + view: this.touchedView
  438 + })
  439 + }
  440 + }
  441 + if (this.findedIndex < 0 || (this.touchedView && !this.touchedView.id)) {
  442 + // 证明点击了背景 或无法移动的view
  443 + this.frontContext.draw();
  444 + if (isDelete) {
  445 + this.triggerEvent('touchEnd', {
  446 + view: this.currentPalette.views[deleteIndex],
  447 + index: deleteIndex,
  448 + type: 'delete'
  449 + })
  450 + this.doAction()
  451 + } else if (this.findedIndex < 0) {
  452 + this.triggerEvent('viewClicked', {})
  453 + }
  454 + this.findedIndex = -1
  455 + this.prevFindedIndex = -1
  456 + } else if (this.touchedView && this.touchedView.id) {
  457 + this.sliceLayers();
  458 + }
  459 + },
  460 +
  461 + sliceLayers() {
  462 + const bottomLayers = this.currentPalette.views.slice(0, this.findedIndex)
  463 + const topLayers = this.currentPalette.views.slice(this.findedIndex + 1)
  464 + const bottomDraw = {
  465 + width: this.currentPalette.width,
  466 + height: this.currentPalette.height,
  467 + background: this.currentPalette.background,
  468 + views: bottomLayers
  469 + }
  470 + const topDraw = {
  471 + width: this.currentPalette.width,
  472 + height: this.currentPalette.height,
  473 + views: topLayers
  474 + }
  475 + if (this.prevFindedIndex < this.findedIndex) {
  476 + new Pen(this.bottomContext, bottomDraw).paint();
  477 + this.doAction(null, (callbackInfo) => {
  478 + this.movingCache = callbackInfo
  479 + })
  480 + new Pen(this.topContext, topDraw).paint();
  481 + } else {
  482 + new Pen(this.topContext, topDraw).paint();
  483 + this.doAction(null, (callbackInfo) => {
  484 + this.movingCache = callbackInfo
  485 + })
  486 + new Pen(this.bottomContext, bottomDraw).paint();
  487 + }
  488 + this.prevFindedIndex = this.findedIndex
  489 + },
  490 +
  491 + startX: 0,
  492 + startY: 0,
  493 + startH: 0,
  494 + startW: 0,
  495 + isScale: false,
  496 + startTimeStamp: 0,
  497 + onTouchStart(event) {
  498 + if (this.isDisabled) {
  499 + return
  500 + }
  501 + const {
  502 + x,
  503 + y
  504 + } = event.touches[0]
  505 + this.startX = x
  506 + this.startY = y
  507 + this.startTimeStamp = new Date().getTime()
  508 + if (this.touchedView && !this.isEmpty(this.touchedView)) {
  509 + const {
  510 + rect
  511 + } = this.touchedView
  512 + if (this.isInScale(x, y, rect)) {
  513 + this.isScale = true
  514 + this.movingCache = {}
  515 + this.startH = rect.bottom - rect.top
  516 + this.startW = rect.right - rect.left
  517 + } else {
  518 + this.isScale = false
  519 + }
  520 + } else {
  521 + this.isScale = false
  522 + }
  523 + },
  524 +
  525 + onTouchEnd(e) {
  526 + if (this.isDisabled) {
  527 + return
  528 + }
  529 + const current = new Date().getTime()
  530 + if ((current - this.startTimeStamp) <= 500 && !this.hasMove) {
  531 + !this.isScale && this.onClick(e)
  532 + } else if (this.touchedView && !this.isEmpty(this.touchedView)) {
  533 + this.triggerEvent('touchEnd', {
  534 + view: this.touchedView,
  535 + })
  536 + }
  537 + this.hasMove = false
  538 + },
  539 +
  540 + onTouchCancel(e) {
  541 + if (this.isDisabled) {
  542 + return
  543 + }
  544 + this.onTouchEnd(e)
  545 + },
  546 +
  547 + hasMove: false,
  548 + onTouchMove(event) {
  549 + if (this.isDisabled) {
  550 + return
  551 + }
  552 + this.hasMove = true
  553 + if (!this.touchedView || (this.touchedView && !this.touchedView.id)) {
  554 + return
  555 + }
  556 + const {
  557 + x,
  558 + y
  559 + } = event.touches[0]
  560 + const offsetX = x - this.startX
  561 + const offsetY = y - this.startY
  562 + const {
  563 + rect,
  564 + type
  565 + } = this.touchedView
  566 + let css = {}
  567 + if (this.isScale) {
  568 + const newW = this.startW + offsetX > 1 ? this.startW + offsetX : 1
  569 + if (this.touchedView.css && this.touchedView.css.minWidth) {
  570 + if (newW < this.touchedView.css.minWidth.toPx()) {
  571 + return
  572 + }
  573 + }
  574 + if (this.touchedView.rect && this.touchedView.rect.minWidth) {
  575 + if (newW < this.touchedView.rect.minWidth) {
  576 + return
  577 + }
  578 + }
  579 + const newH = this.startH + offsetY > 1 ? this.startH + offsetY : 1
  580 + css = {
  581 + width: `${newW}px`,
  582 + }
  583 + if (type !== 'text') {
  584 + if (type === 'image') {
  585 + css.height = `${(newW) * this.startH / this.startW }px`
  586 + } else {
  587 + css.height = `${newH}px`
  588 + }
  589 + }
  590 + } else {
  591 + this.startX = x
  592 + this.startY = y
  593 + css = {
  594 + left: `${rect.x + offsetX}px`,
  595 + top: `${rect.y + offsetY}px`,
  596 + right: undefined,
  597 + bottom: undefined
  598 + }
  599 + }
  600 + this.doAction({
  601 + view: {
  602 + css
  603 + }
  604 + }, (callbackInfo) => {
  605 + if (this.isScale) {
  606 + this.movingCache = callbackInfo
  607 + }
  608 + }, !this.isScale)
  609 + },
  610 +
  611 + initScreenK() {
  612 + if (!(getApp() && getApp().systemInfo && getApp().systemInfo.screenWidth)) {
  613 + try {
  614 + getApp().systemInfo = wx.getSystemInfoSync();
  615 + } catch (e) {
  616 + console.error(`Painter get system info failed, ${JSON.stringify(e)}`);
  617 + return;
  618 + }
  619 + }
  620 + this.screenK = 0.5;
  621 + if (getApp() && getApp().systemInfo && getApp().systemInfo.screenWidth) {
  622 + this.screenK = getApp().systemInfo.screenWidth / 750;
  623 + }
  624 + setStringPrototype(this.screenK, this.properties.scaleRatio);
  625 + },
  626 +
  627 + initDancePalette() {
  628 + if (this.properties.use2D) {
  629 + return;
  630 + }
  631 + this.isDisabled = true;
  632 + this.initScreenK();
  633 + this.downloadImages(this.properties.dancePalette).then(async (palette) => {
  634 + this.currentPalette = palette
  635 + const {
  636 + width,
  637 + height
  638 + } = palette;
  639 +
  640 + if (!width || !height) {
  641 + console.error(`You should set width and height correctly for painter, width: ${width}, height: ${height}`);
  642 + return;
  643 + }
  644 + this.setData({
  645 + painterStyle: `width:${width.toPx()}px;height:${height.toPx()}px;`,
  646 + });
  647 + this.frontContext || (this.frontContext = await this.getCanvasContext(this.properties.use2D, 'front'));
  648 + this.bottomContext || (this.bottomContext = await this.getCanvasContext(this.properties.use2D, 'bottom'));
  649 + this.topContext || (this.topContext = await this.getCanvasContext(this.properties.use2D, 'top'));
  650 + this.globalContext || (this.globalContext = await this.getCanvasContext(this.properties.use2D, 'k-canvas'));
  651 + new Pen(this.bottomContext, palette, this.properties.use2D).paint(() => {
  652 + this.isDisabled = false;
  653 + this.isDisabled = this.outterDisabled;
  654 + this.triggerEvent('didShow');
  655 + });
  656 + this.globalContext.draw();
  657 + this.frontContext.draw();
  658 + this.topContext.draw();
  659 + });
  660 + this.touchedView = {};
  661 + },
  662 +
  663 + startPaint() {
  664 + this.initScreenK();
  665 +
  666 + this.downloadImages(this.properties.palette).then(async (palette) => {
  667 + const {
  668 + width,
  669 + height
  670 + } = palette;
  671 +
  672 + if (!width || !height) {
  673 + console.error(`You should set width and height correctly for painter, width: ${width}, height: ${height}`);
  674 + return;
  675 + }
  676 +
  677 + let needScale = false;
  678 + // 生成图片时,根据设置的像素值重新绘制
  679 + if (width.toPx() !== this.canvasWidthInPx) {
  680 + this.canvasWidthInPx = width.toPx();
  681 + needScale = this.properties.use2D;
  682 + }
  683 + if (this.properties.widthPixels) {
  684 + setStringPrototype(this.screenK, this.properties.widthPixels / this.canvasWidthInPx)
  685 + this.canvasWidthInPx = this.properties.widthPixels
  686 + }
  687 +
  688 + if (this.canvasHeightInPx !== height.toPx()) {
  689 + this.canvasHeightInPx = height.toPx();
  690 + needScale = needScale || this.properties.use2D;
  691 + }
  692 + this.setData({
  693 + photoStyle: `width:${this.canvasWidthInPx}px;height:${this.canvasHeightInPx}px;`,
  694 + });
  695 + if (!this.photoContext) {
  696 + this.photoContext = await this.getCanvasContext(this.properties.use2D, 'photo');
  697 + }
  698 + if (needScale) {
  699 + const scale = getApp().systemInfo.pixelRatio;
  700 + this.photoContext.width = this.canvasWidthInPx * scale;
  701 + this.photoContext.height = this.canvasHeightInPx * scale;
  702 + this.photoContext.scale(scale, scale);
  703 + }
  704 + new Pen(this.photoContext, palette).paint(() => {
  705 + this.saveImgToLocal();
  706 + });
  707 + setStringPrototype(this.screenK, this.properties.scaleRatio);
  708 + });
  709 + },
  710 +
  711 + downloadImages(palette) {
  712 + return new Promise((resolve, reject) => {
  713 + let preCount = 0;
  714 + let completeCount = 0;
  715 + const paletteCopy = JSON.parse(JSON.stringify(palette));
  716 + if (paletteCopy.background) {
  717 + preCount++;
  718 + downloader.download(paletteCopy.background, this.properties.LRU).then((path) => {
  719 + paletteCopy.background = path;
  720 + completeCount++;
  721 + if (preCount === completeCount) {
  722 + resolve(paletteCopy);
  723 + }
  724 + }, () => {
  725 + completeCount++;
  726 + if (preCount === completeCount) {
  727 + resolve(paletteCopy);
  728 + }
  729 + });
  730 + }
  731 + if (paletteCopy.views) {
  732 + for (const view of paletteCopy.views) {
  733 + if (view && view.type === 'image' && view.url) {
  734 + preCount++;
  735 + /* eslint-disable no-loop-func */
  736 + downloader.download(view.url, this.properties.LRU).then((path) => {
  737 + view.originUrl = view.url;
  738 + view.url = path;
  739 + wx.getImageInfo({
  740 + src: path,
  741 + success: (res) => {
  742 + // 获得一下图片信息,供后续裁减使用
  743 + view.sWidth = res.width;
  744 + view.sHeight = res.height;
  745 + },
  746 + fail: (error) => {
  747 + // 如果图片坏了,则直接置空,防止坑爹的 canvas 画崩溃了
  748 + view.url = "";
  749 + console.error(`getImageInfo ${view.url} failed, ${JSON.stringify(error)}`);
  750 + },
  751 + complete: () => {
  752 + completeCount++;
  753 + if (preCount === completeCount) {
  754 + resolve(paletteCopy);
  755 + }
  756 + },
  757 + });
  758 + }, () => {
  759 + completeCount++;
  760 + if (preCount === completeCount) {
  761 + resolve(paletteCopy);
  762 + }
  763 + });
  764 + }
  765 + }
  766 + }
  767 + if (preCount === 0) {
  768 + resolve(paletteCopy);
  769 + }
  770 + });
  771 + },
  772 +
  773 + saveImgToLocal() {
  774 + const that = this;
  775 + setTimeout(() => {
  776 + wx.canvasToTempFilePath({
  777 + canvasId: 'photo',
  778 + canvas: that.properties.use2D ? that.canvasNode : null,
  779 + destWidth: that.canvasWidthInPx * getApp().systemInfo.pixelRatio,
  780 + destHeight: that.canvasHeightInPx * getApp().systemInfo.pixelRatio,
  781 + success: function (res) {
  782 + that.getImageInfo(res.tempFilePath);
  783 + },
  784 + fail: function (error) {
  785 + console.error(`canvasToTempFilePath failed, ${JSON.stringify(error)}`);
  786 + that.triggerEvent('imgErr', {
  787 + error: error
  788 + });
  789 + },
  790 + }, this);
  791 + }, 300);
  792 + },
  793 +
  794 +
  795 + getCanvasContext(use2D, id) {
  796 + const that = this;
  797 + return new Promise(resolve => {
  798 + if (use2D) {
  799 + const query = wx.createSelectorQuery().in(that);
  800 + const selectId = `#${id}`;
  801 + query.select(selectId)
  802 + .fields({ node: true, size: true })
  803 + .exec((res) => {
  804 + that.canvasNode = res[0].node;
  805 + const ctx = that.canvasNode.getContext('2d');
  806 + const wxCanvas = new WxCanvas('2d', ctx, id, true, that.canvasNode);
  807 + resolve(wxCanvas);
  808 + });
  809 + } else {
  810 + const temp = wx.createCanvasContext(id, that);
  811 + resolve(new WxCanvas('mina', temp, id, true));
  812 + }
  813 + })
  814 + },
  815 +
  816 + getImageInfo(filePath) {
  817 + const that = this;
  818 + wx.getImageInfo({
  819 + src: filePath,
  820 + success: (infoRes) => {
  821 + if (that.paintCount > MAX_PAINT_COUNT) {
  822 + const error = `The result is always fault, even we tried ${MAX_PAINT_COUNT} times`;
  823 + console.error(error);
  824 + that.triggerEvent('imgErr', {
  825 + error: error
  826 + });
  827 + return;
  828 + }
  829 + // 比例相符时才证明绘制成功,否则进行强制重绘制
  830 + if (Math.abs((infoRes.width * that.canvasHeightInPx - that.canvasWidthInPx * infoRes.height) / (infoRes.height * that.canvasHeightInPx)) < 0.01) {
  831 + that.triggerEvent('imgOK', {
  832 + path: filePath
  833 + });
  834 + } else {
  835 + that.startPaint();
  836 + }
  837 + that.paintCount++;
  838 + },
  839 + fail: (error) => {
  840 + console.error(`getImageInfo failed, ${JSON.stringify(error)}`);
  841 + that.triggerEvent('imgErr', {
  842 + error: error
  843 + });
  844 + },
  845 + });
  846 + },
  847 + },
  848 +});
  849 +
  850 +
  851 +function setStringPrototype(screenK, scale) {
  852 + /* eslint-disable no-extend-native */
  853 + /**
  854 + * 是否支持负数
  855 + * @param {Boolean} minus 是否支持负数
  856 + * @param {Number} baseSize 当设置了 % 号时,设置的基准值
  857 + */
  858 + String.prototype.toPx = function toPx(minus, baseSize) {
  859 + if (this === '0') {
  860 + return 0
  861 + }
  862 + let reg;
  863 + if (minus) {
  864 + reg = /^-?[0-9]+([.]{1}[0-9]+){0,1}(rpx|px|%)$/g;
  865 + } else {
  866 + reg = /^[0-9]+([.]{1}[0-9]+){0,1}(rpx|px|%)$/g;
  867 + }
  868 + const results = reg.exec(this);
  869 + if (!this || !results) {
  870 + console.error(`The size: ${this} is illegal`);
  871 + return 0;
  872 + }
  873 + const unit = results[2];
  874 + const value = parseFloat(this);
  875 +
  876 + let res = 0;
  877 + if (unit === 'rpx') {
  878 + res = Math.round(value * (screenK || 0.5) * (scale || 1));
  879 + } else if (unit === 'px') {
  880 + res = Math.round(value * (scale || 1));
  881 + } else if (unit === '%') {
  882 + res = Math.round(value * baseSize / 100);
  883 + }
  884 + return res;
  885 + };
  886 +}
0 887 \ No newline at end of file
... ...
packageB/components/painter/painter.json 0 → 100644
  1 +{
  2 + "component": true,
  3 + "usingComponents": {}
  4 +}
0 5 \ No newline at end of file
... ...
packageB/components/painter/painter.wxml 0 → 100644
  1 +<view style='position: relative;{{customStyle}};{{painterStyle}}'>
  2 + <block wx:if="{{!use2D}}">
  3 + <canvas canvas-id="photo" style="{{photoStyle}};position: absolute; left: -9999px; top: -9999rpx;" />
  4 + <canvas canvas-id="bottom" style="{{painterStyle}};position: absolute;" />
  5 + <canvas canvas-id="k-canvas" style="{{painterStyle}};position: absolute;" />
  6 + <canvas canvas-id="top" style="{{painterStyle}};position: absolute;" />
  7 + <canvas
  8 + canvas-id="front"
  9 + style="{{painterStyle}};position: absolute;"
  10 + bindtouchstart="onTouchStart"
  11 + bindtouchmove="onTouchMove"
  12 + bindtouchend="onTouchEnd"
  13 + bindtouchcancel="onTouchCancel"
  14 + disable-scroll="{{true}}" />
  15 + </block>
  16 + <block wx:if="{{use2D}}">
  17 + <canvas type="2d" id="photo" style="{{photoStyle}};" />
  18 + <!-- <canvas type="2d" id="bottom" style="{{painterStyle}};position: absolute;" />
  19 + <canvas type="2d" id="k-canvas" style="{{painterStyle}};position: absolute;" />
  20 + <canvas type="2d" id="top" style="{{painterStyle}};position: absolute;" />
  21 + <canvas
  22 + type="2d"
  23 + id="front"
  24 + style="{{painterStyle}};position: absolute;"
  25 + bindtouchstart="onTouchStart"
  26 + bindtouchmove="onTouchMove"
  27 + bindtouchend="onTouchEnd"
  28 + bindtouchcancel="onTouchCancel"
  29 + disable-scroll="{{true}}" /> -->
  30 + </block>
  31 +</view>
... ...
packageB/pages/zuhegou/index/index.js 0 → 100644
  1 +
  2 +const app = getApp();
  3 +let self = null;
  4 +
  5 +let imgDraw = {
  6 + "width": "650px",
  7 + "height": "843px",
  8 + "background": "https://mshopimg.yolipai.net/miniapp/images/zhg/bg-zuhegou.jpg",
  9 + "views": [
  10 + { // 头像
  11 + "type": "image",
  12 + "url": "https://desk-fd.zol-img.com.cn/t_s960x600c5/g5/M00/0F/08/ChMkJlauzXWIDrXBAAdCg2xP7oYAAH9FQOpVAIAB0Kb342.jpg",
  13 + "css": {
  14 + "width": "100px",
  15 + "height": "100px",
  16 + "top": "20px",
  17 + "left": "275px",
  18 + "borderRadius": "100px",
  19 + "mode": "scaleToFill",
  20 + "borderWidth": "2px",
  21 + "borderColor": "#fff",
  22 + }
  23 + },
  24 + { //昵称
  25 + "type": "text",
  26 + "text": "我是店铺名称名称名称名称名称名称名称名称名称名称名称名称名称名称",
  27 + "css": {
  28 + "width": "400px",
  29 + "color": "#fff",
  30 + "top": "136px",
  31 + "left": "137px",
  32 + "fontSize": "26px",
  33 + "maxLines": "1",
  34 + }
  35 + },
  36 + { //10元任选3件
  37 + "type": "text",
  38 + "text": "10元任选3件",
  39 + "css": {
  40 + "color": "#fff",
  41 + "width": "650px",
  42 + "top": "270px",
  43 + "fontSize": "70px",
  44 + "maxLines": "1",
  45 + "textAlign": "center",
  46 + "fontWeight": "bold",
  47 + }
  48 + },
  49 + { //活动时间
  50 + "type": "text",
  51 + "text": "活动时间:2021.07.06 - 2021.07.08",
  52 + "css": {
  53 + "color": "#333",
  54 + "width": "650px",
  55 + "top": "440px",
  56 + "fontSize": "26px",
  57 + "fontWeight": "normal",
  58 + "maxLines": "1",
  59 + "textAlign": "center"
  60 + }
  61 + },
  62 + { // 码
  63 + "type": "qrcode",
  64 + "content": "二维码地址",
  65 + "css": {
  66 + "color": "#000",
  67 + "width": "200px",
  68 + "height": "200px",
  69 + "top": "525px",
  70 + "left": "225px",
  71 + }
  72 + },
  73 + { // 扫码提示
  74 + "type": "text",
  75 + "text": "长按扫码即可参与活动",
  76 + "css": {
  77 + "color": "#666",
  78 + "width": "650px",
  79 + "top": "750px",
  80 + "fontSize": "26px",
  81 + "textAlign": "center"
  82 + }
  83 + },
  84 + ]
  85 +};
  86 +
  87 +
  88 +Page({
  89 +
  90 + /**
  91 + * 页面的初始数据
  92 + */
  93 + data: {
  94 + showRule: false,
  95 + showMask: false,
  96 + showNum: false,
  97 + haveAdded: false,
  98 + imgDraw: imgDraw,
  99 + },
  100 +
  101 + /**
  102 + * 生命周期函数--监听页面加载
  103 + */
  104 + onLoad: function (options) {
  105 + self = this;
  106 + },
  107 +
  108 + /**
  109 + * 生命周期函数--监听页面初次渲染完成
  110 + */
  111 + onReady: function () {
  112 +
  113 + },
  114 +
  115 + /**
  116 + * 生命周期函数--监听页面显示
  117 + */
  118 + onShow: function () {
  119 +
  120 + },
  121 +
  122 + /**
  123 + * 生命周期函数--监听页面隐藏
  124 + */
  125 + onHide: function () {
  126 +
  127 + },
  128 +
  129 + /**
  130 + * 生命周期函数--监听页面卸载
  131 + */
  132 + onUnload: function () {
  133 +
  134 + },
  135 +
  136 + /**
  137 + * 页面相关事件处理函数--监听用户下拉动作
  138 + */
  139 + onPullDownRefresh: function () {
  140 +
  141 + },
  142 +
  143 + /**
  144 + * 页面上拉触底事件的处理函数
  145 + */
  146 + onReachBottom: function () {
  147 +
  148 + },
  149 +
  150 + /**
  151 + * 用户点击右上角分享
  152 + */
  153 + onShareAppMessage: function () {
  154 +
  155 + },
  156 +
  157 + /**
  158 + * 点击规则详情,弹出规则
  159 + */
  160 + showRule() {
  161 + this.setData({
  162 + type: 0, //控制显示规则详情还是提示信息,0规则详情,1提示信息
  163 + showRule: true,
  164 + showMask: true,
  165 + });
  166 + },
  167 +
  168 + /**
  169 + * 点击关闭按钮关闭规则详情弹窗
  170 + */
  171 + closeRule() {
  172 + this.setData({
  173 + showRule: false,
  174 + showMask: false,
  175 + });
  176 + },
  177 +
  178 + /**
  179 + * 点击提示信息
  180 + */
  181 + showInfo() {
  182 + this.setData({
  183 + type: 1,
  184 + showRule: true,
  185 + showMask: true,
  186 + });
  187 + },
  188 +
  189 + /**
  190 + * 加入购物车
  191 + */
  192 + addToCart() {
  193 + let haveAdded = !this.data.haveAdded;
  194 + let title = '';
  195 + if(haveAdded) {
  196 + title = '加入购物车成功';
  197 + this.setData({
  198 + num: 1,
  199 + showNum: true,
  200 + haveAdded,
  201 + });
  202 + wx.showToast({
  203 + title: title,
  204 + icon: 'success',
  205 + });
  206 + } else {
  207 + wx.showModal({
  208 + title: '温馨提示',
  209 + content: '确定将该商品移出购物车?',
  210 + success (res) {
  211 + if (res.confirm) {
  212 + console.log('用户点击确定');
  213 + title = '移除购物车成功';
  214 + self.setData({
  215 + showNum: false,
  216 + haveAdded: false,
  217 + });
  218 + wx.showToast({
  219 + title: title,
  220 + icon: 'success',
  221 + });
  222 + } else if (res.cancel) {
  223 + console.log('用户点击取消')
  224 + self.setData({
  225 + haveAdded: true,
  226 + });
  227 + }
  228 + }
  229 + })
  230 +
  231 + }
  232 +
  233 + },
  234 +
  235 + /**
  236 + * 增加数量
  237 + */
  238 + add() {
  239 + this.setData({
  240 + num: ++this.data.num,
  241 + });
  242 + },
  243 +
  244 + /**
  245 + * 减少数量
  246 + */
  247 + sub() {
  248 + let num = this.data.num;
  249 + if(num >= 2) {
  250 + this.setData({
  251 + num: --this.data.num,
  252 + });
  253 + };
  254 + if(num == 1) {
  255 + // this.setData({
  256 + // showNum: false,
  257 + // haveAdded: false,
  258 + // });
  259 + wx.showModal({
  260 + title: '温馨提示',
  261 + content: '确定将该商品移出购物车?',
  262 + success (res) {
  263 + if (res.confirm) {
  264 + console.log('用户点击确定');
  265 + // title = '移除购物车成功';
  266 + self.setData({
  267 + showNum: false,
  268 + haveAdded: false,
  269 + });
  270 + wx.showToast({
  271 + title: '移除购物车成功',
  272 + icon: 'success',
  273 + });
  274 + } else if (res.cancel) {
  275 + console.log('用户点击取消')
  276 + self.setData({
  277 + haveAdded: true,
  278 + });
  279 + }
  280 + }
  281 + })
  282 + }
  283 + },
  284 +
  285 + /**
  286 + * 分享
  287 + */
  288 + share() {
  289 + this.setData({
  290 + showMask: true,
  291 + showPoster: true,
  292 + });
  293 + },
  294 +
  295 + /**
  296 + * 关闭海报
  297 + */
  298 + closePoster() {
  299 + this.setData({
  300 + showMask: false,
  301 + showPoster: false,
  302 + });
  303 + },
  304 +
  305 + onImgOK(e) {
  306 + console.log(e.detail.path);
  307 + this.setData({
  308 + myimg: e.detail.path,
  309 + });
  310 + },
  311 +})
0 312 \ No newline at end of file
... ...
packageB/pages/zuhegou/index/index.json 0 → 100644
  1 +{
  2 + "usingComponents": {
  3 + "catch": "/components/catch/catch",
  4 + "nodata": "/components/nodata/nodata",
  5 + "painter": "/packageB/components/painter/painter"
  6 + },
  7 + "enablePullDownRefresh": false,
  8 + "navigationBarTitleText": "组合购"
  9 +}
0 10 \ No newline at end of file
... ...
packageB/pages/zuhegou/index/index.wxml 0 → 100644
  1 +<view class="container">
  2 + <view class="flex jc_sb">
  3 + <view class="white">以下商品10元任选3件</view>
  4 + <view class="rule-container" bindtap="showRule">规则详情<text class="iconfont icon-arrow_right"></text></view>
  5 + </view>
  6 + <view class="countdown-container">距结束还剩 1天<text class="num-box">12</text>:<text class="num-box">12</text>:<text class="num-box">00</text></view>
  7 + <!-- 商品列表 -->
  8 + <view class="list-container">
  9 + <view class="list">
  10 +
  11 + <view class="list-item">
  12 + <view class="img-container"><image src="../../../images/luckDraw/yhq.png" mode="aspectFit" class="img-block"></image></view>
  13 + <view class="pdl20 flex f1 fdc jc_sb">
  14 + <view>
  15 + <view class="fs30 ellipsis-2">商品名称商品名称</view>
  16 + <view class="num-container" wx:if="{{showNum}}">
  17 + <text class="num-box c-dedfe3" bindtap="sub">-</text>
  18 + <text class="num-box">{{num}}</text>
  19 + <text class="num-box" bindtap="add">+</text>
  20 + </view>
  21 + </view>
  22 +
  23 + <view class="flex jc_sb ai_end pdt12">
  24 + <view>
  25 + <text class="rmb fs30 c-red">0.01</text>
  26 + <text class="rmb fs20 line-through cb">200.00</text>
  27 + </view>
  28 + <view class="c-red pdr10 {{haveAdded ? 'active':''}}" bindtap="addToCart"><text class="iconfont icon-gouwuche fs60"></text></view>
  29 + </view>
  30 + </view>
  31 + </view>
  32 +
  33 +
  34 + <!-- <nodata nodataContainer="t-c" wx:if="{{list.pageData.length == 0}}"></nodata> -->
  35 + <nodata nodataContainer="t-c"></nodata>
  36 + </view>
  37 +
  38 + <view class="noMore">没有更多了</view>
  39 +
  40 +
  41 + </view>
  42 + <!-- 底部栏 -->
  43 + <view class="bar-container">
  44 + <view class="t-c bg-fcfb c-fa8b2b fs28 pd20">再买2件,下单立享【10元任选3件】</view>
  45 + <view class="flex jc_sb ai_c pd20">
  46 + <view class="fs30">合计:<text class="rmb c-red fs40">0.01</text><text class="iconfont icon-info cb fs24 mgl10" bindtap="showInfo"></text></view>
  47 + <view class="btn-container">去购物车</view>
  48 + </view>
  49 + </view>
  50 + <!-- 分享 -->
  51 + <view class="share-container" bindtap="share">
  52 + <text class="iconfont icon-zhuanfa fs40"></text>
  53 + <view class="fs22 c-8">分享</view>
  54 + </view>
  55 +
  56 +
  57 + <!-- 规则详情和提示信息弹窗 -->
  58 + <view class="rule-pop-container" wx:if="{{showRule}}">
  59 + <view class="pop-title">{{!type ? '规则详情':'提示'}}<text class="iconfont icon-guan" bindtap="closeRule"></text></view>
  60 + <block wx:if="{{!type}}">
  61 + <view class="pdt40">
  62 + <view class="pdb20">活动时间</view>
  63 + <view class="">2020.12.11 18:00:00 至 2020.12.11 18:00:00</view>
  64 + </view>
  65 + <view class="pdt40">
  66 + <view class="pdb20">活动内容</view>
  67 + <view class="">
  68 + <view class="pdb10">1、活动商品范围内,任选3件仅需10元;</view>
  69 + <view class="">2、此优惠可无限叠加,买得越多优惠越多;</view>
  70 + </view>
  71 + </view>
  72 + <view class="pdt40 pdb20">
  73 + <view class="pdb20">优惠叠加</view>
  74 + <view class="">不可与其他优惠活动同时使用</view>
  75 + </view>
  76 + </block>
  77 + <block wx:else>
  78 + <view class="pdt40 pdb20">
  79 +
  80 + <!-- <view class="pdb20">优惠叠加</view> -->
  81 +
  82 + <view class="">合计金额及提示仅为初步预估,请以最终下单金额为准。</view>
  83 + </view>
  84 + </block>
  85 + <view class="pdv20">
  86 + <view class="btn" bindtap="closeRule">我知道了</view>
  87 + </view>
  88 + </view>
  89 +
  90 + <!-- 遮罩层 -->
  91 + <view class="mask" wx:if="{{showMask}}"></view>
  92 +
  93 + <!-- 海报 -->
  94 + <view class="poster-container" wx:if="{{showPoster}}">
  95 + <view class="t-r pdb20 white2"><text class="iconfont icon-close fs40" bindtap="closePoster"></text></view>
  96 + <image src="{{myimg}}" class="poster" show-menu-by-longpress></image>
  97 + <view class="pdt10 fs26 white2"><text class="iconfont icon-zhiwen"></text> 长按图片保存至相册</view>
  98 + </view>
  99 +
  100 +</view>
  101 +
  102 +<painter style="position: absolute; top: -9999rpx;" palette="{{imgDraw}}" bind:imgOK="onImgOK" />
  103 +
  104 +<!-- 活动异常提醒 -->
  105 +<!-- <catch>当前活动已结束</catch> -->
  106 +
  107 +
  108 +
... ...
packageB/pages/zuhegou/index/index.wxss 0 → 100644
  1 +/* packageB//pages/zuhegou/index/index.wxss */
  2 +page {
  3 + background: #f5f5f5 linear-gradient(to bottom, #FB5A2F, #FA7958, #f5f5f5 26%) no-repeat;
  4 +}
  5 +.container {
  6 + padding: 20rpx 20rpx 240rpx 20rpx;
  7 +}
  8 +.countdown-container {
  9 + font-size: 26rpx;
  10 + padding-top: 10rpx;
  11 + color: white;
  12 +}
  13 +
  14 +.num-box {
  15 + display: inline-block;
  16 + background-color: rgba(255,255,255,.2);
  17 + box-sizing: border-box;
  18 + min-width: 40rpx;
  19 + padding: 4rpx;
  20 + margin: 0 8rpx;
  21 + border-radius: 6rpx;
  22 + text-align: center;
  23 +}
  24 +
  25 +.rule-container {
  26 + font-size: 24rpx;
  27 + background-color: rgba(255,255,255,.2);
  28 + padding: 10rpx 5rpx 10rpx 20rpx;
  29 + border-radius: 26rpx 0 0 26rpx;
  30 + margin-right: -20rpx;
  31 + color: white;
  32 +}
  33 +
  34 +.icon-arrow_right {
  35 + font-size: 24rpx;
  36 +}
  37 +
  38 +.list-container {
  39 + padding-top: 30rpx;
  40 +}
  41 +
  42 +.list {
  43 + border-radius: 10rpx;
  44 + background-color: white;
  45 +}
  46 +
  47 +.list-item {
  48 + padding: 20rpx;
  49 + display: flex;
  50 +}
  51 +
  52 +.img-container {
  53 + width: 200rpx;
  54 + height: 200rpx;
  55 + background-color: #f0f0f0;
  56 + border-radius: 8rpx;
  57 +}
  58 +
  59 +.num-container {
  60 + font-size: 28rpx;
  61 + padding-top: 10rpx;
  62 + text-align: right;
  63 +}
  64 +
  65 +.num-container .num-box {
  66 + background-color: #f3f3f3;
  67 +}
  68 +
  69 +
  70 +
  71 +.noMore {
  72 + padding: 20rpx;
  73 + color: #bbb;
  74 + text-align: center;
  75 + font-size: 26rpx;
  76 +}
  77 +
  78 +.rmb::before {
  79 + content: '¥';
  80 + font-size: 22rpx;
  81 +}
  82 +
  83 +.bar-container {
  84 + position: fixed;
  85 + left: 0;
  86 + bottom: 0;
  87 + width: 100%;
  88 + background-color: white;
  89 +}
  90 +
  91 +.bg-fcfb {
  92 + background-color: #FCFBE5;
  93 +}
  94 +
  95 +.c-fa8b2b {
  96 + color: #FA8B2B;
  97 +}
  98 +
  99 +.c-8 {
  100 + color: #888;
  101 +}
  102 +
  103 +.c-dedfe3 {
  104 + color: #DEDFE3;
  105 +}
  106 +
  107 +.btn-container {
  108 + padding: 16rpx 40rpx;
  109 + background-color: red;
  110 + color: white;
  111 + font-size: 30rpx;
  112 + border-radius: 36rpx;
  113 + background: -webkit-linear-gradient(left,#ff5000,#ff2000) no-repeat;
  114 +}
  115 +
  116 +.share-container {
  117 + position: fixed;
  118 + right: 20rpx;
  119 + bottom: 200rpx;
  120 + width: 100rpx;
  121 + height: 100rpx;
  122 + border-radius: 50%;
  123 + background-color: white;
  124 + display: flex;
  125 + flex-direction: column;
  126 + justify-content: center;
  127 + align-items: center;
  128 + box-shadow: 0 0 16rpx #CCC;
  129 +}
  130 +
  131 +.mask {
  132 + position: fixed;
  133 + top: 0;
  134 + bottom: 0;
  135 + left: 0;
  136 + right: 0;
  137 + background-color: rgba(0,0,0,.6);
  138 +}
  139 +
  140 +.rule-pop-container {
  141 + position: fixed;
  142 + left: 0;
  143 + bottom: 0;
  144 + width: 100%;
  145 + background-color: white;
  146 + z-index: 1;
  147 + border-radius: 16rpx 16rpx 0 0;
  148 + padding: 0 40rpx;
  149 + box-sizing: border-box;
  150 + font-size: 28rpx;
  151 + color: #444;
  152 +}
  153 +
  154 +.pop-title {
  155 + padding: 20rpx 0;
  156 + text-align: center;
  157 + font-size: 32rpx;
  158 + position: relative;
  159 +}
  160 +.icon-guan {
  161 + position: absolute;
  162 + right: 0;
  163 + font-size: 26rpx;
  164 + top: 50%;
  165 + transform: translateY(-50%);
  166 + color: #ccc;
  167 +}
  168 +
  169 +.btn {
  170 + color: white;
  171 + padding: 20rpx 0;
  172 + text-align: center;
  173 + border-radius: 40rpx;
  174 + background: -webkit-linear-gradient(left,#ff5000,#ff2000) no-repeat;
  175 +}
  176 +
  177 +.active {
  178 + color: #ccc;
  179 +}
  180 +
  181 +.poster-container {
  182 + text-align: center;
  183 + color: white;
  184 + position: fixed;
  185 + top: 50%;
  186 + left: 50%;
  187 + transform: translate(-50%, -50%);
  188 +}
  189 +
  190 +.poster {
  191 + width: 650rpx;
  192 + height: 843rpx;
  193 + border-radius: 16rpx;
  194 + margin: 0 auto;
  195 +}
  196 +
  197 +.white2 {
  198 + color: rgba(255,255,255,.8);
  199 +}
0 200 \ No newline at end of file
... ...