答题卡生成与打印

前言

前文说了如何识别答题卡,本文来说说怎么生成答题卡。

OpenCV可以用来生成,但是文字换行等场景就比较难实现,这里使用HTML生成答题卡。

A3/A4尺寸

A4 210mm×297mm

A3 420mm×297mm

HTML转Canvas

虽然OpenCV可以用来绘图 但是制作答题卡的时候还是建议使用HTML来实现,并用html2canvas转为图片。

http://html2canvas.hertzen.com/

https://www.bootcdn.cn/html2canvas/

添加引用

1
<script src="https://cdn.bootcdn.net/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>

或者

1
2
npm install --save html2canvas
import html2canvas from "html2canvas";

示例

1
2
3
html2canvas(document.querySelector("#capture")).then(canvas => {
document.body.appendChild(canvas)
});

注意

部分样式该组件转换的图片和原样式不同。

如table转换的时候border并不会合并,所以计算坐标的时候要加上间隔的像素,如果是三行那么就要加2像素。

Canvas合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
get_all_page: async function () {
let cols = this.cols;
let pages = document.querySelectorAll(".page");
var canvas_arr = [];
let temp_canvas_arr = [];
for (let i = 0; i < pages.length; i++) {
const page = pages[i];
let canvas = await html2canvas(page);
temp_canvas_arr.push(canvas);
if (temp_canvas_arr.length === cols) {
var canvas_all = document.createElement("canvas");
let page_width = temp_canvas_arr[0].width;
let page_height = temp_canvas_arr[0].height;
canvas_all.width = page_width * cols;
canvas_all.height = page_height;
var context = canvas_all.getContext("2d");
for (let j = 0; j < temp_canvas_arr.length; j++) {
const citem = temp_canvas_arr[j];
context.drawImage(citem, j * page_width, 0, page_width, page_height);
}
canvas_arr.push(canvas_all);
temp_canvas_arr = [];
}
}
return canvas_arr;
},

Canvas下载为图片

1
2
3
4
5
6
7
html2canvas(document.querySelector(".page")).then(canvas => {
let href = canvas.toDataURL() // 获取canvas对应的base64编码
let a = document.createElement('a') // 创建a标签
a.download = "答题卡.png" // 设置图片名字
a.href = href
a.dispatchEvent(new MouseEvent('click'))
});

Canvas下载为PDF

https://artskydj.github.io/jsPDF/docs/index.html

图片生成PDF

添加引用

1
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.debug.js" integrity="sha384-NaWTHo/8YCBYJ59830LTz/P4aQZK1sS0SneOgAvhsIl3zBu8r9RevNg5lHCHAuQ/" crossorigin="anonymous"></script>

或者

1
2
npm install jspdf --save
import jsPDF from 'jspdf';

单页下载

示例代码:

1
2
3
4
5
6
7
8
9
html2canvas(document.querySelector(".page")).then(canvas => {
// 三个参数,第一个方向,第二个单位,第三个尺寸格式
var doc = new jsPDF('portrait', 'mm', 'a4');
doc.addImage(canvas, 'PNG', 0, 0, 210, 297);
//添加第二页
doc.addPage('a4', 'portrait');
doc.addImage(canvas, 'PNG', 0, 0, 210, 297);
doc.save('a4.pdf');
});

多页下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
download_page: async function () {
let pages = document.querySelectorAll(".page");
// 三个参数,第一个方向,第二个单位,第三个尺寸格式
var doc = new jsPDF('portrait', 'mm', 'a4');
for (let i = 0; i < pages.length; i++) {
const page = pages[i];
let canvas = await html2canvas(page);
// 默认有一页 所以第一页不用添加
if (i !== 0) {
doc.addPage('a4', 'portrait');
}
doc.addImage(canvas, 'PNG', 0, 0, 210, 297);
}
doc.autoPrint({ variant: 'non-conform' });
doc.save('autoprint.pdf');
},

合并下载

两张A4合并为A3下载

合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
get_all_page: async function () {
let cols = this.cols;
let pages = document.querySelectorAll(".page");
var canvas_arr = [];
let temp_canvas_arr = [];
for (let i = 0; i < pages.length; i++) {
const page = pages[i];
let canvas = await html2canvas(page);
temp_canvas_arr.push(canvas);
if (temp_canvas_arr.length === cols) {
var canvas_all = document.createElement("canvas");
let page_width = temp_canvas_arr[0].width;
let page_height = temp_canvas_arr[0].height;
canvas_all.width = page_width * cols;
canvas_all.height = page_height;
var context = canvas_all.getContext("2d");
for (let j = 0; j < temp_canvas_arr.length; j++) {
const citem = temp_canvas_arr[j];
context.drawImage(citem, j * page_width, 0, page_width, page_height);
}
canvas_arr.push(canvas_all);
temp_canvas_arr = [];
}
}
return canvas_arr;
},

