mp_share
This commit is contained in:
parent
8f4d203f71
commit
16d4cc09da
BIN
fonts/muyao.ttf
Normal file
BIN
fonts/muyao.ttf
Normal file
Binary file not shown.
BIN
fonts/shoushuti.ttf
Normal file
BIN
fonts/shoushuti.ttf
Normal file
Binary file not shown.
BIN
fonts/siyuan.otf
Normal file
BIN
fonts/siyuan.otf
Normal file
Binary file not shown.
BIN
fonts/yangrendong.ttf
Normal file
BIN
fonts/yangrendong.ttf
Normal file
Binary file not shown.
@ -20,6 +20,7 @@
|
|||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"bson": "^4.0.2",
|
"bson": "^4.0.2",
|
||||||
"bunyan": "^1.8.12",
|
"bunyan": "^1.8.12",
|
||||||
|
"canvas": "^2.5.0",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"connect-mongo": "^2.0.3",
|
"connect-mongo": "^2.0.3",
|
||||||
"cookie-parser": "^1.4.4",
|
"cookie-parser": "^1.4.4",
|
||||||
@ -29,6 +30,7 @@
|
|||||||
"express-session": "^1.16.1",
|
"express-session": "^1.16.1",
|
||||||
"express-validator": "^5.3.1",
|
"express-validator": "^5.3.1",
|
||||||
"file-stream-rotator": "^0.4.1",
|
"file-stream-rotator": "^0.4.1",
|
||||||
|
"form-data": "^2.3.3",
|
||||||
"fs-extra": "^8.0.0",
|
"fs-extra": "^8.0.0",
|
||||||
"glob": "^7.1.4",
|
"glob": "^7.1.4",
|
||||||
"helmet": "^3.18.0",
|
"helmet": "^3.18.0",
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import gamesRouter from './games'
|
import gamesRouter from './games';
|
||||||
import settingsRouter from './settings'
|
import settingsRouter from './settings';
|
||||||
import platformsRouter from './platforms'
|
import platformsRouter from './platforms';
|
||||||
import shareRouter from './share'
|
import shareRouter from './share';
|
||||||
|
import mpShareRouter from './mp_share';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
|
|
||||||
router.use('/settings', settingsRouter);
|
router.use('/settings', settingsRouter);
|
||||||
router.use('/platforms', platformsRouter);
|
router.use('/platforms', platformsRouter);
|
||||||
router.use('/share', shareRouter);
|
router.use('/share', shareRouter);
|
||||||
|
router.use('/mp_share', mpShareRouter);
|
||||||
router.use('/', gamesRouter);
|
router.use('/', gamesRouter);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
130
src/controllers/games/mp_share.js
Normal file
130
src/controllers/games/mp_share.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { Router } from 'express';
|
||||||
|
import FormData from 'form-data';
|
||||||
|
import request from 'request';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import painter from '../../utils/painter';
|
||||||
|
import config from '../../../config/config';
|
||||||
|
|
||||||
|
const router = new Router();
|
||||||
|
|
||||||
|
router.get('/test', async (req, res, next) => {
|
||||||
|
var dataBuffer = new Buffer(base64, 'base64');
|
||||||
|
// var imgBuffer = streamifier.createReadStream(dataBuffer);
|
||||||
|
var imgpath = path.join(__dirname, '../../../temp/flower.jpg');
|
||||||
|
console.log(path.join(__dirname, '../../../temp/logo.png'));
|
||||||
|
var formData = {
|
||||||
|
'image-file': fs.createReadStream(imgpath),
|
||||||
|
// 'image-file': dataBuffer,
|
||||||
|
sub_path: '/mp-share/',
|
||||||
|
file_type: 'mp_share'
|
||||||
|
};
|
||||||
|
request.post(
|
||||||
|
{
|
||||||
|
url: 'http://localhost:2333/api/common/upload',
|
||||||
|
formData: formData,
|
||||||
|
headers: {
|
||||||
|
authorization:
|
||||||
|
'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Inl1bGl4aW5nIiwiaWF0IjoxNTYwOTIyOTEyLCJleHAiOjE1NjEwMDkzMTJ9.EAVRtbGxBIp4nDaWUAoO0IqGb6OSqDG5a_GXwbfjkBI'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(err, res1, body) {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
res.send(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.unlink(imgpath, function(error) {
|
||||||
|
if (error) {
|
||||||
|
console.log(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
console.log('删除文件成功');
|
||||||
|
});
|
||||||
|
|
||||||
|
res.send(body);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/test1', async (req, res, next) => {
|
||||||
|
const opt = {
|
||||||
|
baseInfo: {
|
||||||
|
width: '250px',
|
||||||
|
height: '736px',
|
||||||
|
fonts: [
|
||||||
|
{
|
||||||
|
name: 'shoushuti',
|
||||||
|
path: 'fonts/shoushuti.ttf'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
views: [
|
||||||
|
{
|
||||||
|
type: 'image',
|
||||||
|
url:
|
||||||
|
'https://client-1256832210.cos.ap-beijing.myqcloud.com/mp-share/5d09e26a62bb32868763dba3.jpeg',
|
||||||
|
style: {
|
||||||
|
top: '50px',
|
||||||
|
left: '20px',
|
||||||
|
width: '210px', //可选
|
||||||
|
height: '210px', // 可选
|
||||||
|
'border-radius': '105px', // 可选
|
||||||
|
border: '1px solid #fff' // 可选
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text:
|
||||||
|
'人间四月芳菲尽,山寺桃花始盛开。\n长恨春归无觅处,不知转入此中来。\n——白居易·大林寺桃花',
|
||||||
|
style: {
|
||||||
|
left: '165px',
|
||||||
|
top: '300px',
|
||||||
|
color: '#fae',
|
||||||
|
'max-width': '693px',
|
||||||
|
'font-size': '24px',
|
||||||
|
'font-family': 'shoushuti',
|
||||||
|
'line-height': '50px',
|
||||||
|
'writing-mode': 'vertical-rl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const imgTempName = await painter(opt);
|
||||||
|
const imgTempPath = path.join(__dirname, '../../../temp/' + imgTempName);
|
||||||
|
// 生成后上传、删除
|
||||||
|
const formData = {
|
||||||
|
'image-file': fs.createReadStream(imgTempPath),
|
||||||
|
sub_path: '/mp-share/',
|
||||||
|
file_type: 'mp_share'
|
||||||
|
};
|
||||||
|
request.post(
|
||||||
|
{
|
||||||
|
url: config.host + '/api/common/upload',
|
||||||
|
formData: formData,
|
||||||
|
headers: {
|
||||||
|
authorization: `${req.headers.authorization}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(uploadErr, uploadRes, uploadBody) {
|
||||||
|
fs.unlink(imgTempPath, function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (uploadErr) {
|
||||||
|
next(uploadErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.send(uploadBody);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
@ -34,7 +34,7 @@ const GameShareImage = new mongoose.Schema({
|
|||||||
ad_count: {type: Number, default: 0},
|
ad_count: {type: Number, default: 0},
|
||||||
// 广告间隔时间
|
// 广告间隔时间
|
||||||
ad_cd: {type: Number, default: 0},
|
ad_cd: {type: Number, default: 0},
|
||||||
// 0: 分享图, 1: 广告
|
// 0: 分享图优先, 1: 广告优先, 2: 只分享, 3: 只广告
|
||||||
type: {type: Number, default: 0},
|
type: {type: Number, default: 0},
|
||||||
// 备注
|
// 备注
|
||||||
comment: {type: String},
|
comment: {type: String},
|
||||||
|
300
src/utils/painter/index.js
Normal file
300
src/utils/painter/index.js
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
const { registerFont, createCanvas, loadImage } = require('canvas');
|
||||||
|
|
||||||
|
const drawRoundRect = require('./round_rect');
|
||||||
|
const wrapText = require('./wrap_text');
|
||||||
|
const fillTextVertical = require('./vertical_text');
|
||||||
|
|
||||||
|
const drawShadow = require('./shadow');
|
||||||
|
const transformBase64 = require('./transfer_base64');
|
||||||
|
|
||||||
|
const config = require('../../../config/config');
|
||||||
|
|
||||||
|
module.exports = async function paint(opt) {
|
||||||
|
// 获取画布基本信息
|
||||||
|
const baseInfo = opt.baseInfo;
|
||||||
|
const views = opt.views;
|
||||||
|
const canvasW = parseInt(baseInfo.width);
|
||||||
|
const canvasH = parseInt(baseInfo.height);
|
||||||
|
|
||||||
|
//引入字体
|
||||||
|
const fonts = baseInfo.fonts;
|
||||||
|
if (fonts.length > 0) {
|
||||||
|
for (let i = 0; i < fonts.length; i++) {
|
||||||
|
const fontName = fonts[i].name;
|
||||||
|
registerFont(fonts[i].path, { family: `${fontName}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取图片
|
||||||
|
const imgs = [];
|
||||||
|
views.map((view, index) => {
|
||||||
|
if (view.type === 'image' && view.url) {
|
||||||
|
imgs.push({
|
||||||
|
url: view.url,
|
||||||
|
index: index
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const imgsInfo = await _absorbImgs(imgs);
|
||||||
|
|
||||||
|
// 创建画布与画笔
|
||||||
|
const canvas = createCanvas(canvasW, canvasH);
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// 画笔功能扩展
|
||||||
|
ctx.wrapText = wrapText;
|
||||||
|
ctx.fillTextVertical = fillTextVertical;
|
||||||
|
ctx.drawRoundRect = drawRoundRect;
|
||||||
|
|
||||||
|
// 绘制背景
|
||||||
|
_drawBg(ctx, baseInfo);
|
||||||
|
|
||||||
|
//绘制元素
|
||||||
|
|
||||||
|
for (let i = 0; i < views.length; i++) {
|
||||||
|
const view = views[i];
|
||||||
|
_drawView(ctx, view, i, imgsInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成图片
|
||||||
|
const tempName = `${new Date().getTime()}.png`;
|
||||||
|
const tempPath = config.root + `/temp/${tempName}`;
|
||||||
|
transformBase64(canvas.toDataURL(), tempPath);
|
||||||
|
|
||||||
|
return tempName;
|
||||||
|
};
|
||||||
|
|
||||||
|
function _drawBg(ctx, info) {
|
||||||
|
ctx.save();
|
||||||
|
const w = parseInt(info.width);
|
||||||
|
const h = parseInt(info.height);
|
||||||
|
const bg = info['back-ground'];
|
||||||
|
let bdr = info['border-radius'] || 0;
|
||||||
|
|
||||||
|
// 圆角剪切
|
||||||
|
if (bdr && bdr.endsWith('px')) {
|
||||||
|
bdr = parseInt(bdr);
|
||||||
|
} else if (bdr && bdr.endsWith('%')) {
|
||||||
|
bdr = (w * parseInt(bdr)) / 100;
|
||||||
|
}
|
||||||
|
ctx.drawRoundRect(0, 0, w, h, bdr, false, false);
|
||||||
|
ctx.clip();
|
||||||
|
|
||||||
|
if (!bg) {
|
||||||
|
// 默认背景
|
||||||
|
ctx.fillStyle = '#fff';
|
||||||
|
ctx.fillRect(0, 0, w, h);
|
||||||
|
} else if (
|
||||||
|
bg.startsWith('#') ||
|
||||||
|
bg.startsWith('rgba') ||
|
||||||
|
bg.startsWith('rgb') ||
|
||||||
|
bg.toLowerCase() === 'transparent'
|
||||||
|
) {
|
||||||
|
// 纯色填充
|
||||||
|
ctx.fillStyle = bg;
|
||||||
|
ctx.fillRect(0, 0, w, h);
|
||||||
|
}
|
||||||
|
// TODO: 渐变填充
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _drawView(ctx, view, index, imgsInfo) {
|
||||||
|
switch (view.type) {
|
||||||
|
case 'image':
|
||||||
|
_drawImg(ctx, view, index, imgsInfo);
|
||||||
|
break;
|
||||||
|
case 'text':
|
||||||
|
_drawText(ctx, view);
|
||||||
|
break;
|
||||||
|
case 'rect':
|
||||||
|
_drawRect(ctx, view);
|
||||||
|
break;
|
||||||
|
// case 'qrcode':
|
||||||
|
// _drawQRCode(ctx, view);
|
||||||
|
// break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(100, 75, 50, 0, 2 * Math.PI);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _drawImg(ctx, view, index, imgsInfo) {
|
||||||
|
const img = imgsInfo[index];
|
||||||
|
const style = view.style;
|
||||||
|
const w = parseInt(style.width);
|
||||||
|
const h = parseInt(style.height);
|
||||||
|
let x = parseInt(style.left);
|
||||||
|
let y = parseInt(style.top);
|
||||||
|
const hasBd = style['border'] ? true : false;
|
||||||
|
let bdr = style['border-radius'] || 0;
|
||||||
|
|
||||||
|
if (!img) return;
|
||||||
|
ctx.save();
|
||||||
|
|
||||||
|
// 圆角剪切
|
||||||
|
if (bdr && bdr.endsWith('px')) {
|
||||||
|
bdr = parseInt(bdr);
|
||||||
|
} else if (bdr && bdr.endsWith('%')) {
|
||||||
|
bdr = (w * parseInt(bdr)) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否旋转
|
||||||
|
if (style['transform']) {
|
||||||
|
ctx.translate(x + w / 2, y + h / 2);
|
||||||
|
const rotateDeg = _rotateDeg(style['transform']);
|
||||||
|
x = -w / 2;
|
||||||
|
y = -h / 2;
|
||||||
|
ctx.rotate(rotateDeg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasBd) {
|
||||||
|
const borderInfo = style['border'].split(' ');
|
||||||
|
ctx.lineWidth = parseInt(borderInfo[0]);
|
||||||
|
ctx.strokeStyle = borderInfo[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.drawRoundRect(x, y, w, h, bdr, false, hasBd);
|
||||||
|
drawShadow(ctx, style);
|
||||||
|
ctx.clip();
|
||||||
|
|
||||||
|
// 绘画区域比例
|
||||||
|
const cp = w / h;
|
||||||
|
// 原图比例
|
||||||
|
const op = img.width / img.height;
|
||||||
|
if (cp >= op) {
|
||||||
|
const r = img.width / w;
|
||||||
|
const nW = img.width / r;
|
||||||
|
const nH = img.height / r;
|
||||||
|
const nY = y - (nH - h) / 2;
|
||||||
|
ctx.drawImage(img, x, nY, nW, nH);
|
||||||
|
} else {
|
||||||
|
const r = img.height / h;
|
||||||
|
const nW = img.width / r;
|
||||||
|
const nH = img.height / r;
|
||||||
|
const nX = x - (nW - w) / 2;
|
||||||
|
ctx.drawImage(img, nX, y, nW, nH);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _drawText(ctx, view) {
|
||||||
|
const style = view.style;
|
||||||
|
let x = parseInt(style.left);
|
||||||
|
let y = parseInt(style.top);
|
||||||
|
const maxWidth = parseInt(style['max-width']);
|
||||||
|
const lineHeight = parseInt(style['line-height']);
|
||||||
|
const fontWeight = parseInt(style['font-weight']) || 500;
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
// 字体颜色
|
||||||
|
ctx.fillStyle = style.color || '#000';
|
||||||
|
|
||||||
|
// 字体对齐方式
|
||||||
|
ctx.textAlign = style['text-align'] || 'left';
|
||||||
|
|
||||||
|
// 字体大小和fontface
|
||||||
|
ctx.font = style['font-family']
|
||||||
|
? `${fontWeight} ${style['font-size']} "${style['font-family']}"`
|
||||||
|
: `${fontWeight} ${style['font-size']} "Sans"`;
|
||||||
|
// 文字对齐方式
|
||||||
|
if (style['writing-mode'] === 'vertical-rl') {
|
||||||
|
// 竖排文字
|
||||||
|
ctx.fillTextVertical(
|
||||||
|
view.text,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
parseInt(style['max-height']),
|
||||||
|
parseInt(style['line-height']),
|
||||||
|
view
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 横排文字
|
||||||
|
|
||||||
|
// 是否旋转
|
||||||
|
if (style['transform']) {
|
||||||
|
ctx.translate(x + maxWidth / 2, y + lineHeight / 2);
|
||||||
|
const rotateDeg = _rotateDeg(style['transform']);
|
||||||
|
x = -maxWidth / 2;
|
||||||
|
y = -lineHeight / 2;
|
||||||
|
ctx.rotate(rotateDeg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.wrapText(
|
||||||
|
view.text,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
parseInt(style['max-width']),
|
||||||
|
parseInt(style['line-height']),
|
||||||
|
view
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _drawRect(ctx, view) {
|
||||||
|
const style = view.style;
|
||||||
|
const w = parseInt(style.width);
|
||||||
|
const h = parseInt(style.height);
|
||||||
|
let x = parseInt(style.left);
|
||||||
|
let y = parseInt(style.top);
|
||||||
|
const hasBd = style['border'] ? true : false;
|
||||||
|
let bdr = style['border-radius'] || 0;
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
|
||||||
|
// 圆角剪切
|
||||||
|
if (bdr && bdr.endsWith('px')) {
|
||||||
|
bdr = parseInt(bdr);
|
||||||
|
} else if (bdr && bdr.endsWith('%')) {
|
||||||
|
bdr = (w * parseInt(bdr)) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否旋转
|
||||||
|
if (style['transform']) {
|
||||||
|
ctx.translate(x + w / 2, y + h / 2);
|
||||||
|
const rotateDeg = _rotateDeg(style['transform']);
|
||||||
|
x = -w / 2;
|
||||||
|
y = -h / 2;
|
||||||
|
ctx.rotate(rotateDeg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasBd) {
|
||||||
|
const borderInfo = style['border'].split(' ');
|
||||||
|
ctx.lineWidth = parseInt(borderInfo[0]);
|
||||||
|
ctx.strokeStyle = borderInfo[2];
|
||||||
|
}
|
||||||
|
ctx.fillStyle = style['back-ground'];
|
||||||
|
|
||||||
|
ctx.drawRoundRect(x, y, w, h, bdr, true, hasBd);
|
||||||
|
drawShadow(ctx, style);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.clip();
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _absorbImgs(imgs) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
const imgsNum = imgs.length;
|
||||||
|
const result = {};
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < imgsNum; i++) {
|
||||||
|
const imgInfo = imgs[i];
|
||||||
|
const imgData = await loadImage(imgInfo.url);
|
||||||
|
result[imgInfo.index] = imgData;
|
||||||
|
}
|
||||||
|
resolve(result);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取旋转角度
|
||||||
|
function _rotateDeg(str) {
|
||||||
|
const reg = /^rotate\((-?\d*)deg\)$/;
|
||||||
|
const result = reg.exec(str)[1];
|
||||||
|
return (parseInt(result) * Math.PI) / 180 || 0;
|
||||||
|
}
|
13
src/utils/painter/round_path.js
Normal file
13
src/utils/painter/round_path.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module.exports = function roundPath(x, y, w, h, r) {
|
||||||
|
var min_size = Math.min(w, h);
|
||||||
|
if (r > min_size / 2) r = min_size / 2;
|
||||||
|
// 开始绘制
|
||||||
|
this.beginPath();
|
||||||
|
this.moveTo(x + r, y);
|
||||||
|
this.arcTo(x + w, y, x + w, y + h, r);
|
||||||
|
this.arcTo(x + w, y + h, x, y + h, r);
|
||||||
|
this.arcTo(x, y + h, x, y, r);
|
||||||
|
this.arcTo(x, y, x + w, y, r);
|
||||||
|
this.closePath();
|
||||||
|
return this;
|
||||||
|
};
|
16
src/utils/painter/round_rect.js
Normal file
16
src/utils/painter/round_rect.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module.exports = function drawRoundRect(x, y, width, height, r, fill, stroke) {
|
||||||
|
this.save();
|
||||||
|
this.beginPath(); // draw top and top right corner
|
||||||
|
this.moveTo(x + r, y);
|
||||||
|
this.arcTo(x + width, y, x + width, y + r, r); // draw right side and bottom right corner
|
||||||
|
this.arcTo(x + width, y + height, x + width - r, y + height, r); // draw bottom and bottom left corner
|
||||||
|
this.arcTo(x, y + height, x, y + height - r, r); // draw left and top left corner
|
||||||
|
this.arcTo(x, y, x + r, y, r);
|
||||||
|
if (fill) {
|
||||||
|
this.fill();
|
||||||
|
}
|
||||||
|
if (stroke) {
|
||||||
|
this.stroke();
|
||||||
|
}
|
||||||
|
this.restore();
|
||||||
|
};
|
22
src/utils/painter/shadow.js
Normal file
22
src/utils/painter/shadow.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// shadow 支持 (x, y, blur, color), 不支持 spread
|
||||||
|
// shadow:0px 0px 10px rgba(0,0,0,0.1);
|
||||||
|
module.exports = function drawShadow(ctx, style) {
|
||||||
|
if (!style || (!style['box-shadow'] && !style['text-shadow'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let box;
|
||||||
|
if (style['text-shadow']) {
|
||||||
|
box = style['text-shadow'].replace(/,\s+/g, ',').split(' ');
|
||||||
|
} else {
|
||||||
|
box = style['box-shadow'].replace(/,\s+/g, ',').split(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (box.length > 4) {
|
||||||
|
console.error("shadow don't spread option");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.shadowOffsetX = parseInt(box[0], 10);
|
||||||
|
ctx.shadowOffsetY = parseInt(box[1], 10);
|
||||||
|
ctx.shadowBlur = parseInt(box[2], 10);
|
||||||
|
ctx.shadowColor = box[3];
|
||||||
|
};
|
7
src/utils/painter/transfer_base64.js
Normal file
7
src/utils/painter/transfer_base64.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
module.exports = function transformBase64(data, path) {
|
||||||
|
const base64 = data.replace(/^data:image\/\w+;base64,/, '');
|
||||||
|
const dataBuffer = new Buffer(base64, 'base64');
|
||||||
|
fs.writeFileSync(path, dataBuffer);
|
||||||
|
};
|
106
src/utils/painter/vertical_text.js
Normal file
106
src/utils/painter/vertical_text.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* @author zhangxinxu(.com)
|
||||||
|
* @licence MIT
|
||||||
|
* @description http://www.zhangxinxu.com/wordpress/?p=7362
|
||||||
|
*/
|
||||||
|
|
||||||
|
var drawShadow = require('./shadow');
|
||||||
|
|
||||||
|
module.exports = function fillTextVertical(
|
||||||
|
text,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
maxHeight,
|
||||||
|
lineHeight,
|
||||||
|
view
|
||||||
|
) {
|
||||||
|
var context = this;
|
||||||
|
var canvas = context.canvas;
|
||||||
|
|
||||||
|
var arrText = text.split('');
|
||||||
|
var arrWidth = arrText.map(function(letter) {
|
||||||
|
return context.measureText(letter).width;
|
||||||
|
});
|
||||||
|
|
||||||
|
var align = context.textAlign;
|
||||||
|
var baseline = context.textBaseline;
|
||||||
|
|
||||||
|
var style = view.style;
|
||||||
|
var hasStroke = style['-webkit-text-stroke'] ? true : false;
|
||||||
|
var strokeInfo = [];
|
||||||
|
|
||||||
|
var fontSize = parseInt(style['font-size']);
|
||||||
|
|
||||||
|
if (hasStroke) {
|
||||||
|
strokeInfo = style['-webkit-text-stroke'].split(' ');
|
||||||
|
context.lineWidth = parseInt(strokeInfo[0]);
|
||||||
|
context.strokeStyle = strokeInfo[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (align == 'left') {
|
||||||
|
// x = x + Math.max.apply(null, arrWidth) / 2;
|
||||||
|
// } else if (align == 'right') {
|
||||||
|
// x = x - Math.max.apply(null, arrWidth) / 2;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (
|
||||||
|
baseline == 'bottom' ||
|
||||||
|
baseline == 'alphabetic' ||
|
||||||
|
baseline == 'ideographic'
|
||||||
|
) {
|
||||||
|
y = y - arrWidth[0] / 2;
|
||||||
|
} else if (baseline == 'top' || baseline == 'hanging') {
|
||||||
|
y = y + arrWidth[0] / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.textAlign = 'left';
|
||||||
|
context.textBaseline = 'top';
|
||||||
|
|
||||||
|
// 开始逐字绘制
|
||||||
|
var curHeihgt = 0;
|
||||||
|
var initialY = y;
|
||||||
|
|
||||||
|
arrText.forEach(function(letter, index) {
|
||||||
|
// 确定下一个字符的纵坐标位置
|
||||||
|
|
||||||
|
var letterWidth = arrWidth[index];
|
||||||
|
curHeihgt += letterWidth;
|
||||||
|
// 换行
|
||||||
|
if (curHeihgt > maxHeight || letter === '\n') {
|
||||||
|
x -= lineHeight;
|
||||||
|
y = initialY;
|
||||||
|
curHeihgt = 0;
|
||||||
|
}
|
||||||
|
// 是否需要旋转判断
|
||||||
|
var code = letter.charCodeAt(0);
|
||||||
|
if (code <= 256 || code == 8212) {
|
||||||
|
context.translate(x + fontSize / 2, y + fontSize / 2);
|
||||||
|
// 英文字符,旋转90°
|
||||||
|
context.rotate((90 * Math.PI) / 180);
|
||||||
|
context.translate(-x - fontSize / 2, -y - fontSize / 2);
|
||||||
|
} else if (index > 0 && text.charCodeAt(index - 1) < 256) {
|
||||||
|
// y修正
|
||||||
|
y = y + arrWidth[index - 1] / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制阴影
|
||||||
|
if (style['text-shadow']) {
|
||||||
|
drawShadow(context, view.style);
|
||||||
|
}
|
||||||
|
// 描边
|
||||||
|
if (hasStroke) {
|
||||||
|
context.strokeText(letter, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.fillText(letter, x, y);
|
||||||
|
|
||||||
|
// 旋转坐标系还原成初始态
|
||||||
|
context.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
// 确定下一个字符的纵坐标位置
|
||||||
|
var letterWidth = arrWidth[index];
|
||||||
|
y = y + letterWidth;
|
||||||
|
});
|
||||||
|
// 水平垂直对齐方式还原
|
||||||
|
context.textAlign = align;
|
||||||
|
context.textBaseline = baseline;
|
||||||
|
};
|
71
src/utils/painter/wrap_text.js
Normal file
71
src/utils/painter/wrap_text.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* @author zhangxinxu(.com)
|
||||||
|
* @licence MIT
|
||||||
|
* @description http://www.zhangxinxu.com/wordpress/?p=7362
|
||||||
|
*/
|
||||||
|
|
||||||
|
var drawShadow = require('./shadow');
|
||||||
|
|
||||||
|
module.exports = function wrapText(text, x, y, maxWidth, lineHeight, view) {
|
||||||
|
if (
|
||||||
|
typeof text != 'string' ||
|
||||||
|
typeof x != 'number' ||
|
||||||
|
typeof y != 'number' ||
|
||||||
|
typeof maxWidth != 'number' ||
|
||||||
|
typeof lineHeight != 'number'
|
||||||
|
) {
|
||||||
|
console.log('必填参数有误', text, x, y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var context = this;
|
||||||
|
var canvas = context.canvas;
|
||||||
|
|
||||||
|
// 字符分隔为数组
|
||||||
|
var arrText = text.split('');
|
||||||
|
var line = '';
|
||||||
|
|
||||||
|
var style = view.style;
|
||||||
|
var hasStroke = style['-webkit-text-stroke'] ? true : false;
|
||||||
|
var strokeInfo = [];
|
||||||
|
|
||||||
|
context.textBaseline = 'top';
|
||||||
|
|
||||||
|
if (hasStroke) {
|
||||||
|
strokeInfo = style['-webkit-text-stroke'].split(' ');
|
||||||
|
context.lineWidth = parseInt(strokeInfo[0]);
|
||||||
|
context.strokeStyle = strokeInfo[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var n = 0; n < arrText.length; n++) {
|
||||||
|
var testLine = line + arrText[n];
|
||||||
|
var metrics = context.measureText(testLine);
|
||||||
|
var testWidth = metrics.width;
|
||||||
|
|
||||||
|
if ((testWidth > maxWidth && n > 0) || arrText[n] === '\n') {
|
||||||
|
// 绘制阴影
|
||||||
|
if (style['text-shadow']) {
|
||||||
|
drawShadow(context, view.style);
|
||||||
|
}
|
||||||
|
// 描边
|
||||||
|
if (hasStroke) {
|
||||||
|
context.strokeText(line, x, y);
|
||||||
|
}
|
||||||
|
context.fillText(line, x, y);
|
||||||
|
line = arrText[n] === '\n' ? '' : arrText[n];
|
||||||
|
y += lineHeight;
|
||||||
|
} else {
|
||||||
|
line = testLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 绘制阴影
|
||||||
|
if (style['text-shadow']) {
|
||||||
|
drawShadow(context, view.style);
|
||||||
|
}
|
||||||
|
// 描边
|
||||||
|
if (hasStroke) {
|
||||||
|
context.strokeText(line, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.fillText(line, x, y);
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user