前言
前文说了如何识别答题卡,本文来说说怎么生成答题卡。
OpenCV可以用来生成,但是文字换行等场景就比较难实现,这里使用HTML生成答题卡。
A3/A4尺寸
A4 210mm×297mm
A3 420mm×297mm
HTML转Canvas
虽然OpenCV可以用来绘图 但是制作答题卡的时候还是建议使用HTML来实现,并用html2canvas
转为图片。
http://html2canvas.hertzen.com/
https://www.bootcdn.cn/html2canvas/
添加引用
代码语言:javascript复制<script src="https://cdn.bootcdn.net/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
或者
代码语言:javascript复制npm install --save html2canvas
import html2canvas from "html2canvas";
示例
代码语言:javascript复制html2canvas(document.querySelector("#capture")).then(canvas => {
document.body.appendChild(canvas)
});
注意
部分样式该组件转换的图片和原样式不同。 如table转换的时候border并不会合并,所以计算坐标的时候要加上间隔的像素,如果是三行那么就要加2像素。
Canvas合并
代码语言:javascript复制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下载为图片
代码语言:javascript复制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
添加引用
代码语言:javascript复制<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.debug.js" integrity="sha384-NaWTHo/8YCBYJ59830LTz/P4aQZK1sS0SneOgAvhsIl3zBu8r9RevNg5lHCHAuQ/" crossorigin="anonymous"></script>
或者
代码语言:javascript复制npm install jspdf --save
import jsPDF from 'jspdf';
单页下载
示例代码:
代码语言:javascript复制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');
});
多页下载
代码语言:javascript复制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下载
合并
代码语言:javascript复制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;
},
下载
代码语言:javascript复制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) |
下载后自动打印
下载后的文件打开时自动调用打印
代码语言:javascript复制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打印
打印单张
代码语言:javascript复制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);
});
打印多张
代码语言:javascript复制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的时候要注意设置以下项,特别是纸张大小和边距,否则跟实际的效果不符合。
页面添加样式
代码语言:javascript复制@media print {
@page{
margin:0;size:420mm 297mm;
}
}
即
代码语言:javascript复制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
代码语言:javascript复制let odiv = document.querySelector(".div2");
console.info(odiv.getBoundingClientRect().left);
console.info(odiv.getBoundingClientRect().top);
注意 一定要添加父元素的Border的宽度(clientLeft)。 隐藏的元素要用opacity: 0;,不能用display: none;,否则获取不了位置。 这种方式不是特别精确,如果dom的宽高不是整数的时候会出现偏差。 运算效率也相对较低。
方法2
这种方式要注意滚动条所在的DOM是那个。
代码语言:javascript复制// 得到对象的相对浏览器的坐标
function getObjPos(_target) {
var target = _target;
var pos = {
x: target.offsetLeft,
y: target.offsetTop
};
target = target.offsetParent;
while (target) {
pos.x = target.offsetLeft;
pos.y = target.offsetTop;
target = target.offsetParent
}
return pos;
}
let odiv = document.querySelector(".div2");
let pos = getObjPos(odiv);
console.info("x:" pos.x " y:" pos.y)
注意
隐藏的元素要用
opacity: 0;
,不能用display: none;
,否则获取不了位置。 运算效率相对高点。
相对位置
网页元素的相对位置,指该元素左上角
相对于浏览器窗口左上角的坐标
。
方法1
获取元素的相对位置,JS还提供了一种更简单的方法:Element.getBoundingClientRect()
代码语言:javascript复制Element.getBoundingClientRect()返回一个对象,对象包含了元素距离窗口的位置属性:left、right、top、bottom
let odiv = document.querySelector(".div2");
console.info(odiv.getBoundingClientRect().left);
console.info(odiv.getBoundingClientRect().top);
方法2
有了绝对位置以后,获得相对位置就很容易了,只要将绝对坐标减去页面的滚动条滚动的距离就可以了。
代码语言:javascript复制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
代码语言:javascript复制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));
}
}
调用
代码语言:javascript复制new unitUtil().px2mm(width)
方式2
代码语言:javascript复制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;
}
}
调用
代码语言:javascript复制new unitConversion().px2mm(width)
new unitConversion().mm2px(width)
HTML
client
其中
- clientHeight:内容可视区域的高度,也就是说页面浏览器中可看到内容区域的高度(不含边框,也不含滚动条)。
- clientLeft/clientTop: 就是边框的宽度,如果不指定一个边框,值就是0.
clientWidth
属性表示元素的内部宽度,以像素计。该属性包括内边距,但不包括垂直滚动条(如果有)、边框和外边距。
如上图所示, 计算方式为, 分为如下两种:
- 存在垂直滚动条 content width padding - scollbarWidth
- 不存在滚动条 content width padding
clientHeight
属性表示元素内部的高度(单位像素),包含内边距,但不包括水平滚动条、边框和外边距。
如上图所示, 计算方式为如下两种:
- 存在水平滚动条 content height padding - scollbarWidth
- 不存在滚动条 content height padding
clientLeft
表示一个元素的左边框的宽度.
计算方式为如下两种情况:
- 如果文字方向从右往左(默认从左往右,通过设置 direction: rtl;)进行排列,且存在垂直滚动条的情况下 border width scollbar width
- 默认情况下 border width
注意:
如果当前元素是行内元素(inline)时, clientLeft将返回 0;
计算滚动条宽度
代码语言:javascript复制// 默认情况下(没有滚动条情况下)
clientWidth = content width paddingLeftWidth paddingRightWidth;
// 对上面示例来说 clientWidth = 200 10 10;
// 有滚动条情况下:
clientWidth = (content width paddingLeftWidth paddingRightWidth) - scrollbarWidth
// 可以推断出滚动条计算方式:
scrollbarWidth = (content width paddingLeftWidth paddingRightWidth) - clientWidth;
offset
计算时都包括此对象的border,padding 获取对象到父级的距离取决于最近的定位父级
其中
- offsetWidth:获取元素自身的宽度(包含边框)
- offsetHeight:获取元素自身的高度(包含边框)
- offsetLeft:获取对象左侧与定位父级之间的距离
- offsetTop:获取对象上侧与定位父级之间的距离
offsetLeft 返回值包含:
- 本元素向左偏移的像素值,元素的外边距(margin)
offset父元素
的左侧内边距(padding)
注意
offset父元素 不是父元素 是一直向外找的第一个有定位的元素。
注:
与style.top 不同,offsetLeft只可读,不可以对其进行赋值。 offsetTop 返回的是数字,而 style.top 返回的是字符串,除了数字外还带有单位:px。 与style.width属性的区别在于:如对象的宽度设定值为百分比宽度,则无论页面变大还是变小,style.width都返回此百分比,而offsetWidth则返回在不同页面中对象的宽度值而不是百分比值
比如这个例子
代码语言:javascript复制<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>offset</title>
</head>
<body>
<div class="div1">
<div class="div2"></div>
</div>
<style>
body {
margin: 0;
padding: 0;
}
.div1 {
position: relative;
width: 200px;
height: 200px;
background-color: burlywood;
margin: 64px;
border: 32px solid black;
padding: 16px;
}
.div2 {
position: relative;
width: 100px;
height: 100px;
background-color: rgb(127, 165, 219);
margin: 8px;
border: 4px solid black;
padding: 2px;
}
</style>
<script>
let div2 = document.querySelector(".div2");
console.info(div2.offsetLeft);
console.info(div2.offsetParent)
</script>
</body>
</html>
获取到的div2.offsetLeft
就是24,为div2的margin
div1的padding
谷歌、Edge、火狐、IE均是如此。
scroll
其中:
- scrollLeft:设置或获取当前左滚的距离,即左卷的距离;
- scrollTop:设置或获取当前上滚的距离,即上卷的距离;
- scrollHeight:获取对象可滚动的总高度;
- scrollWidth:获取对象可滚动的总宽度;
- scrollHeight = content padding;(即border之内的内容)
getBoundingClientRect
Element.getBoundingClientRect()
方法返回一个 DOMRect
对象,其提供了元素的大小及其相对于视口的位置。
该对象使用 left
、top
、right
、bottom
、x
、y
、width
和 height
这几个以像素为单位的只读属性描述整个矩形的位置和大小。
除了 width
和 height
以外的属性是相对于视图窗口的左上角来计算的。