下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
download_page: async function () {
// 三个参数,第一个方向,第二个单位,第三个尺寸格式
var doc = new jsPDF('landscape', 'mm', 'a3');
let canvas_arr = await this.get_all_page();
for (let i = 0; i < canvas_arr.length; i++) {
const canvas = canvas_arr[i];
// 默认有一页 所以第一页不用添加
if (i !== 0) {
doc.addPage('a3', 'landscape');
}
doc.addImage(canvas, 'PNG', 0, 0, 420, 297);
}
doc.save('autoprint.pdf');
},

方法及参数

jsPDF()

Name Type Default Description
orientation string portrait 方向 “portrait” or “landscape” (or shortcuts “p” or “l”).
unit string mm 单位 “pt” (points), “mm”, “cm”, “m”, “in” or “px”.
format string/Array a4 首页的大小 可以使用:a0 - a10 b0 - b10 c0 - c10 默认为”a4”.
也可以使用具体的大小数组 如: [595.28, 841.89]

添加图片

注意添加图片前一定要先添加页面。

addImage(imageData, format, x, y, width, height, alias, compression, rotation)

Parameters:

Name Type Description
imageData string \ HTMLImageElement \ HTMLCanvasElement \ Uint8Array imageData as base64 encoded DataUrl or Image-HTMLElement or Canvas-HTMLElement
format string format of file if filetype-recognition fails, e.g. ‘JPEG’
x number x Coordinate (in units declared at inception of PDF document) against left edge of the page
y number y Coordinate (in units declared at inception of PDF document) against upper edge of the page
width number width of the image (in units declared at inception of PDF document)
height number height of the Image (in units declared at inception of PDF document)
alias string alias of the image (if used multiple times)
compression string compression of the generated JPEG, can have the values ‘NONE’, ‘FAST’, ‘MEDIUM’ and ‘SLOW’
rotation number rotation of the image in degrees (0-359)

下载后自动打印

下载后的文件打开时自动调用打印

1
2
3
4
5
6
7
8
9
10
html2canvas(document.querySelector(".page")).then(canvas => {
// 三个参数,第一个方向,第二个单位,第三个尺寸格式
var doc = new jsPDF('portrait', 'mm', 'a4');
doc.addImage(canvas, 'PNG', 0, 0, 210, 297);
//添加第二页
doc.addPage('a4', 'portrait');
doc.addImage(canvas, 'PNG', 0, 0, 210, 297);
doc.autoPrint({ variant: 'non-conform' });
doc.save('autoprint.pdf');
});

注意这样并不会在下载后自动打印,只是下载的文件被打开时触发打印。

Canvas打印

打印单张

1
2
3
4
5
6
7
8
9
10
11
12
html2canvas(document.querySelector(".page")).then(canvas => {
var dataURL = canvas.toDataURL("image/png");
var printWindow = window.open();
var style = document.createElement('style');
style.innerHTML = "@media print {@page{margin:0;size:210mm 297mm;}}";
printWindow.document.head.appendChild(style);
printWindow.document.write('<img src="' + dataURL + '" width="100%" />');
setTimeout(() => {
printWindow.print();
printWindow.close();
}, 0);
});

打印多张

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
print_page: async function () {
let pages = document.querySelectorAll(".page");
var printWindow = window.open();
var style = document.createElement('style');
style.innerHTML = "@media print {@page{margin:0;size:420mm 297mm;}}";
printWindow.document.head.appendChild(style);
for (let i = 0; i < pages.length; i++) {
const page = pages[i];
let canvas = await html2canvas(page);
var dataURL = canvas.toDataURL("image/png");
printWindow.document.write('<img src="' + dataURL + '" width="100%" />');
}
setTimeout(() => {
printWindow.print();
printWindow.close();
}, 0);
}

打印样式

网页上使用图片打印A3的时候要注意设置以下项,特别是纸张大小和边距,否则跟实际的效果不符合。

页面添加样式

1
2
3
4
5
@media print {
@page{
margin:0;size:420mm 297mm;
}
}

1
2
3
var style = document.createElement('style');
style.innerHTML = "@media print {@page{margin:0;size:420mm 297mm;}}";
printWindow.document.head.appendChild(style);

获取DIV坐标

绝对位置

