painter-highlight
Version:
A simple tool for generating highlighted code images.(一个简单的高亮代码图片生成工具)
450 lines (411 loc) • 12.1 kB
text/typescript
/* eslint-disable prettier/prettier */
import hljs from 'highlight.js';
import { Pen, toPx } from 'painter-kernel';
type template = {
background: string;
width: string;
height: string;
borderRadius: string;
views: any;
};
const phl = function (
CanvasNode: object,
canvas: CanvasRenderingContext2D,
template: template,
code: string,
language: string,
) {
let views: any[] = [];
let defaultStyle = {
default: {
color: '#55b5db',
},
property: {
color: '#a074c4',
},
comment: {
color: '#41535b',
},
literal: {
color: '#ee3300ef',
},
doctag: {
color: '#ff7b72',
},
built_in: {
color: '#55b5db',
},
keyword: {
color: '#e6cd69',
},
'template-tag': {
color: '#ff7b72',
},
'template-variable': {
color: '#9fca56',
},
type: {
color: '#9fca56',
},
string: {
color: '#55b5db',
},
attr: {
color: '#ff7b72',
},
number: {
color: '#cd3f45',
},
params: {
color: '#55b5db',
},
'variable.language': {
color: '#55b5db',
},
'variable.constant': {
color: '#55b5db',
},
subst: {
color: 'Purple',
},
title: {
color: '#d2a8ff',
},
'title.class': {
color: '#df88df',
},
'title.class.inherited': {
color: '#dda8ff',
},
'title.function': {
color: '#a074c4',
},
deletion: {
color: '#ffdcd7',
backgroundColor: '#67060c',
},
addition: {
color: '#aff5b4',
backgroundColor: '#033a16',
},
strong: {
color: '#c9d1d9',
fontWeight: 'bold',
},
emphasis: {
color: '#c9d1d9',
fontStyle: 'italic',
},
sign: {
color: '#eee',
},
attribute: {
color: '#9fca56',
},
};
// 转换成颜色的map
const styleMap = new Map(Object.entries(defaultStyle));
let stackMap = [];
// 匹配换行符
const reg = RegExp(/\n/g);
// 匹配字符
const reg2 = RegExp(/([^\s])/g);
// 匹配空格
const reg22 = RegExp(/([\s])/g);
// 匹配tab符
const reg3 = RegExp(/\t/g);
// 换行记录数
let lineWarp = 0;
// 多行注释记录数
let commentWarp = 0;
// 括号记录数
let leftBracket = 0;
let rightBracket = 0;
// 小圆点颜色
let dotsColor = ['#E0443E', '#DEA123', '#1AAB29'];
// 高度
let maxHeight = 0;
// 宽度
let maxWidth = 0;
let codeCopy = code
.split('\n')
.map((line, index) => {
let width: number;
let spaceLength: any = line.match(reg22)?.length;
if (spaceLength === undefined) spaceLength = 0;
width = line.match(reg2)?.length + spaceLength + (line.match(reg3)?.length ? line.match(reg3)?.length : 0);
if (line.match(reg22) && line.match(reg2)?.length) {
let tap1 = line.indexOf(line.match(reg2)[0]);
line = line.slice(0, tap1) + line.slice(0, tap1) + line.slice(tap1);
}
// if (line.match(',') || line.match('。')) {
// // 中文时
// width =
// 1.5 * line.match(reg2)?.length + 0.5*spaceLength + (line.match(reg3)?.length ? line.match(reg3)?.length : 0);
// }
if (typeof line === 'string' && line.match(reg3)) {
line = line.replace(reg3, ' ');
}
if (maxWidth < width) {
maxWidth = width ? width : 0;
}
maxHeight++;
return line;
})
.join('\n');
codeCopy = '\n' + codeCopy;
let stack = hljs.highlight(codeCopy, { language: language })._emitter.root.children;
// 分离\n的方法
const stringSeparate = (stackI: string) => {
// 还没分离的部分
let start = stackI;
// 内部有多少\n
let nCount = stackI.match(reg);
if (stackI.match(/\)/)) {
let rightBrackets = stackI.indexOf(')');
stackMap.push(stackI.slice(0, rightBrackets));
start = stackI.slice(rightBrackets);
}
if (nCount !== null) {
while (nCount.length) {
let sNode = start.indexOf(nCount[0]);
// 前
if (start.slice(0, sNode)) {
stackMap.push(start.slice(0, sNode));
}
// 中: 弹出第一个已经匹配过的
stackMap.push(start.slice(sNode, sNode + 1));
// 后
start = start.slice(sNode + 1);
nCount.shift();
}
}
stackMap.push(start);
};
// 将符号和换行符分离
for (let i = 0; i < stack.length; i++) {
if (typeof stack[i] === 'string') {
// string
// 有换行符和字符的时候
if (stack[i].match(reg) && stack[i].match(reg2)) {
stringSeparate(stack[i]);
} else if (stack[i].match(/\]/)) {
// 右括号粘住的问题
let rightMidBrackets = stack[i].indexOf(']');
if (stack[i].match(/\)/)) {
let rightBrackets = stack[i].indexOf(')');
stackMap.push(
stack[i].slice(0, rightMidBrackets),
stack[i].slice(rightMidBrackets, rightBrackets),
stack[i].slice(rightBrackets),
);
} else {
stackMap.push(stack[i].slice(0, rightMidBrackets), stack[i].slice(rightMidBrackets));
}
} else if (stack[i].match(/\)/)) {
let rightBrackets = stack[i].indexOf(')');
stackMap.push(stack[i].slice(0, rightBrackets), stack[i].slice(rightBrackets));
} else {
stackMap.push(stack[i]);
}
} else {
// object
if (stack[i].kind === 'comment') {
// 是注释时直接导入
stackMap.push(stack[i]);
} else if (stack[i].kind === 'property' && stack[i].children[0].children) {
stackMap.push(stack[i].children[0].children[0]);
} else if (stack[i].children.length > 1) {
// 若存在多个children
let stackCC = stack[i].children;
for (let i of stackCC) {
if (typeof i === 'string' && i.match(/\)/)?.length) {
let rightBrackets = i.indexOf(')');
stackMap.push(i.slice(0, rightBrackets + 1));
stackMap.push(i.slice(rightBrackets + 1));
} else {
stackMap.push(i);
}
}
} else if (
stack[i].kind === 'params' &&
typeof stack[i].children[0] === 'object' &&
stack[i].children[0].kind !== 'regexp'
) {
// console.log(stack[i]);
stringSeparate(stack[i].children[0]);
} else {
stackMap.push(stack[i]);
}
}
}
// 去除''
let ss = [];
stackMap.forEach((data) => {
if (data !== '') ss.push(data);
});
stack = ss;
// 主要循环判断
for (let index = 0; index < stack.length; index++) {
let t = {
id: 'hl0_' + index,
text: '',
type: 'text',
css: {
top: 'calc(hl0_' + (index - 1) + '.top)',
left: 'calc(hl0_' + (index - 1) + '.right)',
color: defaultStyle.default.color,
fontSize: '19px',
},
};
// 匹配为object时
if (typeof stack[index] === 'object') {
let col: { color: string };
// 有子项与没子项的区分
if (!stack[index].children.length) {
t.text = stack[index].children;
(t.css.left = 'calc(hl0_' + (index - 1) + '.right - 5px)'), (col = styleMap.get(stack[index].kind));
} else {
t.text = stack[index].children.join('');
(t.css.left = 'calc(hl0_' + (index - 1) + '.right + 1px)'), (col = styleMap.get(stack[index].kind));
}
// 括号美化
if (leftBracket || rightBracket) {
if (stack[index] !== ';') {
t.css.left = 'calc(hl0_' + (index - 1) + '.right - 8px)';
}
leftBracket = 0;
rightBracket = 0;
}
// 多行注释时
if (
stack[index].children.length &&
stack[index].children[0].kind !== 'regexp' &&
stack[index].children[0].match !== null &&
stack[index].children[0]?.match(/\/\*\*/g)
) {
commentWarp += stack[index].children[0].match(reg)?.length ? stack[index].children[0].match(reg)?.length : 0;
}
// 颜色匹配
if (!col) {
t.css.color = styleMap.get('default').color;
} else {
t.css.color = col.color;
}
// 匹配为string时
} else if (typeof stack[index] === 'string') {
t.text = stack[index];
// 区分符号和字母
if (stack[index].match(RegExp(/^[a-zA-Z]/g))) {
t.css.color = styleMap.get('string').color;
} else if (stack[index].match(RegExp(/=/g))) {
t.css.color = styleMap.get('attribute').color;
} else {
if (stack[index].match(reg2) !== null) {
if (stack[index].match(reg2).length !== 1) maxWidth = maxWidth + 0.6 * stack[index].match(reg2).length;
}
t.css.color = styleMap.get('sign').color;
}
t.css.left = 'calc(hl0_' + (index - 1) + '.right + 2px)';
// 括号美化
if (leftBracket || rightBracket) {
if (stack[index] !== ';') {
t.css.left = 'calc(hl0_' + (index - 1) + '.right - 10px)';
}
leftBracket = 0;
rightBracket = 0;
}
//调整空格位置
// if (stack[index].match(reg22)?.length) {
// let k = stack[index].match(reg22)?.length;
// t.css.left = 'calc(hl0_' + (index - 1) + '.right +' + k * 2 + ' px)';
// }
// 换行符存在时进行记录
if (stack[index].match(reg) && !stack[index].match(/\`/g)) {
lineWarp = stack[index].match(reg).length;
} else if (stack[index] === ' ') {
t.css.left = 'calc(hl0_' + (index - 1) + '.right - 8px)';
} else if (stack[index].match(/\(/) || stack[index].match(/\[/) || stack[index].match(/\{/)) {
switch (stack[index - 1]) {
case '(':
case '[':
case '{':
t.css.left = 'calc(hl0_' + (index - 1) + '.right - 10px)';
break;
case ' ':
t.css.left = 'calc(hl0_' + (index - 1) + '.right - 5px)';
break;
default:
t.css.left = 'calc(hl0_' + (index - 1) + '.right + 3px)';
break;
}
leftBracket = 1;
} else if (stack[index] == ')' || stack[index] == ']' || stack[index] == '}') {
if (stack[index - 1] == '}') {
t.css.left = 'calc(hl0_' + (index - 1) + '.right - 9px)';
}
rightBracket = 1;
}
}
// 换行
if (lineWarp) {
t.css.top = 'calc(hl0_' + (index - 1) + '.top +' + 23 * (lineWarp + commentWarp) + ' px)';
t.css.left = '0';
// 缩进问题
if (stack[index].match(reg) && stack[index].slice(2)) {
t.css.left = 'calc(hl0_0.right)';
}
lineWarp = 0;
commentWarp = 0;
}
// tab符号
// if (typeof stack[index] === 'string' && stack[index].match(reg3)) {
// t.css.left = 'calc(hl0_' + index + '.right + ' + 9 * stack[index].match(reg3).length + 'px)';
// }
// 放入最后的数组
views.push(t);
}
views[0].css.top = '55px';
views[1].css.left = 'calc(hl0_0.right + 0px)';
views[0].css.left = '0';
// 窗口小圆点
for (let t = 0; t < 3; t++) {
let dots = {
id: 'hl1_' + t,
type: 'rect',
css: {
top: '18px',
left: 18 + 20 * t + 'px',
height: '12px',
width: '12px',
color: dotsColor[t],
borderRadius: '50%',
},
};
views.unshift(dots);
}
// vscode插件使用
// views.unshift({
// id: 'hl1_' + 4,
// text: 'VSCode插件 前端每日一题',
// type: 'text',
// css: {
// top: '14px',
// left: 18 + 20 * 4 + 'px',
// fontSize: '17px',
// color: '#41535b',
// },
// });
if (template.height == 'auto') {
template.height = maxHeight * 23 + 55 + 'px';
}
if (template.width == 'auto') {
template.width = maxWidth * 12 + 'px';
}
CanvasNode.width = toPx(template.width);
CanvasNode.height = toPx(template.height);
template.views = views;
const pen = new Pen(canvas, template);
pen.paint();
};
export default phl;