Web printing exploration

  javascript

The author recently made a request to build a print template on the Web, learned some valuable things from it, and specially recorded an article to share.

summarize

This paper first describes the background of the requirements of the Web printing project of the project team in which the author works, then describes the problems encountered by the author in the process of practicing the Web printing project, expounds the solutions to the problems of Web printing, and finally gives another solution to the requirements of Web printing, that is, the use ofHeadless browserA scheme for generating and printing pictures. The expected reading time is 5 ~ 10 minutes.

This article is mainly divided into the following aspects:

  • Web build print template requirements
  • Basic concept

    • Printing device interface
    • Page model
    • Introduce print style
  • Dealing with Web Printing Pagination
  • Remove browser default header bottom
  • Build a custom header bottom
  • The Solution of Generating Pictures Using HeadlesBrowser

Web build print template requirements

The product manager’s little sister recently wrote the author such a demand:

  1. A template page for printing a report is realized, and the browser or the client calls the interface of the printing device to print the corresponding report.
  2. The corresponding report supports the configuration of report templates, which can be divided into several types, such as extremely simple version for free players, basic version for low-income players, advanced version for top-up players, and top-up version for local tyrants. Yes, top-up can become stronger.
  3. Pagination function is needed to support the display of corresponding contents to corresponding pages. For example: Content A is basic information, which needs to be displayed on the first page. Subsistence guarantee players enjoy Content B, which is displayed on the second page … Local tyrants enjoy all functions, which is displayed on the n page.
  4. Show the corresponding header and bottom of the product configuration.

So, in order to solve the above needs, the author wrote a template like this, as follows:

<div class="page1">
    <div>我是第一页1</div>
    <div>我是第一页2</div>
    <div>我是第一页3</div>
</div>
<div class="page2">
    <div>我是第二页1</div>
    <div>我是第二页2</div>
    <div>我是第二页3</div>
</div>
<div class="page3">
    <div>我是第三页1</div>
    <div>我是第三页2</div>
    <div>我是第三页3</div>
</div>
<div class="page4">
    <div>我是第四页1</div>
    <div>我是第四页2</div>
    <div>我是第四页3</div>
</div>

Basic concept

Printing device interface

Browser printing is a very mature application. The simplest printing is called directly.window.print()Or calldocument.execCommand('print'). At this time, the browser will pop up a print preview window, which is generated through the pagepdfUsed for print preview. As shown in the figure, a print preview of Google’s home page is shown:

Page model

Like CSS box model, page box model consists of margin, border, padding and content area:

There are two points to note:

  • When printing a page, only the content area of the page is printed out
  • By default, the page has header and footer information, which is displayed to the outside margin of the page.

By default, the page is displayed from left to right and from top to bottom. If you need to change the direction of the printing device, you can set the root elementdirectionAndwriting-modeProperty to change the page orientation.

Introduce print style

Print styles can be introduced in three ways:

  1. Use@media print
@media print {
    body {
        background-color:#FFFFFF;
        margin: 0mm;  /* this affects the margin on the content before sending to printer */
    }
    // ...
}
  1. Inline style usagemediaProperties:
<style type="text/css" media="print">
</style>
  1. Use in CSS@import
@import url("print.css") print;
  1. Used in HTMLlinkLabel additionmediaProperties:
<link rel="stylesheet" media="print" href="print.css">

Dealing with Web Printing Pagination

The first problem encountered in the project requirements is the need to deal with Web printing paging. Even if this part does not occupy the height of one page, manual paging is required. At first, I realized this by calculating the height of each part of the page and reserving a part of the outer edge distance below the height of the node corresponding to the part of the page. As shown in the following code, I learned the width-to-height ratio of A4 paper by looking up data.297 : 210To calculate the height to be reserved for each part, remove the distance from the outside of the page (20mm left and 20 mm right):

const A4_HEIGHT_WIDTH_RATE = 297 / (210 - 2 * 13); // 打印区域长宽比:(A4纸高)比(A4纸宽减去左右侧20mm的边距)
const PAGE_WIDTH = 680; // 页面宽度(像素值)
const PAGE_HEIGHT = PAGE_WIDTH * A4_HEIGHT_WIDTH_RATE; // 页面高度

