资讯专栏INFORMATION COLUMN

H5 canvas生成图片并上传文件转成PDF下载canvas文字排版

canopus4u / 2941人阅读

摘要:将预览的图片上传,后端生成,在管理系统中下载。技术要点文字排版设置指定背景颜色引入外部字体绘制文字图片将生成的图片转成上传这里根据后端协商,此处后端要求将图片生成,并点击批量下载实现步骤文字排版在一般容器中,如果要实现文字的排版很容易。

最近遇到一个业务需求,在小程序端定制预览功能,并在预览的图片中使用指定的外部字体。将预览的图片上传OSS,后端生成PDF,在管理系统中下载。
但是…………,经过实践发现,小程序尽管做了分包处理,依旧不能在本地存放字体包,把字体放OSS上返回,但是出现跨域,尽管配置了允许跨域,依旧不行。而且!!!小程序的canvas API没法设置字体,没有h5中canvas中的context.font = "字体名称"方法。最终决定曲线救国,放弃小程序端的预览生成canvas功能,将canvas引入字体,生成图片等操作放在管理系统中,采用原生canvas来实现。

技术要点

canvas文字排版

canvas设置指定背景颜色

canvas引入外部字体

canvas绘制文字图片

将canvas生成的base64图片转成file上传(这里根据后端协商,此处后端要求)

将图片生成PDF,并点击批量下载

实现步骤 canvas文字排版 在一般HTML容器中,如果要实现文字的排版很容易。比如:

实现文本超出自动换行,默认文本超出容器宽度就会自动换行,也可以使用word-wrap:break-word实现强制换行。
实现文字竖排,有几种方式:

给文本容器设置writing-mode样式:(存在兼容性问题)

</>复制代码

  1. writing-mode:vertical-rl;//垂直方向自右而左的书写方式。即 top-bottom-right-left
  2. 或者
  3. writing-mode:vertical-lr;//垂直方向内内容从上到下,水平方向从左到右

具体效果如图:

但是这个对于浏览器也存在一定兼容性问题,使用的时候需要注意。

使用宽度控制换行:(不存在兼容性,推荐方式)

设置每行的宽度为一个字大小,利用文本超出默认换行的特性,或者设置超出强制换行,实现文本竖排。

利用br标签实现或者每个文字存放一个标签实现换行:(很死板的写法,比较low,不推荐)

给每个文字后添加br标签,或者每个文字放一个标签,这样写灵活性不高,非常不推荐!

canvas中实现文字排版

实现文字横排

canvas中,如果文本超出canvas大小,并不会自动换行,会直接在超出的后面继续绘制成一排。
canvas中也没有直接可以设置换行的api,那该怎么实现换行呢?
可以通过js控制,通过计算当前绘制文字的x坐标,如果x坐标大于canvas的宽度,将x坐标赋值为0(绘制的起始点x坐标),y坐标累加一个文字的高度,从而实现文本换行。

实现文字竖排

竖排的逻辑和横排是一样的。文字竖排只是y坐标累加,趟超过canvas的高度时,将y坐标赋值为0(绘制的起始点y坐标),x坐标累加一个文字的高度,从而实现竖排且文本换行。

</>复制代码

  1. 部分代码片段

