在web页面中提供打印功能

最近一个项目需要在页面里面提供打印功能,这个功能似乎看起来很不起眼,因为在目前各大网站的文章页面都有打印功能,之前的项目也使用过,而且很简单就一句话:

window.print();

调用本地的打印接口,很方便,也很轻量,主流浏览器支持非常好,用户只要点个按钮或者链接就基本上搞定。 但是也有问题,比如不能精确分页,不能设置纸型等。

除此之外,还有两种常见的基于网页的打印方式: 继续阅读在web页面中提供打印功能

JTable Print

几天在做关于JTable的打印工作,项目中客户需要直接将JTable打印出来,之前这方面工作做的比较少,经过这几天的研究,稍微有些积累,现在分享一下。

JTable打印目前用的比较多的还是JasperReport来实现的,JasperReport开源免费,但是不是特别好用,这也是为什么JasperReport免费用,但是培训是收费的。

不过常用的一些JTable的打印实现起来也还好了,只是复杂的稍微有些麻烦,可以看看我之前介绍的一篇入门文章:JasperReport 个人使用的一些经验

这里主要分享的是通过Swing 打印的积累,JasperReport常用的简单的打印没问题,但是遇上比较复杂的表单,比如合成表头等,就稍微有点麻烦,当然也有可能是自己没有找到JasperReport的处理方法,要是有朋友知道欢迎分享下。

通过Swing来绘制,应该是无奈的一步,不过通过这无奈的一步,和同事的帮助下,却学习到了不少这方面的知识,尤其是Swing绘制方面的。

下面的代码是我抽出来的一个比较通用的,基于TableModel的表格打印,普通表头,不带合成表头的,如果Table Model不一样,那么自己就需要改改了。

import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.math.BigDecimal;
import java.text.NumberFormat;
import javax.swing.JTable;
import javax.swing.table.TableModel;

/**
 *
 * @author xiaoquan
 */
public class SwingCommonPrinitTools implements Printable {

    private TableModel model = null;
    private String info;
    private int totalRow = 0;
    private static final int LEFT = 0;
    private static final int RIGHT = 1;
    private static final int CENTER = 2;
    private static final int AUTO = 3;