const $page1El = document.querySelector('.page1');
const page1Height = parseInt($page1El.clientHeight); // page1的高度是多少像素
const pageNum = Math.ceil(page1Height / PAGE_HEIGHT); // page1需要占多少页,超过1页的高度,就需要占2页,因此向上取整
const marginBottom = pageNum * PAGE_HEIGHT - page1Height; // 需要预留多少外边距
$page1El.style.marginBottom = `${marginBottom}px`;

However, CSS already supports paging problems in printing devices, and can be set bybreak-after: page;Orpage-break-after: always;To realize paging in printing equipment:

.page1 {
    break-after: page;
    page-break-after: always;
}
// ...

Remove browser default header bottom

After the paging effect is realized, the url of the current page will appear at the bottom of the page when the page is printed:

By default, the page has header and footer information that is displayed to the outside margin of the page. By removing the outside margin of the page model, the content will not extend to the edge of the pagebodyelementarymarginTo ensure A4 paper printed pages with outside margin:

@media print {
    @page {
        margin: 0;
    }
    body {
        margin: 2cm;
    }
}

The printed page no longer has the default page bottom:

Build a custom header bottom

By combining the corresponding header, bottom elementpositionSet tofixedThe corresponding nodes can be fixed to any part of the page, and they will be repeated on each printed page.

.header {
    position: fixed;
    top: 0;
}
.footer {
    position: fixed;
    bottom: 0;
}

The Solution of Generating Pictures Using HeadlesBrowser

So much has been said above, all of which are Web printing solutions implemented at the front end, but in fact, if you can directly go through the Web page in the background, save the page template in advance, pull back the background data and run it.Headless browserGenerate a screenshot, by printing screenshots can solve this problem, the following tophantomjscoordinatepugFor example, show the author useHeadless browserA simple solution for generating pictures:

// 针对链接的截图服务
// 返回phantom实例的promise对象,为了获取对应的base64编码
function captureByUrl(url, data) {
    let instance;
    let page;
    const destroyInstance = () => {
        // 关闭页面
        page.close();

        // 退出实例
        instance.exit();
    };

    return phantom.create() // 首先,创建phantom实例
        .then((_instance) => {
            instance = _instance;

            return instance.createPage();
        })
        .then((_page) => {
            page = _page;
            if (data.width && data.height) { // 设置phantom截图页面的宽高值
                page.property('viewportSize', {
                    width: data.width,
                    height: data.height
                });
            }
            page.setting('userAgent', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36');
            return page.open(url);
        })
        .then(() => {
            return page.renderBase64('PNG'); // 渲染对应图片,拿到base64字符串
        })
        .then((image) => {
            destroyInstance(); // 销毁phantom实例
            return image;
        }, (error) => {
            destroyInstance(); // 销毁phantom实例
            throw error;
        });
}

As shown in the above code, use Headless browser to open a link throughrenderBase64Generate base64 string from preview screenshot of corresponding page.

Correspondingly, on the server side, you can read the pre-written datapugTemplate, the corresponding data is passed in to generate a preview map of the corresponding page, and then screenshots generated by Headless browser are saved locally, thus realizing the solution of Web printing on the server. as shown in the following code, the template is read for the server and part of the code of the picture is saved:

// 针对模板和数据的截图服务
function captureByTemplate(template, data) {
    const content = pug.compile(template)(Object.assign({
        URL_PREFIX,
    }, data));
    const contentInBase64 = new Buffer(content).toString('base64');
    const url = `data:text/html;charset=utf8;base64,${contentInBase64}`;

    return captureByUrl(url, data);
}

captureByTemplate(fs.readFileSync('./print.pug', 'utf-8'), data)
    .then(base64Data => {
        fs.writeFile("out.png", base64Data, 'base64', function(err) {
            console.error(err);
        });
    })
    .catch(err => {
        console.error(err);
    });

Summary

This paper is a project summary of the author’s practice of Web printing related projects. Firstly, it describes the general requirements of Web printing projects, and then, under the printing equipment, the presentation form of the page model. Then it describes some common problems encountered by the author in the process of practice and gives some general solutions. Finally, considering that Headless browser can also be used to meet the requirements of printing templates, the author takes phantomjs and pug templates as examples to carry out a simple practice.

Finally, the author has recently established a technology exchange group. Everyone is welcome to discuss technology in the group. In addition, the medical and health department is also recruiting front-end, back-end and data engineers to help the group advertise. Friends who want to join Tencent’s medical and health department can add groups or send resumes directly to counterxing@tencent.com.