</>复制代码

  1. /**
  2. * canvas绘制文字
  3. * @param {CanvasRenderingContext2D对象} context
  4. * @param {绘制内容} text
  5. * @param {起始点x坐标制} x
  6. * @param {起始点y坐标制} y
  7. */
  8. drawTextVertical(context, text, x, y) {
  9. let startX = x,
  10. startY = y; //记录开始的位置,用于文字换行赋值
  11. let spaceCount = 0;
  12. let arrText = text.trim().split("");
  13. let formatText = text.replace(///g, "").split(""); // 去掉单斜杠
  14. let align = context.textAlign;
  15. let baseline = context.textBaseline;
  16. context.textAlign = "center";
  17. context.textBaseline = "middle";
  18. context.font = "Pacifico"
  19. // 开始逐字绘制
  20. arrText.forEach(function (letter, index) {
  21. // 确定下一个字符的纵坐标位置
  22. // 是否需要旋转判断
  23. let code = letter.charCodeAt(0);
  24. // 计算文字间距
  25. let letterWidth = 22 * 2.3;
  26. if (code <= 256) {
  27. context.translate(x, y);
  28. // 英文字符,旋转90°
  29. context.rotate(90 * Math.PI / 180);
  30. context.translate(-x, -y);
  31. }
  32. if (code !== 47) context.fillText(letter, x, y);
  33. // 旋转坐标系还原成初始态
  34. context.setTransform(1, 0, 0, 1, 0, 0);
  35. // 单斜杠换行或者长度超过8 此处要过滤在第9字是换行的符号的情况
  36. if ((code === 47 && !spaceCount) || (!spaceCount && index && index % 7 === 0)) {
  37. // 单斜杠/ 代表换行 charCode=47
  38. spaceCount += 1;
  39. y = startY;
  40. x = index ? (startX + letterWidth) : x;
  41. startX = x;
  42. } else if (code !== 47) {
  43. // 如果是空格 减少字间距
  44. if (code !== 32) {
  45. y = y + letterWidth;
  46. } else {
  47. y = y + letterWidth / 2
  48. }
  49. }
  50. });
  51. // 水平垂直对齐方式还原
  52. context.textAlign = align;
  53. context.textBaseline = baseline;
  54. }

canvas设置背景颜色

canvas生成图片的时候可以指定图片格式(jpg,jpeg,png等),但是只能生成位图(放大会失真)。如果想提高canvas生成图片的质量,可以引入 hidpi-canvas-polyfill 插件,具体使用可以参考这篇文章 解决canvas生成图片模糊 。
canvas生成图片的背景默认是透明的,如果想多带带设置背景颜色,可以使用ctx.fillStyle进行填充,但是设置文字颜色,则文字颜色会覆盖背景颜色,因为设置文字颜色也是使用ctx.fillStyle。那么,这种情况可以使用以下办法解决:
1.使用canvas.getImageData复制画布上的像素数据
2.循环遍历复制的每个像素点,然后给每个像素设置rgb
3.将设置好的流数据通过putImageData放回画布上。

</>复制代码

  1. let imageData = ctx.getImageData(0, 0, width, height);
  2. for (let i = 0; i < imageData.data.length; i += 4) {
  3. // 当该像素是透明的,则设置成白色
  4. if (imageData.data[i + 3] == 0) {
  5. imageData.data[i] = 255;
  6. imageData.data[i + 1] = 255;
  7. imageData.data[i + 2] = 255;
  8. imageData.data[i + 3] = 255;
  9. }
  10. }
  11. ctx.putImageData(imageData, 0, 0);

但是要注意背景和文字的绘制顺序,必须先绘制背景,再绘制文字,如果顺序颠倒,则文字会出现很明显的锯齿状,有点模糊,这就和定位中z-index原理类似。

canvas引入外部字体

1.首先引入字体库,为了节省本地空间,可以从服务端引入,但是需要注意跨域问题
也可以将字体库放本地,直接相对路径引入。

</>复制代码

  1. // 从服务端引入
  2. @font-face {
  3. font-family: "FZCUJINLJW";
  4. src: url("https://www.xxxx.com/FZCUJINLJW.TTF") ;
  5. }
  6. // 本地引入
  7. @font-face {
  8. font-family: "FZCUJINLJW";
  9. src: url("../../assets/FZCUJINLJW.TTF") ;
  10. }

2.通过CanvasRenderingContext2D对象设置字体,字号等

</>复制代码

  1. ctx.font = "24px FZCUJINLJW";
  2. ctx.fillStyle = "#db9a00";//填充颜色
canvas绘制文字图片

</>复制代码

  1. let canvas = document.getElementById("canvas");
  2. let ctx = canvas.getContext("2d");//拿到一个CanvasRenderingContext2D对象
  3. ctx.beginPath();// 开始绘制文字
  4. ctx.font = `${FONT_SIZE}px FZCUJINLJW`;
  5. ctx.fillStyle = "#db9a00";//填充颜色
  6. ctx.fillText("绘制的内容", /*绘制的x坐标*/, /*绘制的y坐标*/);
  7. let imgBase64 = canvas.toDataURL("image/png", 1);
  8. ctx.closePath();
  9. ctx.save();// 保存当前画布内容
  10. //如果需要在画布上循环绘制多次,需要手动清除画布上已经保存的内容,如果不清除,则画布内容会叠加。
  11. ctx.clearRect(0, 0, canvasObj.width, canvasObj.height);

canvas生成图片并上传服务端

通过ctx.toDataURL可以获取到画布内容的base64编码

</>复制代码

  1. let imgBase64 = canvas.toDataURL("image/png", 1);

如果服务端支持使用base64上传,则不用处理,此处因为后端需要file文件类型,所以需要将base64转成file对象,代码如下:

</>复制代码

  1. let file = dataURLtoFile(imgBase64, "jpg"); // 将base转为file对象
  2. function dataURLtoFile(urlData, fileName) {
  3. var bytes = window.atob(urlData.split(",")[1]); //去掉url的头,并转换为byte
  4. var mime = urlData.split(",")[0].match(/:(.*?);/)[1];
  5. //处理异常,将ascii码小于0的转换为大于0
  6. var ab = new ArrayBuffer(bytes.length);
  7. var ia = new Uint8Array(ab);
  8. for (var i = 0; i < bytes.length; i++) {
  9. ia[i] = bytes.charCodeAt(i);
  10. }
  11. return new File([ab], fileName, { type: mime });
  12. }

转成file对象后,通过FormData格式上传

</>复制代码

  1. let formdata = new FormData();
  2. formdata.append("multipartList", file);
  3. ajax.post(url,data:formdata).then()

此处需要注意,当canvas生成的图片比较小时(比如5kb以下),有可能导致文件上传失败,我之前踩过此坑。

将图片生成PDF,并点击批量下载

此处是和后端商量,将canvas生成的图片上传服务端,并返回图片的OSS地址,再将此地址作为参数传给后端,获取到PDF的下载链接,前端通过window.open(url)的方式实现文件下载。

</>复制代码

  1. let uploadUrl = window.interfercesPrefix + "/admin/goods/tbgoods/uploadImages";
  2. let downLoadUrl = "/app/goods/tbgoods/downLoadPdf";
  3. // 上传图片
  4. ajaxUploderImg({ url: uploadUrl, data: formdata }).then(res => {
  5. // 将图片作为参数获取PDF下载地址
  6. this.props.dispatch(downLoadPdf({ url: downLoadUrl, imgUrl: res.data }));
  7. }).catch(err => {
  8. if (err) {
  9. notification["error"]({
  10. message: err.message,
  11. description:
  12. "图片绘制出错,请重试!",
  13. });
  14. } else {
  15. notification["error"]({
  16. message: "下载出错,请返回"
  17. });
  18. }
  19. })

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105169.html

相关文章

  • Javascript 将html转成pdf,下载,支持多页哦(html2canvas 和 jsPDF

    摘要:最近碰到个需求,需要把当前页面生成,并下载。但这并不是真的截图,而是通过遍历页面结构,收集所有元素信息及相应样式,渲染出。由于只能将它能处理的生成,因此渲染出来的结果并不是与原来一致。 最近碰到个需求,需要把当前页面生成pdf,并下载。弄了几天,自己整理整理,记录下来,我觉得应该会有人需要 :) 项目源码地址:https://github.com/linwalker/... html2...

    macg0406 评论0 收藏0
  • Javascript 将html转成pdf,下载,支持多页哦(html2canvas 和 jsPDF

    摘要:最近碰到个需求,需要把当前页面生成,并下载。但这并不是真的截图,而是通过遍历页面结构,收集所有元素信息及相应样式,渲染出。由于只能将它能处理的生成,因此渲染出来的结果并不是与原来一致。 最近碰到个需求,需要把当前页面生成pdf,并下载。弄了几天,自己整理整理,记录下来,我觉得应该会有人需要 :) 项目源码地址:https://github.com/linwalker/... html2...

    codecraft 评论0 收藏0
  • 其实,前端还可以这样做简历

    摘要:接下来,亮出自己做的简历。登录进入后,就可以选择一个你喜欢的简历模板进行制作简历了。将页面左边的工具栏拿掉,然后将简历宽度放大到接近浏览器宽度即可达到像素最高的效果。此外,会自动保存你做的简历,方便下次编辑。 以下文章摘自我的博客,原文链接 下面的简历图片不上传了,想看的点击原文链接就能看到了。 简述下原理:首先找一个可以在线制作简历并提供简历模板的网站,然后在模板上填好自己的信息,并...

    zhaochunqi 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<