    public void printTable(TableModel model,
            String info) {
        this.model = model;
        this.info = info;
        totalRow = model.getRowCount();
        PrinterJob printJob = PrinterJob.getPrinterJob();
        printJob.setPrintable(this);
        if (printJob.printDialog()) {
            try {
                printJob.print();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    private static final double paper_offset_x = 20;
    private static final double paper_offset_y = 20;
    private static final double title_time_margin = 10;
    private static final double time_body_margin = 2;
    private static final double cell_padding_y = 3;
    private static final double cell_padding_x = 2;
    private static final double body_btm_margin = 20;
    private static final double body_cell_height = 20;
    private static final Font title_font = new Font("黑体", Font.PLAIN, 18);
    private static final Font time_font = new Font("Dialog", Font.PLAIN, 10);
    private static final Font body_font = new Font("Dialog", Font.PLAIN, 10);

    @Override
    public int print(Graphics g, PageFormat pf, int pageIndex) throws
            PrinterException {
        //纸张宽
        double pageWidth = pf.getImageableWidth();
        //纸张高
        double pageHeight = pf.getImageableHeight();
        //打印的内容起始X
        double pageStartX = pf.getImageableX();
        //打印的内容起始Y
        double pageStartY = pf.getImageableY();

        //表头高
        double tableHeadH = 0;
        //Cell高
        double cellH = 0;

        //计算表头高度和单元格高度
        g.setFont(body_font);
        FontMetrics cellFm = g.getFontMetrics();
        cellH = cellFm.getHeight() + cell_padding_y * 2 + 1;
        tableHeadH = cellH * 2;

        //计算Title以及其位置
        String title = info;
        g.setFont(title_font);
        FontMetrics titleFm = g.getFontMetrics();
        int titleW = titleFm.stringWidth(title);

        //表底和表头文字属性
        g.setFont(time_font);
        FontMetrics btmFm = g.getFontMetrics();
        FontMetrics timeFm = g.getFontMetrics();

        //表格以上的Margin
        double tableTopMargin = paper_offset_y + titleFm.getHeight() +
                title_time_margin + timeFm.getHeight() + time_body_margin;

        //表格每列的最大宽度
        double[] cellColMaxWidths = caculateTableCellWidth(model, cellFm);

        //当前Page的数据容量高度-不包括表头和表尾
        double currentPageDataCapacityHeight = pageHeight - tableTopMargin -
                tableHeadH - btmFm.getHeight() - body_btm_margin - 1;

        //当前Page的数据容量
        int currentPageBodyCapacityRows = (int) (currentPageDataCapacityHeight /
                cellH);

        //Y方向的分页数量
        int pagesY = 0;
        if (model.getRowCount() % currentPageBodyCapacityRows == 0) {
            pagesY = (int) (model.getRowCount() /
                    currentPageBodyCapacityRows);
        } else {
            pagesY = (int) (model.getRowCount() /
                    currentPageBodyCapacityRows) +
                    1;
        }


        //当前页数大于总页数时不打印
        if (pageIndex + 1 > pagesY) {
            return NO_SUCH_PAGE;
        }

        //绘制Title
        g.setFont(title_font);
        g.drawString(title, (int) (pageStartX +
                (pageWidth - titleW) / 2), (int) (pageStartY +
                paper_offset_y +
                titleFm.getAscent()));

        //绘制区域移动到新的(0,0)点
        g.translate((int) (paper_offset_x + pageStartX), (int) (tableTopMargin +
                pageStartY));
        int currentX = 0, currentY = 0;

        //绘制第一张表

        //绘制表头
        g.setFont(time_font);
        String time = "表头: " + info;
        g.drawString(time, currentX, currentY);
        currentY += 5;
        //绘制单一表头
        for (int i = 0; i < model.getColumnCount(); i++) {
            double width = cellColMaxWidths[i];
            double height = tableHeadH;
            String name = model.getColumnName(i);
            drawCell(g, name, currentX, currentY, (int) width,
                    (int) height, CENTER);
            currentX += width;
        }

        //绘制数据
        currentX = 0;
        currentY = (int) tableHeadH;
        //当前Page的数据容量
        int rightCellX = 0;
        int yIndex = pageIndex;
        int startRow = currentPageBodyCapacityRows * yIndex;
        int endRow = (currentPageBodyCapacityRows * (yIndex + 1)) >
                totalRow
                ? totalRow
                : (currentPageBodyCapacityRows * (yIndex + 1));
        for (int row = startRow; row < endRow; row++) {
            //绘制单项表头下面的数据
            for (int i = 0; i < model.getColumnCount(); i++) {
                double width = cellColMaxWidths[i];
                double height = body_cell_height;
                Object value = model.getValueAt(row, i);
                drawCell(g, value, currentX, currentY, (int) width,
                        (int) height, AUTO);
                currentX += width;
                rightCellX = currentX;
            }
            currentX = 0;
            currentY += cellH;
        }

        //绘制闭合线,下面和右侧两条
        g.drawLine(currentX, currentY, rightCellX, currentY);
        g.drawLine(rightCellX, 5, rightCellX, currentY);

        drawBottomInfo(pageIndex, pagesY, currentY, g, (int) pageWidth);
        return PAGE_EXISTS;
    }

    private void drawBottomInfo(int pageIndex, int pagesY,
            int currentY, Graphics g, int pageWidth) {
        if (pageIndex + 1 == pagesY) {
            //绘制底部信息
            int btmX = 0;
            int btmY = currentY + 20;
            g.drawString("负责人:", btmX, btmY);
            g.drawString("制表:", pageWidth / 3, btmY);
            FontMetrics fm = g.getFontMetrics();
            int dataWidth = fm.stringWidth("日期: 2009/10/26");
            g.drawString("日期:", pageWidth - dataWidth, btmY);
        }
    }

    /**
     * 计算最大列宽
     * @param cellFm
     * @return
     */
    private double[] caculateTableCellWidth(
            TableModel model,
            FontMetrics cellFm) {
        //表格每列的最大宽度
        double[] cellColMaxWidths = new double[model.getColumnCount()];

        //计算表头每列最大宽度
        double[] headerColMaxWidths = new double[model.getColumnCount()];

        for (int i = 0; i < model.getColumnCount(); i++) {
            String name = model.getColumnName(i);
            headerColMaxWidths[i] = cellFm.stringWidth(name) + cell_padding_x *
                    2 + 1;
        }
        //没有数据时,表头每列的最大宽度就是表格每列的最大宽度
        cellColMaxWidths = headerColMaxWidths;

        //算数据每列的最大宽度和表头每列最大宽度对比
        for (int j = 0; j < model.getRowCount(); j++) {
            for (int i = 0; i < model.getColumnCount(); i++) {
                //做些数据类型的判断
                Object value = model.getValueAt(j, i);
                if (value instanceof BigDecimal) {
                    value = ((BigDecimal) value).doubleValue();
                }
                String text = "";
                if (value != null) {
                    text = value.toString();
                }
                double temp = cellFm.stringWidth(text) + cell_padding_x * 2 + 1;
                if (cellColMaxWidths[i] < temp) {
                    cellColMaxWidths[i] = temp;
                }
            }
        }
        return cellColMaxWidths;
    }

    /**
     * 绘制单元格及里面的文字
     * @param g
     * @param value
     * @param x
     * @param y
     * @param width
     * @param height
     */
    private static void drawCell(Graphics g, Object value, int x, int y,
            int width,
            int height, int locate) {

        g.drawLine(x, y, x + width - 1, y);
        g.drawLine(x, y, x, y + height - 1);
        FontMetrics fm = g.getFontMetrics();
        if (value == null) {
            value = "";
        }
        switch (locate) {
            case 0:
                //居左
                g.drawString(value.toString(), (int) (x + cell_padding_x), y +
                        (height - fm.getHeight()) / 2 + fm.getAscent());
            case 1:
                //居右
                g.drawString(value.toString(),
                        (int) (x +
                        (width - fm.stringWidth(value.toString()) + width -
                        fm.stringWidth(value.toString()) - cell_padding_x) /
                        2), y +
                        (height - fm.getHeight()) / 2 + fm.getAscent());
            case 2:
                //居中
                g.drawString(value.toString(), x + (width - fm.stringWidth(
                        value.toString())) / 2, y + (height -
                        fm.getHeight()) / 2 + fm.getAscent());
            case 3:
                //自动判断
                NumberFormat formatter = NumberFormat.getNumberInstance();
                formatter.setMinimumFractionDigits(2);
                formatter.setMaximumFractionDigits(2);
                //根据数据类型左对齐还是右对齐绘制还是居中对齐
                if (value instanceof BigDecimal) {
                    //居右
                    value = ((BigDecimal) value).doubleValue();
                    value = formatter.format(value);
                    g.drawString(value.toString(),
                            (int) (x +
                            (width - fm.stringWidth(value.toString()) + width -
                            fm.stringWidth(value.toString()) - cell_padding_x) /
                            2), y +
                            (height - fm.getHeight()) / 2 + fm.getAscent());
                } else if (value instanceof Integer || value instanceof Long ||
                        value instanceof Double) {
                    //居右
                    g.drawString(value.toString(),
                            (int) (x +
                            (width - fm.stringWidth(value.toString()) + width -
                            fm.stringWidth(value.toString()) - cell_padding_x) /
                            2), y +
                            (height - fm.getHeight()) / 2 + fm.getAscent());
                } else {
                    //居中
                    g.drawString(value.toString(), x + (width - fm.stringWidth(
                            value.toString())) / 2, y + (height -
                            fm.getHeight()) / 2 + fm.getAscent());
                }
        }
    }

    public static void main(String[] args) {
        new SwingCommonPrinitTools().printTable(testData(), "测试");
    }

    private static TableModel testData() {
        final Object rows[][] = {
            {"one", "ichi - \u4E00", "Test1", "Test2", "Test3"},
            {"two", "ni - \u4E8C", "Test1", "Test2", "Test3"},
            {"three", "san - \u4E09", "Test1", "Test2", "Test3"},
            {"four", "shi - \u56DB", "Test1", "Test2", "Test3"},
            {"five", "go - \u4E94", "Test1", "Test2", "Test3"},
            {"six", "roku - \u516D", "Test1", "Test2", "Test3"},
            {"seven", "shichi - \u4E03", "Test1", "Test2", "Test3"},
            {"eight", "hachi - \u516B", "Test1", "Test2", "Test3"},
            {"nine", "kyu - \u4E5D", "Test1", "Test2", "Test3"},
            {"ten", "ju - \u5341", "Test1", "Test2", "Test3"},
            {"one", "ichi - \u4E00", "Test1", "Test2", "Test3"},
            {"two", "ni - \u4E8C", "Test1", "Test2", "Test3"},
            {"three", "san - \u4E09", "Test1", "Test2", "Test3"},
            {"four", "shi - \u56DB", "Test1", "Test2", "Test3"},
            {"five", "go - \u4E94", "Test1", "Test2", "Test3"},
            {"six", "roku - \u516D", "Test1", "Test2", "Test3"},
            {"seven", "shichi - \u4E03", "Test1", "Test2", "Test3"},
            {"eight", "hachi - \u516B", "Test1", "Test2", "Test3"},
            {"nine", "kyu - \u4E5D", "Test1", "Test2", "Test3"},
            {"ten", "ju - \u5341", "Test1", "Test2", "Test3"},
            {"one", "ichi - \u4E00", "Test1", "Test2", "Test3"},
            {"two", "ni - \u4E8C", "Test1", "Test2", "Test3"},
            {"three", "san - \u4E09", "Test1", "Test2", "Test3"},
            {"four", "shi - \u56DB", "Test1", "Test2", "Test3"},
            {"five", "go - \u4E94", "Test1", "Test2", "Test3"},
            {"six", "roku - \u516D", "Test1", "Test2", "Test3"},
            {"seven", "shichi - \u4E03", "Test1", "Test2", "Test3"},
            {"eight", "hachi - \u516B", "Test1", "Test2", "Test3"},
            {"nine", "kyu - \u4E5D", "Test1", "Test2", "Test3"},
            {"ten", "ju - \u5341", "Test1", "Test2", "Test3"},
            {"one", "ichi - \u4E00", "Test1", "Test2", "Test3"},
            {"two", "ni - \u4E8C", "Test1", "Test2", "Test3"},
            {"three", "san - \u4E09", "Test1", "Test2", "Test3"},
            {"four", "shi - \u56DB", "Test1", "Test2", "Test3"},
            {"five", "go - \u4E94", "Test1", "Test2", "Test3"},
            {"six", "roku - \u516D", "Test1", "Test2", "Test3"},
            {"seven", "shichi - \u4E03", "Test1", "Test2", "Test3"},
            {"eight", "hachi - \u516B", "Test1", "Tes12121t2", "Test3"},
            {"nine", "kyu - \u4E5D", "Test1", "Test2", "Test3"},
            {"ten", "ju - \u5341", "Test1", "Test2", "Test3"},
            {"one", "ichi - \u4E00", "Test1", "Test2", "Test3"},
            {"two", "ni - \u4E8C", "Test1", "Test2", "Test3"},
            {"three", "san - \u4E09", "Test1", "Test2", "Test3"},
            {"four", "shi - \u56DB", 12, "Test2", "Test3"},
            {"five", "go - \u4E94", 121212, "Test2", "Test3"},
            {"six", "roku - \u516D", 1212121212, "Test2", "Test3"},
            {"seven", "shichi - \u4E03", 12.01, "Test2", "Test3"},
            {"eight", "hachi - \u516B", 135.12, "Test2", "Test3"},
            {"nine", "kyu - \u4E5D", 93828.34, "Test2", "Test3"},
            {"ten", "ju - \u5341", "Test1", "Test2", "Test3"},
            {"one", "ichi - \u4E00", "Test1", "Test2", "Test3"},
            {"two", "ni - \u4E8C", "Test1", "Test2", "Test3"},
            {"three", "san - \u4E09", "Test1", "Test2", "Test3"},
            {"four", "shi - \u56DB", "Test1", "Test2", "Test3"},
            {"five", "go - \u4E94", "Test1", "Test2", "Test3"},
            {"six", "roku - \u516D", "Test1", "Test2", "Test3"},
            {"seven", "shichi - \u4E03", "Test1", "Test2", "Test3"},
            {"eight", "hachi - \u516B", "Test1", "Test2", "T1212121212est3"},
            {"nine", "kyu - \u4E5D", "Test1", "Test2", "Test3"},
            {"ten", "ju - \u5341", "Test1", "Test2", "Test3"},};
        final Object headers[] = {"English", "Japanese", "Column1", "Column2",
            "Column3"};
        JTable table = new JTable(rows, headers);
        return table.getModel();
    }
}