canvas 实现单段文字自动换行、及多段文字自动换行

<template>
    <el-dialog v-model="visible" title="季度诊断" center width="425px" top="0" class="dialog-header-blue-bg">
        <canvas :ref="(ref) => ref && canvasRefs(ref)" width="375" height="780" style="margin: auto;"></canvas>
        <template #footer>
            <span class="dialog-footer">
                <el-button @click="visible = false">取消</el-button>
                <el-button type="primary" @click="handleSave">
                    保存
                </el-button>
            </span>
        </template>
    </el-dialog>
</template>

<script setup>
import { ref } from 'vue'
import request from "@/utils/request"
import { userHandleRequest } from '@/utils/hooks/userHandleRequest'
// 控制dialog显示隐藏
const visible = ref(false)
const basicData = ref({})  // 基础数据
// 获取canvas实例
let canvasRefs
const canvasRef = ref()
const canvasRefPromise = new Promise((res) => (canvasRefs = res));
(async function () {
    canvasRef.value = await canvasRefPromise
    draw(canvasRef.value)
})()
// x轴开始位置
const MARGIN_LEFT = 52;
// 最大宽度
const MAX_WIDTH = 280
// 行高
const LINE_HEIGHT = 20
// 开始绘制
const draw = (canvasRef) => {
    let ctx = canvasRef?.getContext('2d');
    if (!ctx) return
    const image = new Image();
    image.src = '/src/assets/image/1.png';
    image.onload = () => {
        ctx.drawImage(image, 0, 0, canvasRef.width, canvasRef.height);
        //修改字体
        ctx.font = 'bold 22px Arial';
        ctx.fillStyle = 'rgb(170,123,68)'
        //绘制文本
        ctx.fillText(`2022年第三季度`, 108, 105);
        //修改字体
        ctx.font = 'bold 15px Arial';
        ctx.fillStyle = 'rgb(51,51,51)'
        //绘制文本
        ctx.fillText('您好!', MARGIN_LEFT, 135);
        getWarpText(ctx, `公司2022年第三季度的财务已经完成,贵司的财务和经营成果如下:`, MARGIN_LEFT, 160, MAX_WIDTH, 24)
        //修改字体
        ctx.font = '13px Arial';
        ctx.fillStyle = 'rgb(51,51,51)'
        const arrText = [
            `1、资产总额:1000元,其中预付账款和其他应收款共计1000元,占资产总额1000%。`,
            `2、负债总额:1000元,其中预收账款和其他应付款共计1000元,占负债总额1000%。`,
            `3、所有者权益总额:1000元。`,
            `4、收入总额:1000元。`,
            `5、成本费用总额:1000元。`,
            `6、利润总额:1000元。`,
            `7、纳税总额:1000元,其中企业所得税1000元,增值税1000元,附加税费1000元,其他税额1000元。`
        ]
        getWarpTextMulti(ctx, arrText, 220)
        //修改字体
        ctx.font = 'bold 15px Arial';
        ctx.fillStyle = 'rgb(51,51,51)'
        //绘制文本
        getWarpText(ctx, `以上是贵公司季度财务报表情况,您有任何问题可以随时咨询您的会。`, MARGIN_LEFT, 624, MAX_WIDTH, 24)
    };
}

/**
 * 计算多句换行文字
 * @param: canvas canvas上下文对象
 * @param: arrText 渲染文本数组
 * @param y 渲染第一行y轴位置
 * @param gap 文字之间的上下间隔
*/
function getWarpTextMulti(canvas, arrText, y, gap = 10) {
    for (let index = 0; index < arrText.length; index++) {
        const text = arrText[index];
        let lineArr = getWarpText(canvas, text, MARGIN_LEFT, y, MAX_WIDTH, LINE_HEIGHT)
        y += LINE_HEIGHT * lineArr.length + gap
    }
}
/**
 * 计算换行文字
 * @param canvas canvas上下文对象
 * @param text 渲染文本
 * @param x 渲染x轴位置
 * @param y 渲染第一行y轴位置
 * @param maxWidth 最大宽度
 * @param lineHeight 行高
 * @param maxLine 最大行数 超出隐藏并显示省略号 不传入则代表不限制
 */
function getWarpText(canvas, text, x, y, maxWidth, lineHeight, maxLine) {
    // 对入参的类型进行检测
    if (typeof text != 'string' || typeof x != 'number' || typeof y != 'number' || typeof lineHeight != 'number') {
        throw new Error("参数传入出错")
    }
    //如果最大宽度未定义 默认为canvas宽度
    if (typeof maxWidth == 'undefined') {
        maxWidth = (canvas && canvas.width);
    }
    if (typeof lineHeight == 'undefined') {
        lineHeight = (canvas.canvas && parseInt(window.getComputedStyle(canvas.canvas).lineHeight))
            || parseInt(window.getComputedStyle(document.body).lineHeight)
    }
    let arrText = text.split('');
    let line = '';
    let lines = [];
    let lastLine = ""
    let ellipsis = canvas.measureText("...");
    let ellipsisWidth = ellipsis.width;
    for (let n = 0; n < arrText.length; n++) {
        //每个循环累加字符
        let testLine = line + arrText[n];
        //检测累加字符 获取累加字符的高度和宽度
        let metrics = canvas.measureText(testLine);
        let testWidth = metrics.width;
        let lineWidth = canvas.measureText(line).width;

        // 如果当前添加行是最后一行 则替换最后一个字符为"..." 判断长度是否需要删去最后一个字符
        if (maxLine && maxLine - 1 === lines.length && ((lineWidth + ellipsisWidth) > maxWidth)) {
            line = line.slice(0, line.length - 1) + "...";
            testWidth = lineWidth + ellipsisWidth;
        }
        //如果累加字符的宽度大于定义的绘制文本最大宽度 则绘制累加字符的文本 并且设置换行间距再次进行绘制
        if (testWidth > maxWidth && n > 0) {
            lastLine = line;
            lines.push({
                text: line,
                x: x,
                y: y
            })
            if (maxLine && maxLine <= lines.length) {
                break
            }
            line = arrText[n];
            y += lineHeight;
        } else {
            line = testLine;
        }
    }
    if (lastLine !== line) {
        lines.push({
            text: line,
            x: x,
            y: y
        })
    }
    for (let i = 0; i < lines.length; i++) {
        const item = lines[i];
        canvas.fillText(item.text, item.x, item.y);

    }
    return lines;
}

// 保存为图片
const handleSave = () => {
    // 获取图像数据  
    const imageData = canvasRef.value.toDataURL();
    // 将图像数据保存到本地文件  
    const link = document.createElement('a');
    link.href = imageData;
    link.download = `季度诊断报告.png`;
    link.click();
}

// 打开dialog 并请求数据
const open = async (e) => {
        visible.value = true
        if(canvasRef.value){
            draw(canvasRef.value)
        }
    } catch (error) {}

}
defineExpose({
    open
})
</script>

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值