From ac07f757e0a0c6408e1e7cfbf125dbfc5d6d285f Mon Sep 17 00:00:00 2001 From: CPPAlien Date: Mon, 21 Jun 2021 11:34:57 +0800 Subject: [PATCH 1/2] feat: add calc feature --- README.md | 84 ++++++++-- components/painter/lib/calc.js | 50 ++++++ components/painter/lib/pen.js | 283 +++++++++++++++------------------ components/painter/painter.js | 62 +++++--- pages/example/example.js | 2 +- palette/relative.js | 55 +++++++ 6 files changed, 343 insertions(+), 193 deletions(-) create mode 100644 components/painter/lib/calc.js create mode 100644 palette/relative.js diff --git a/README.md b/README.md index 8f83ebe..308d92a 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,8 @@ **TODO** - [x] canvas2d 接口支持 -- [ ] base64 图片支持 [测试版本](https://github.com/Kujiale-Mobile/Painter/tree/base64) +- [x] base64 图片支持 +- [x] calc 支持 - [ ] node 端服务版的 painter - [ ] line-space 属性支持 - [ ] 三角形等常用图形的支持 @@ -411,30 +412,81 @@ function _textDecoration(decoration, index, color) { ![](https://user-images.githubusercontent.com/4279515/46778627-290ead80-cd47-11e8-8483-2e36e39b36f0.png) -#### 相对布局方法 - -很多人有获得文本宽度的需求,因为文本宽度随着字数不同而动态变化,如果想在文本后面加个图标,那么我们就需要获得文本宽度。Painter 的解决方案如下: +#### 相对布局 +1,在需要暴露自己位置的信息的元素上增加一个 id 属性,如下: ``` -1,首先你需要为检测长度的文本添加一个 id。如下 { - id: 'my-text-id', + id: 'myTextId', type: 'text', - -2,然后在后面的 view 中,你可以在 left、right、top属性中使用这个id。如下 -left: ['10rpx', 'my-text-id', 比例] -表示布局在距离左边(10rpx + 该text文本宽度 * 比例) 的距离,比例默认为 1,可省去,你也可以使用负数或小数来做计算,最终的 left 会加上文本宽度乘以该数的值。 - + ... +} +``` +2,然后在后面的 view 中,你可以在 left、right、top、bottom、width、height 属性中使用 calc 属性。如下 +``` +left: 'calc(myTextId.bottom + 100rpx)' +width: 'calc(myTextId.width * 2)' ``` -注意: +
例子代码(点击展开)
-- 比例一定为一个 number -- 获得的长度为 view 自身的尺寸,而非该 view 到对应边的 距离 + 自身尺寸 +```javascript +{ + width: '654rpx', + height: '1000rpx', + background: '#eee', + views: [ + { + id: 'one', + type: 'rect', + css: { + width: '200rpx', + left: '50%', + align: 'center', + top: '30rpx', + height: '100rpx', + }, + }, + { + id: 'two', + type: 'rect', + css: { + width: '200rpx', + left: 'calc(one.left + 100rpx)', + align: 'center', + top: 'calc(one.bottom + 10rpx)', + height: '100rpx', + }, + }, + { + id: 'three', + type: 'rect', + css: { + width: '200rpx', + left: 'calc(two.left + 100rpx)', + align: 'center', + top: 'calc(two.bottom + 3 * 10rpx)', + height: '100rpx', + }, + }, + { + id: 'four', + type: 'rect', + css: { + width: 'calc(2 * one.width)', + left: 'calc(one.left)', + align: 'center', + top: 'calc(three.bottom + 10rpx)', + height: '100rpx', + }, + }, + ], +} +``` -如果想获得高度,top 也支持上述用法,并且除文本外,你可以对任何 view 设置一个 id,然后使用上述方法进行相对布局。 +
-**注:相对布局的那个 view 代码一定需要在被相对的 view 的下面。** +**注:相对布局的 view 代码一定需要在被相对的 view 的之后。** #### border 类型 diff --git a/components/painter/lib/calc.js b/components/painter/lib/calc.js new file mode 100644 index 0000000..27dfbc1 --- /dev/null +++ b/components/painter/lib/calc.js @@ -0,0 +1,50 @@ +var calculate = function (s) { + s = s.trim(); + const stack = new Array(); + let preSign = '+'; + let numStr = ''; + const n = s.length; + for (let i = 0; i < n; ++i) { + if (s[i] === '.' || (!isNaN(Number(s[i])) && s[i] !== ' ')) { + numStr += s[i]; + } else if (s[i] === '(') { + let isClose = 1; + let j = i; + while (isClose > 0) { + j += 1; + if (s[j] === '(') isClose += 1; + if (s[j] === ')') isClose -= 1; + } + numStr = `${calculate(s.slice(i + 1, j))}`; + i = j; + } + if (isNaN(Number(s[i]) && s[i] !== '.') || i === n - 1) { + let num = parseFloat(numStr); + switch (preSign) { + case '+': + stack.push(num); + break; + case '-': + stack.push(-num); + break; + case '*': + stack.push(stack.pop() * num); + break; + case '/': + stack.push(stack.pop() / num); + break; + default: + break; + } + preSign = s[i]; + numStr = ''; + } + } + let ans = 0; + while (stack.length) { + ans += stack.pop(); + } + return ans; +}; + +module.exports = calculate; diff --git a/components/painter/lib/pen.js b/components/painter/lib/pen.js index 6758ee3..18d4733 100644 --- a/components/painter/lib/pen.js +++ b/components/painter/lib/pen.js @@ -5,20 +5,22 @@ export default class Painter { constructor(ctx, data) { this.ctx = ctx; this.data = data; - this.globalWidth = {}; - this.globalHeight = {}; } - isMoving = false - movingCache = {} + isMoving = false; + // 动态模板时的缓存,加速渲染 + movingCache = {}; + callbackInfo = {}; + // 用于存储带 id 的 view 的 rect 信息 + viewRect = {}; paint(callback, isMoving, movingCache) { this.style = { width: this.data.width.toPx(), height: this.data.height.toPx(), }; if (isMoving) { - this.isMoving = true - this.movingCache = movingCache + this.isMoving = true; + this.movingCache = movingCache; } this._background(); for (const view of this.data.views) { @@ -31,10 +33,7 @@ export default class Painter { _background() { this.ctx.save(); - const { - width, - height, - } = this.style; + const { width, height } = this.style; const bg = this.data.background; this.ctx.translate(width / 2, height / 2); @@ -60,7 +59,7 @@ export default class Painter { _drawAbsolute(view) { if (!(view && view.type)) { // 过滤无效 view - return + return; } // 证明 css 为数组形式,需要合并 if (view.css && view.css.length) { @@ -85,20 +84,14 @@ export default class Painter { } } - _border({ - borderRadius = 0, - width, - height, - borderWidth = 0, - borderStyle = 'solid' - }) { + _border({ borderRadius = 0, width, height, borderWidth = 0, borderStyle = 'solid' }) { let r1 = 0, r2 = 0, r3 = 0, - r4 = 0 + r4 = 0; const minSize = Math.min(width, height); if (borderRadius) { - const border = borderRadius.split(/\s+/) + const border = borderRadius.split(/\s+/); if (border.length === 4) { r1 = Math.min(border[0].toPx(false, minSize), width / 2, height / 2); r2 = Math.min(border[1].toPx(false, minSize), width / 2, height / 2); @@ -111,30 +104,42 @@ export default class Painter { const lineWidth = borderWidth && borderWidth.toPx(false, minSize); this.ctx.lineWidth = lineWidth; if (borderStyle === 'dashed') { - this.ctx.setLineDash([lineWidth * 4 / 3, lineWidth * 4 / 3]); + this.ctx.setLineDash([(lineWidth * 4) / 3, (lineWidth * 4) / 3]); // this.ctx.lineDashOffset = 2 * lineWidth } else if (borderStyle === 'dotted') { this.ctx.setLineDash([lineWidth, lineWidth]); } - const notSolid = borderStyle !== 'solid' + const notSolid = borderStyle !== 'solid'; this.ctx.beginPath(); - notSolid && r1 === 0 && this.ctx.moveTo(-width / 2 - lineWidth, -height / 2 - lineWidth / 2) // 顶边虚线规避重叠规则 + notSolid && r1 === 0 && this.ctx.moveTo(-width / 2 - lineWidth, -height / 2 - lineWidth / 2); // 顶边虚线规避重叠规则 r1 !== 0 && this.ctx.arc(-width / 2 + r1, -height / 2 + r1, r1 + lineWidth / 2, 1 * Math.PI, 1.5 * Math.PI); //左上角圆弧 - this.ctx.lineTo(r2 === 0 ? notSolid ? width / 2 : width / 2 + lineWidth / 2 : width / 2 - r2, -height / 2 - lineWidth / 2); // 顶边线 + this.ctx.lineTo( + r2 === 0 ? (notSolid ? width / 2 : width / 2 + lineWidth / 2) : width / 2 - r2, + -height / 2 - lineWidth / 2, + ); // 顶边线 - notSolid && r2 === 0 && this.ctx.moveTo(width / 2 + lineWidth / 2, -height / 2 - lineWidth) // 右边虚线规避重叠规则 + notSolid && r2 === 0 && this.ctx.moveTo(width / 2 + lineWidth / 2, -height / 2 - lineWidth); // 右边虚线规避重叠规则 r2 !== 0 && this.ctx.arc(width / 2 - r2, -height / 2 + r2, r2 + lineWidth / 2, 1.5 * Math.PI, 2 * Math.PI); // 右上角圆弧 - this.ctx.lineTo(width / 2 + lineWidth / 2, r3 === 0 ? notSolid ? height / 2 : height / 2 + lineWidth / 2 : height / 2 - r3); // 右边线 + this.ctx.lineTo( + width / 2 + lineWidth / 2, + r3 === 0 ? (notSolid ? height / 2 : height / 2 + lineWidth / 2) : height / 2 - r3, + ); // 右边线 - notSolid && r3 === 0 && this.ctx.moveTo(width / 2 + lineWidth, height / 2 + lineWidth / 2) // 底边虚线规避重叠规则 + notSolid && r3 === 0 && this.ctx.moveTo(width / 2 + lineWidth, height / 2 + lineWidth / 2); // 底边虚线规避重叠规则 r3 !== 0 && this.ctx.arc(width / 2 - r3, height / 2 - r3, r3 + lineWidth / 2, 0, 0.5 * Math.PI); // 右下角圆弧 - this.ctx.lineTo(r4 === 0 ? notSolid ? -width / 2 : -width / 2 - lineWidth / 2 : -width / 2 + r4, height / 2 + lineWidth / 2); // 底边线 + this.ctx.lineTo( + r4 === 0 ? (notSolid ? -width / 2 : -width / 2 - lineWidth / 2) : -width / 2 + r4, + height / 2 + lineWidth / 2, + ); // 底边线 - notSolid && r4 === 0 && this.ctx.moveTo(-width / 2 - lineWidth / 2, height / 2 + lineWidth) // 左边虚线规避重叠规则 + notSolid && r4 === 0 && this.ctx.moveTo(-width / 2 - lineWidth / 2, height / 2 + lineWidth); // 左边虚线规避重叠规则 r4 !== 0 && this.ctx.arc(-width / 2 + r4, height / 2 - r4, r4 + lineWidth / 2, 0.5 * Math.PI, 1 * Math.PI); // 左下角圆弧 - this.ctx.lineTo(-width / 2 - lineWidth / 2, r1 === 0 ? notSolid ? -height / 2 : -height / 2 - lineWidth / 2 : -height / 2 + r1); // 左边线 - notSolid && r1 === 0 && this.ctx.moveTo(-width / 2 - lineWidth, -height / 2 - lineWidth / 2) // 顶边虚线规避重叠规则 + this.ctx.lineTo( + -width / 2 - lineWidth / 2, + r1 === 0 ? (notSolid ? -height / 2 : -height / 2 - lineWidth / 2) : -height / 2 + r1, + ); // 左边线 + notSolid && r1 === 0 && this.ctx.moveTo(-width / 2 - lineWidth, -height / 2 - lineWidth / 2); // 顶边虚线规避重叠规则 if (!notSolid) { this.ctx.closePath(); @@ -154,13 +159,11 @@ export default class Painter { borderRadius, width, height, - borderStyle - }) + borderStyle, + }); this.ctx.fill(); // 在 ios 的 6.6.6 版本上 clip 有 bug,禁掉此类型上的 clip,也就意味着,在此版本微信的 ios 设备下无法使用 border 属性 - if (!(getApp().systemInfo && - getApp().systemInfo.version <= '6.6.6' && - getApp().systemInfo.platform === 'ios')) { + if (!(getApp().systemInfo && getApp().systemInfo.version <= '6.6.6' && getApp().systemInfo.platform === 'ios')) { this.ctx.clip(); } this.ctx.globalAlpha = 1; @@ -174,25 +177,20 @@ export default class Painter { if (!view.css) { return; } - const { - borderRadius, - borderWidth, - borderColor, - borderStyle - } = view.css; + const { borderRadius, borderWidth, borderColor, borderStyle } = view.css; if (!borderWidth) { return; } this.ctx.save(); this._preProcess(view, true); - this.ctx.strokeStyle = (borderColor || 'black'); + this.ctx.strokeStyle = borderColor || 'black'; this._border({ borderRadius, width, height, borderWidth, - borderStyle - }) + borderStyle, + }); this.ctx.stroke(); this.ctx.restore(); } @@ -216,14 +214,18 @@ export default class Painter { if (!view.css.fontSize) { view.css.fontSize = '20rpx'; } - this.ctx.font = `${textStyle} ${fontWeight} ${view.css.fontSize.toPx()}px "${view.css.fontFamily || 'sans-serif'}"`; + this.ctx.font = `${textStyle} ${fontWeight} ${view.css.fontSize.toPx()}px "${ + view.css.fontFamily || 'sans-serif' + }"`; // 计算行数 let lines = 0; const linesArray = []; for (let i = 0; i < textArray.length; ++i) { const textLength = this.ctx.measureText(textArray[i]).width; const minWidth = view.css.fontSize.toPx() + paddings[1] + paddings[3]; - let partWidth = view.css.width ? view.css.width.toPx(false, this.style.width) - paddings[1] - paddings[3] : textLength; + let partWidth = view.css.width + ? view.css.width.toPx(false, this.style.width) - paddings[1] - paddings[3] + : textLength; if (partWidth < minWidth) { partWidth = minWidth; } @@ -260,14 +262,14 @@ export default class Painter { width = Math.round(view.sWidth / ratio); height = Math.round(view.sHeight / ratio); } else if (view.css.width === 'auto') { - height = view.css.height.toPx(false, this.style.height); - width = view.sWidth / view.sHeight * height; + height = view.css.height.toPx(false, this.style.height, this.viewRect); + width = (view.sWidth / view.sHeight) * height; } else if (view.css.height === 'auto') { - width = view.css.width.toPx(false, this.style.width); - height = view.sHeight / view.sWidth * width; + width = view.css.width.toPx(false, this.style.width, this.viewRect); + height = (view.sHeight / view.sWidth) * width; } else { - width = view.css.width.toPx(false, this.style.width); - height = view.css.height.toPx(false, this.style.height); + width = view.css.width.toPx(false, this.style.width, this.viewRect); + height = view.css.height.toPx(false, this.style.height, this.viewRect); } break; } @@ -276,26 +278,26 @@ export default class Painter { console.error('You should set width and height'); return; } - width = view.css.width.toPx(false, this.style.width); - height = view.css.height.toPx(false, this.style.height); + width = view.css.width.toPx(false, this.style.width, this.viewRect); + height = view.css.height.toPx(false, this.style.height, this.viewRect); break; } let x; if (view.css && view.css.right) { if (typeof view.css.right === 'string') { - x = this.style.width - view.css.right.toPx(true, this.style.width); + x = this.style.width - view.css.right.toPx(true, this.style.width, this.viewRect); } else { // 可以用数组方式,把文字长度计算进去 // [right, 文字id, 乘数(默认 1)] const rights = view.css.right; - x = this.style.width - rights[0].toPx(true, this.style.width) - this.globalWidth[rights[1]] * (rights[2] || 1); + x = this.style.width - rights[0].toPx(true, this.style.width) - this.viewRect[rights[1]].width * (rights[2] || 1); } } else if (view.css && view.css.left) { if (typeof view.css.left === 'string') { - x = view.css.left.toPx(true, this.style.width); + x = view.css.left.toPx(true, this.style.width, this.viewRect); } else { const lefts = view.css.left; - x = lefts[0].toPx(true, this.style.width) + this.globalWidth[lefts[1]] * (lefts[2] || 1); + x = lefts[0].toPx(true, this.style.width) + this.viewRect[lefts[1]].width * (lefts[2] || 1); } } else { x = 0; @@ -303,23 +305,23 @@ export default class Painter { //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); let y; if (view.css && view.css.bottom) { - y = this.style.height - height - view.css.bottom.toPx(true, this.style.height); + y = this.style.height - height - view.css.bottom.toPx(true, this.style.height, this.viewRect); } else { if (view.css && view.css.top) { if (typeof view.css.top === 'string') { - y = view.css.top.toPx(true, this.style.height); + y = view.css.top.toPx(true, this.style.height, this.viewRect); } else { const tops = view.css.top; - y = tops[0].toPx(true, this.style.height) + this.globalHeight[tops[1]] * (tops[2] || 1); + y = tops[0].toPx(true, this.style.height) + this.viewRect[tops[1]].height * (tops[2] || 1); } } else { - y = 0 + y = 0; } } const angle = view.css && view.css.rotate ? this._getAngle(view.css.rotate) : 0; // 当设置了 right 时,默认 align 用 right,反之用 left - const align = view.css && view.css.align ? view.css.align : (view.css && view.css.right ? 'right' : 'left'); + const align = view.css && view.css.align ? view.css.align : view.css && view.css.right ? 'right' : 'left'; const verticalAlign = view.css && view.css.verticalAlign ? view.css.verticalAlign : 'top'; // 记录绘制时的画布 let xa = 0; @@ -350,17 +352,17 @@ export default class Painter { // 记录该 view 的有效点击区域 // TODO ,旋转和裁剪的判断 // 记录在真实画布上的左侧 - let left = x + let left = x; if (align === 'center') { - left = x - width / 2 + left = x - width / 2; } else if (align === 'right') { - left = x - width + left = x - width; } var top = y; if (verticalAlign === 'center') { top = y - height / 2; } else if (verticalAlign === 'bottom') { - top = y - height + top = y - height; } if (view.rect) { view.rect.left = left; @@ -376,7 +378,7 @@ export default class Painter { right: left + width, bottom: top + height, x: view.css && view.css.right ? x - width : x, - y: y + y: y, }; } @@ -394,8 +396,14 @@ export default class Painter { } this._doShadow(view); if (view.id) { - this.globalWidth[view.id] = width; - this.globalHeight[view.id] = height; + this.viewRect[view.id] = { + width, + height, + left: x, + top: y, + right: x + width, + bottom: y + height, + }; } return { width: width, @@ -407,9 +415,7 @@ export default class Painter { } _doPaddings(view) { - const { - padding, - } = view.css ? view.css : {}; + const { padding } = view.css ? view.css : {}; let pd = [0, 0, 0, 0]; if (padding) { const pdg = padding.split(/\s+/); @@ -442,19 +448,14 @@ export default class Painter { // 画文字的背景图片 _doBackground(view) { this.ctx.save(); - const { - width: rawWidth, - height: rawHeight, - } = this._preProcess(view, true); + const { width: rawWidth, height: rawHeight } = this._preProcess(view, true); - const { - background, - } = view.css; + const { background } = view.css; let pd = this._doPaddings(view); const width = rawWidth + pd[1] + pd[3]; const height = rawHeight + pd[0] + pd[2]; - this._doClip(view.css.borderRadius, width, height, view.css.borderStyle) + this._doClip(view.css.borderRadius, width, height, view.css.borderStyle); if (GD.api.isGradient(background)) { GD.api.doGradient(background, width, height, this.ctx); } else { @@ -467,10 +468,7 @@ export default class Painter { _drawQRCode(view) { this.ctx.save(); - const { - width, - height, - } = this._preProcess(view); + const { width, height } = this._preProcess(view); QR.api.draw(view.content, this.ctx, -width / 2, -height / 2, width, height, view.css.background, view.css.color); this.ctx.restore(); this._doBorder(view, width, height); @@ -481,10 +479,7 @@ export default class Painter { return; } this.ctx.save(); - const { - width, - height, - } = this._preProcess(view); + const { width, height } = this._preProcess(view); // 获得缩放到图片大小级别的裁减框 let rWidth = view.sWidth; let rHeight = view.sHeight; @@ -514,7 +509,6 @@ export default class Painter { this._doBorder(view, width, height); } - callbackInfo = {} _fillAbsText(view) { if (!view.text) { return; @@ -524,23 +518,13 @@ export default class Painter { this._doBackground(view); } this.ctx.save(); - const { - width, - height, - extra, - } = this._preProcess(view, view.css.background && view.css.borderRadius); - this.ctx.fillStyle = (view.css.color || 'black'); + const { width, height, extra } = this._preProcess(view, view.css.background && view.css.borderRadius); + this.ctx.fillStyle = view.css.color || 'black'; if (this.isMoving && JSON.stringify(this.movingCache) !== JSON.stringify({})) { - this.globalWidth[view.id] = this.movingCache.globalWidth + this.viewRect[view.id] = this.movingCache.viewRect; this.ctx.textAlign = view.css.textAlign ? view.css.textAlign : 'left'; for (const i of this.movingCache.lineArray) { - const { - measuredWith, - text, - x, - y, - textDecoration - } = i + const { measuredWith, text, x, y, textDecoration } = i; if (view.css.textStyle === 'stroke') { this.ctx.strokeText(text, x, y, measuredWith); } else { @@ -558,24 +542,19 @@ export default class Painter { } } } else { - const { - lines, - lineHeight, - textArray, - linesArray, - } = extra; + const { lines, lineHeight, textArray, linesArray } = extra; // 如果设置了id,则保留 text 的长度 if (view.id) { let textWidth = 0; for (let i = 0; i < textArray.length; ++i) { - const _w = this.ctx.measureText(textArray[i]).width + const _w = this.ctx.measureText(textArray[i]).width; textWidth = _w > textWidth ? _w : textWidth; } - this.globalWidth[view.id] = width ? (textWidth < width ? textWidth : width) : textWidth; + this.viewRect[view.id].width = width ? (textWidth < width ? textWidth : width) : textWidth; if (!this.isMoving) { Object.assign(this.callbackInfo, { - globalWidth: this.globalWidth[view.id] - }) + viewRect: this.viewRect[view.id], + }); } } let lineIndex = 0; @@ -594,7 +573,10 @@ export default class Painter { let measuredWith = this.ctx.measureText(text).width; // 如果测量大小小于width一个字符的大小,则进行补齐,如果测量大小超出 width,则进行减除 // 如果已经到文本末尾,也不要进行该循环 - while ((start + alreadyCount <= textArray[j].length) && (width - measuredWith > view.css.fontSize.toPx() || measuredWith - width > view.css.fontSize.toPx())) { + while ( + start + alreadyCount <= textArray[j].length && + (width - measuredWith > view.css.fontSize.toPx() || measuredWith - width > view.css.fontSize.toPx()) + ) { if (measuredWith < width) { text = textArray[j].substr(start, ++alreadyCount); } else { @@ -607,7 +589,7 @@ export default class Painter { } measuredWith = this.ctx.measureText(text).width; } - start += text.length + start += text.length; // 如果是最后一行了,发现还有未绘制完的内容,则加... if (lineIndex === lines - 1 && (j < textArray.length - 1 || start < textArray[j].length)) { while (this.ctx.measureText(`${text}...`).width > width) { @@ -629,7 +611,7 @@ export default class Painter { lineX = x - measuredWith / 2; break; case 'right': - x = (width / 2); + x = width / 2; lineX = x - measuredWith; break; default: @@ -637,7 +619,9 @@ export default class Painter { lineX = x; break; } - const y = -(height / 2) + (lineIndex === 0 ? view.css.fontSize.toPx() : (view.css.fontSize.toPx() + lineIndex * lineHeight)); + const y = + -(height / 2) + + (lineIndex === 0 ? view.css.fontSize.toPx() : view.css.fontSize.toPx() + lineIndex * lineHeight); lineIndex++; if (view.css.textStyle === 'stroke') { this.ctx.strokeText(text, x, y, measuredWith); @@ -654,43 +638,47 @@ export default class Painter { this.ctx.lineTo(lineX + measuredWith, y); textDecoration = { moveTo: [lineX, y], - lineTo: [lineX + measuredWith, y] - } + lineTo: [lineX + measuredWith, y], + }; } if (/\boverline\b/.test(view.css.textDecoration)) { this.ctx.moveTo(lineX, y - fontSize); this.ctx.lineTo(lineX + measuredWith, y - fontSize); textDecoration = { moveTo: [lineX, y - fontSize], - lineTo: [lineX + measuredWith, y - fontSize] - } + lineTo: [lineX + measuredWith, y - fontSize], + }; } if (/\bline-through\b/.test(view.css.textDecoration)) { this.ctx.moveTo(lineX, y - fontSize / 3); this.ctx.lineTo(lineX + measuredWith, y - fontSize / 3); textDecoration = { moveTo: [lineX, y - fontSize / 3], - lineTo: [lineX + measuredWith, y - fontSize / 3] - } + lineTo: [lineX + measuredWith, y - fontSize / 3], + }; } this.ctx.closePath(); this.ctx.strokeStyle = view.css.color; this.ctx.stroke(); } if (!this.isMoving) { - this.callbackInfo.lineArray ? this.callbackInfo.lineArray.push({ - text, - x, - y, - measuredWith, - textDecoration - }) : this.callbackInfo.lineArray = [{ - text, - x, - y, - measuredWith, - textDecoration - }] + this.callbackInfo.lineArray + ? this.callbackInfo.lineArray.push({ + text, + x, + y, + measuredWith, + textDecoration, + }) + : (this.callbackInfo.lineArray = [ + { + text, + x, + y, + measuredWith, + textDecoration, + }, + ]); } } } @@ -701,27 +689,20 @@ export default class Painter { _drawAbsRect(view) { this.ctx.save(); - const { - width, - height, - } = this._preProcess(view); + const { width, height } = this._preProcess(view); if (GD.api.isGradient(view.css.color)) { GD.api.doGradient(view.css.color, width, height, this.ctx); } else { this.ctx.fillStyle = view.css.color; } - const { - borderRadius, - borderStyle, - borderWidth - } = view.css + const { borderRadius, borderStyle, borderWidth } = view.css; this._border({ borderRadius, width, height, borderWidth, - borderStyle - }) + borderStyle, + }); this.ctx.fill(); this.ctx.restore(); this._doBorder(view, width, height); @@ -735,7 +716,7 @@ export default class Painter { } const box = view.css.shadow.replace(/,\s+/g, ',').split(/\s+/); if (box.length > 4) { - console.error('shadow don\'t spread option'); + console.error("shadow don't spread option"); return; } this.ctx.shadowOffsetX = parseInt(box[0], 10); @@ -745,6 +726,6 @@ export default class Painter { } _getAngle(angle) { - return Number(angle) * Math.PI / 180; + return (Number(angle) * Math.PI) / 180; } } diff --git a/components/painter/painter.js b/components/painter/painter.js index 6bded60..4146bf4 100644 --- a/components/painter/painter.js +++ b/components/painter/painter.js @@ -3,6 +3,7 @@ import Downloader from './lib/downloader'; import WxCanvas from './lib/wx-canvas'; const util = require('./lib/util'); +const calc = require('./lib/calc'); const downloader = new Downloader(); @@ -851,36 +852,47 @@ Component({ function setStringPrototype(screenK, scale) { /* eslint-disable no-extend-native */ /** - * 是否支持负数 - * @param {Boolean} minus 是否支持负数 + * string 到对应的 px * @param {Number} baseSize 当设置了 % 号时,设置的基准值 + * @param {Object} relativeViewRect 所相对的 view 的信息 */ - String.prototype.toPx = function toPx(minus, baseSize) { + String.prototype.toPx = function toPx(_, baseSize, relativeViewRect) { if (this === '0') { - return 0 - } - let reg; - if (minus) { - reg = /^-?[0-9]+([.]{1}[0-9]+){0,1}(rpx|px|%)$/g; - } else { - reg = /^[0-9]+([.]{1}[0-9]+){0,1}(rpx|px|%)$/g; - } - const results = reg.exec(this); - if (!this || !results) { - console.error(`The size: ${this} is illegal`); return 0; } - const unit = results[2]; - const value = parseFloat(this); - - let res = 0; - if (unit === 'rpx') { - res = Math.round(value * (screenK || 0.5) * (scale || 1)); - } else if (unit === 'px') { - res = Math.round(value * (scale || 1)); - } else if (unit === '%') { - res = Math.round(value * baseSize / 100); + const REG = /-?[0-9]+([.]{1}[0-9]+){0,1}(rpx|px|%)/g; + + const parsePx = origin => { + const results = REG.exec(origin); + if (!origin || !results) { + console.error(`The size: ${origin} is illegal`); + return 0; + } + const unit = results[2]; + const value = parseFloat(origin); + + let res = 0; + if (unit === 'rpx') { + res = Math.round(value * (screenK || 0.5) * (scale || 1)); + } else if (unit === 'px') { + res = Math.round(value * (scale || 1)); + } else if (unit === '%') { + res = Math.round((value * baseSize) / 100); + } + return res; + }; + const formula = /^calc\((.+)\)$/.exec(this) + if (formula && formula[1]) { + // 进行 calc 计算 + const afterOne = formula[1].replace(/([^\s]+)\.(left|right|bottom|top|width|height)/, (word) => { + const [id, attr] = word.split('.'); + return relativeViewRect[id][attr] + } + ); + const afterTwo = afterOne.replace(REG, parsePx); + return calc(afterTwo); + } else { + return parsePx(this); } - return res; }; } \ No newline at end of file diff --git a/pages/example/example.js b/pages/example/example.js index bd1c5e4..d9960d3 100644 --- a/pages/example/example.js +++ b/pages/example/example.js @@ -1,4 +1,4 @@ -import Card from '../../palette/card'; +import Card from '../../palette/relative'; // src/pages/xml2can/xml2can.js Page({ diff --git a/palette/relative.js b/palette/relative.js new file mode 100644 index 0000000..81c1d25 --- /dev/null +++ b/palette/relative.js @@ -0,0 +1,55 @@ +export default class LastMayday { + palette() { + return ({ + width: '654rpx', + height: '1000rpx', + background: '#eee', + views: [ + { + id: 'one', + type: 'rect', + css: { + width: '200rpx', + left: '50%', + align: 'center', + top: '30rpx', + height: '100rpx', + }, + }, + { + id: 'two', + type: 'rect', + css: { + width: '200rpx', + left: 'calc(one.left + 100rpx)', + align: 'center', + top: 'calc(one.bottom + 10rpx)', + height: '100rpx', + }, + }, + { + id: 'three', + type: 'rect', + css: { + width: '200rpx', + left: 'calc(two.left + 100rpx)', + align: 'center', + top: 'calc(two.bottom + 3 * 10rpx)', + height: '100rpx', + }, + }, + { + id: 'four', + type: 'rect', + css: { + width: 'calc(2 * one.width)', + left: 'calc(one.left)', + align: 'center', + top: 'calc(three.bottom + 10rpx)', + height: '100rpx', + }, + }, + ], + }); + } +} From 09b3ebd63ef1a2bd08e2114b31f85e1ce611603a Mon Sep 17 00:00:00 2001 From: CPPAlien Date: Mon, 21 Jun 2021 12:26:51 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E6=AD=A3=E5=88=99=E8=A1=A8=E8=BE=BE?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + README.md | 4 +--- components/painter/painter.js | 6 +++--- palette/relative.js | 4 +--- 4 files changed, 6 insertions(+), 9 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..496ee2c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index 308d92a..f08456c 100644 --- a/README.md +++ b/README.md @@ -441,8 +441,7 @@ width: 'calc(myTextId.width * 2)' type: 'rect', css: { width: '200rpx', - left: '50%', - align: 'center', + left: 'calc(50% - 100rpx)', top: '30rpx', height: '100rpx', }, @@ -453,7 +452,6 @@ width: 'calc(myTextId.width * 2)' css: { width: '200rpx', left: 'calc(one.left + 100rpx)', - align: 'center', top: 'calc(one.bottom + 10rpx)', height: '100rpx', }, diff --git a/components/painter/painter.js b/components/painter/painter.js index 4146bf4..0dac8cb 100644 --- a/components/painter/painter.js +++ b/components/painter/painter.js @@ -860,10 +860,10 @@ function setStringPrototype(screenK, scale) { if (this === '0') { return 0; } - const REG = /-?[0-9]+([.]{1}[0-9]+){0,1}(rpx|px|%)/g; + const REG = /-?[0-9]+(\.[0-9]+)?(rpx|px|%)/; const parsePx = origin => { - const results = REG.exec(origin); + const results = new RegExp(REG).exec(origin); if (!origin || !results) { console.error(`The size: ${origin} is illegal`); return 0; @@ -889,7 +889,7 @@ function setStringPrototype(screenK, scale) { return relativeViewRect[id][attr] } ); - const afterTwo = afterOne.replace(REG, parsePx); + const afterTwo = afterOne.replace(new RegExp(REG, 'g'), parsePx); return calc(afterTwo); } else { return parsePx(this); diff --git a/palette/relative.js b/palette/relative.js index 81c1d25..311d8e2 100644 --- a/palette/relative.js +++ b/palette/relative.js @@ -10,8 +10,7 @@ export default class LastMayday { type: 'rect', css: { width: '200rpx', - left: '50%', - align: 'center', + left: 'calc(50% - 100rpx)', top: '30rpx', height: '100rpx', }, @@ -22,7 +21,6 @@ export default class LastMayday { css: { width: '200rpx', left: 'calc(one.left + 100rpx)', - align: 'center', top: 'calc(one.bottom + 10rpx)', height: '100rpx', },