网页元素的绝对位置,指该元素的左上角相对于整张网页左上角的坐标

首先,每个元素都有offsetTop和offsetLeft属性,表示该元素的左上角与父容器(offsetParent对象)左上角的距离。所以,只需要将这两个值进行累加,就可以得到该元素的绝对坐标。但这里要注意一个问题:要考虑offsetParent的border的宽度。

方式1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 得到对象的相对浏览器的坐标
export function getObjPos(_target) {
var target = _target;
var pos = {
x: target.offsetLeft,
y: target.offsetTop
};

target = target.offsetParent;
while (target) {
pos.x += target.offsetLeft + target.clientLeft;
pos.y += target.offsetTop + target.clientTop;

target = target.offsetParent
}
return pos;
}

注意

一定要添加父元素的Border的宽度(clientLeft)。

隐藏的元素要用opacity: 0;,不能用display: none;,否则获取不了位置。

这种方式不是特别精确,如果dom的宽高不是整数的时候会出现偏差。

运算效率也相对较低。

方式2

这种方式要注意滚动条所在的DOM是那个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export function getObjPos(_target, scroll_dom) {
var rect = _target.getBoundingClientRect();
var pos = {
x: rect.left,
y: rect.top
};
var scrollLeft = 0;
var scrollTop = 0;
if (scroll_dom) {
scrollLeft = scroll_dom.scrollLeft;
scrollTop = scroll_dom.scrollTop;
} else {
scrollLeft =
document.body.scrollLeft || document.documentElement.scrollLeft;
scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
}

pos.x += scrollLeft;
pos.y += scrollTop;
return pos;
}

注意

隐藏的元素要用opacity: 0;,不能用display: none;,否则获取不了位置。

运算效率相对高点。

相对位置

网页元素的相对位置,指该元素左上角相对于浏览器窗口左上角的坐标

方法1

获取元素的相对位置,JS还提供了一种更简单的方法:Element.getBoundingClientRect()

Element.getBoundingClientRect()返回一个对象,对象包含了元素距离窗口的位置属性:left、right、top、bottom

1
2
3
let odiv = document.querySelector(".div2");
console.info(odiv.getBoundingClientRect().left);
console.info(odiv.getBoundingClientRect().top);

方法2

有了绝对位置以后,获得相对位置就很容易了,只要将绝对坐标减去页面的滚动条滚动的距离就可以了。

1
2
3
4
5
6
7
8
function getObjPosR (element) {
let pos = getObjPos2(element);
var scrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft;
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
pos.x -= scrollLeft;
pos.y -= scrollTop;
return pos;
}

PX和MM互转

方式1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function unitUtil () {
var pxWidth = 0;
var tmpNode = document.createElement("DIV");
tmpNode.style.cssText = "width:1mm;position:absolute;left:0px;top:0px;z-index:99;visibility:hidden";
document.body.appendChild(tmpNode);
pxWidth = tmpNode.getBoundingClientRect().width.toFixed(2);
tmpNode.parentNode.removeChild(tmpNode);

this.mm2px = function (mm) {
return parseFloat((mm * pxWidth).toFixed(2));
}

this.px2mm = function (px) {
return parseFloat((px / pxWidth).toFixed(2));
}
}

调用

1
new unitUtil().px2mm(width)

方式2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function unitConversion () {
/**
* 获取DPI
* @returns {Array}
*/
this.getDPI = function () {
var arrDPI = new Array();
if (window.screen.deviceXDPI != undefined) {
arrDPI[0] = window.screen.deviceXDPI;
arrDPI[1] = window.screen.deviceYDPI;
}
else {
var tmpNode = document.createElement("DIV");
tmpNode.style.cssText = "width:1in;height:1in;position:absolute;left:0px;top:0px;z-index:99;visibility:hidden";
document.body.appendChild(tmpNode);
arrDPI[0] = parseInt(tmpNode.offsetWidth);
arrDPI[1] = parseInt(tmpNode.offsetHeight);
tmpNode.parentNode.removeChild(tmpNode);
}
return arrDPI;
};
/**
* px转换为mm
* @param value
* @returns {number}
*/
this.px2mm = function (value) {
var inch = value / this.getDPI()[0];
var c_value = inch * 25.4;
return c_value;
};

/**
* mm转换为px
* @param value
* @returns {number}
*/
this.mm2px = function (value) {
var inch = value / 25.4;
var c_value = inch * this.conversion_getDPI()[0];
return c_value;
}
}

调用

1
2
new unitConversion().px2mm(width)
new unitConversion().mm2px(width)