答题卡生成与打印

2022-09-09 09:07:41 浏览数 (1)

前言

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

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()

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

代码语言:javascript复制
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 对象,其提供了元素的大小及其相对于视口的位置。

该对象使用 lefttoprightbottomxywidthheight 这几个以像素为单位的只读属性描述整个矩形的位置和大小。

除了 widthheight 以外的属性是相对于视图窗口的左上角来计算的。

0 人点赞