java標籤打印-斑馬打印機

java實現標籤打印-斑馬打印機

背景

之前項目中有一個功能-批量打印資產標籤,用戶在設備資產界面勾選資產,選擇打印樣式,然後打印到標籤紙上。花了兩個星期後終於把這個功能上線了,頭皮屑都掉了不知道多少

在這裏插入圖片描述

初步設計

這裏就說一下初步設計的思路。因需求可以總結爲兩種情況,一種是打印資產信息加條形碼(資產編碼生成),另外一種是資產信息加二維碼(資產編碼生成),一開始的設計簡單可以分爲三張表:模板、字段以及模板和字段的關聯關係表。前端利用html按照業務提供的標籤紙參數大小,把兩種情況的樣子畫好定爲樣式一、樣式二,類似如圖
在這裏插入圖片描述

然後新建模板,選擇樣式保存,然後在模板裏面添加字段,字段詳情裏面可以設置數據來源字段或者來源sql,最後可以點擊圖片中的名稱、編號等位置彈出彈框,在彈框中綁定字段。後端處理時利用圖片中位置所綁定的字段設置的數據來源字段或者來源sql去得到實際的數據替換html中對應位置的數據,二維碼或者條形碼是用實際數據(實際場景都是資產編碼這種)的base64編碼去替換html中的img的src。
這樣就得到實際的資產信息標籤的html了

然後把轉換後的html生成pdf導出

這些花了大概一週時間,pdf也導出了
(html轉pdf可以看我另一篇博客:
https://blog.csdn.net/hunan961/article/details/104901519
中間踩過的坑都有記錄)

後面去了客戶現場,纔看到了標籤打印機,之前一直都是雲開發。
然後打印機通過usb連上筆記本,下載驅動,點開之前生成過的pdf,右擊打印

…打印出來的完全對不上,整個標籤出來了半個外邊的框框,其他東西都沒有了,而且還橫跨了兩張標籤紙。

然後開始調試,調試代碼中紙張長寬,打印區域長寬,打印方向,打印機首選項中大小邊距方向等。調了一天也沒調出來,調到最後打印隊列裏面顯示已打印,但是實際上啥也沒有了。

這時我是猜測pdf是太大了,超過範圍就打不出東西,於是我拿標籤紙和模板裏的樣式對比了一下,真的大了好多,於是我把實際的長寬給了前端,前端反饋按照這個長寬樣式畫不出來,內容塞不下。。。後面我在代碼裏面嘗試設置縮放也沒弄出來
(生成的pdf大小都有一百多K,後面用第二種方式生成的pdf最多隻有十幾K)

着急的我第二天晚上在網上看到了斑馬打印機的指令語言zpl,於是去看了zpl指令文檔和一些類似的用zpl打印標籤的博客

後一天運行寫好的demo,利用驅動打印稍微調一調,就出來了像點樣子的標籤了。

這時候,我的思路就是使用zpl指令,把兩種樣式都定義好(後面用戶調成了四種),實際場景中直接用資產信息去替換指令中對應的位置就好了。

這時候我又找到了另一個神器–ZebraDesigner 2
用這個直接畫好後可以直接打印,也可以生成zpl指令文檔,完美,接下來就是在服務中完成代碼了。

zpl指令驅動打印

打印類:

/**
 * 斑馬打印機打印類-ZPL語音
 */
public class ZplPrinter {
    private PrintService printService = null;//打印機服務
    private String content = "";

    /*private String begin_1 = "\u0010CT~~CD,~CC^~CT~\n" +
            "^XA~TA000~JSN^LT0^MNW^MTT^PON^PMN^LH0,0^JMA^PR3,3~SD28^JUS^LRN^CI0^XZ\n" +
            "^XA\n" +
            "^MMT\n" +
            "^PW160\n" +
            "^LL0280\n" +
            "^LS0\n"; // 開始(個)*/

    private static final String begin = "\u0010CT~~CD,~CC^~CT~" +
            "^XA~TA000~JSN^LT0^MNW^MTT^PON^PMN^LH0,0^JMA^PR3,3~SD28^JUS^LRN^CI0^XZ" +
            "^XA" +
            "^MMT" +
            "^PW695" +
            "^LL0280" +
            "^LS0"; //開始(排)

//    private String logo_1 = "^FO64,0^GFA,01152,01152,00012,:Z64:\n" +
//            "eJztUjtOAzEQHcdYKRKxKbBERSiRaCgRi2D3Bou07nOMVMiIi6zEJaKsxHKUHINqzfy8QEWZBsu2np6eZ97MGOB//bVMaCZs+/2EQwhVximlwyQPoc3yYRjeFXvkg+L1CEWKGqb9zlB2cjgMBvfCmxGv+afyAM5JArsDWLyII4fSUAtfoJcUnyXtU8AnkngR+7c9lMLXZNNzBWsg+wUb8obKcsw/YAER5sxj5Im/RzcHsB/KmwYM87eId2A75fEIv+VYhjtEFXl6InrK2gnfYnThtzCCjSB69GMmfd+jWPXUzhy/SCnraQDV5OdxiCb79z/4C7TK/BnZ0TjnAHdAmYXH7bhXp6y30jfews+oLLqp/4amfiJziQkntpR5YZ8bCkUrlvh/bgRX9H907hscQNwIXhkf6kowGrp+FUgpnVNMVpaKqTUrxXDJG9SpqzKexasMf/3nY68vBLVZLg==:36A9\n";

    private static final String logo_1 = "^FO192,170^GFA,01536,01536,00012,:Z64:" +
            "eJztUjFOw0AQnLMTCoQciliiQrzANQWKzwV9Cm+fF/ACinsKT/FTTlSI4pTSBbJZ7+5Z+QEUWfmi0WhvbjK7wLX+uLbkV3yLjwzdEX3GhXxaG76RLzzwORk+HvkYPn0DdybTUe83ikvMcyjtVUdETvENmpQQBO/ZD3U+y7dziILvgZq8PvAEHOQX8mQPc8pWXoDnzO/l42KFxgw5L83KD9JfKe+I+dr4abrgOZ+t2A/sf8TOdNi/8kVAkVKQfo7G84XcH9GGnfKcZ732jzB97l+8a/+w/K1Vf/EifoqIalj9SPrCu4jyDDzatBxl/0xOUbSUJ6/5jCh5AO/C9xwndcq/oflM4UswgTy9Kt9iDihHwXXH8lvNvxm4t9K51MvcbYN25+XSoPNlCWd8kfQIz1xNisFptj+GOU7KC3eYpikaZpvrgrJ9dqS17A9yNSmu+HKfr/XP6xchOF9e:F887" +
            "^FO512,170^GFA,02048,02048,00016,:Z64:" +
            "eJztk0FOAzEMRX8aIhCtKIuOxBFYskQqEsMNBmm85xgsI3XJJVhyhFFn06PMEbqsVDTBCTR2uuIAY7WVvl5tf8cJMMUUqKjU9zOvpatNo/VV+kjccgWt3wCjCpiXGqZWevMFPKjyjl5jjVMsbBixEL1yRO2F6Dv0fTcT3bB/ckX7MFoxwK2JlAEmh/T7Z4fdNZAJo/U9MCjOLZB5rNypAbizKThS/qNwRwxb4fZ4OON8/quTvoENYcCTcMPzC78G5v1W8isY/kOR7zFKfsX7Q5Fv42w6P3rPfJmOR/dP3rP/uccahf+0/cwvPZY74D3zGq7V8+/4AHzqkTnvR85vwDKMZp95w+sjJ/vp0H9s7WfWrSFDldzQbzvyFEPWFNtXddZ9zF174fF+qhfwzO4RRMfSTvF59/vNnBm1oi1vL+xF83aI9AM6cnilI1eSx+cHoCLef635/vtCn7/fKab4V/wA4xhbmA==:B3EC" +
            "^FO352,170^GFA,01152,01152,00012,:Z64:" +
            "eJztUjFOxDAQHAeOO0WUFBQcUJ5S8QAKHsL9gZICiEnJK1JG+wpT0FNQXqR7wpVXEIXxrh3dA6DDsqPRaLw7GS/wv/50leInfObuMixadBkf67a14I184YYnJNy2PAm/fwHnqUwjnV8YPnHjiJPUtRCRwvAp1v3GGV7RjzQ+l69HZw2ugUq8NaDHJ/1CW3ZITql8BB4yv9INxAr3yVDhVWy8V/2F8YWQr4x3w/cBz3zKiOeg/x0uUx36N37G3femZzSeF7I+oDZ96ZlnlfVuB0z66N30Pv7WVD96UT+zgKWf/Gj6yh8FzLfArfItCsn+t3BD0FrGi9d82HXOB9gr3zFOaSy3DdYfvftULBAvb8a/uJGudoqrhuVLy3/9Su3S3qWK754m6IpuUBsuWaJI/GxjR3lylRh2TLPeG45xSh6452EYQsK0OQ0o7Y8Jxt+SjDk/YcKH8/wL6wd4H3r/:A9FE" +
            "^FO0,170^GFA,01536,01536,00012,:Z64:" +
            "eJztUjFOw0AQnDMkjixKCgoClJErHkDBQ8gfKCkAHy55hUtrX2EKegrKWMoTUqbAMnO7d1Z+gJCyurNGo7nd8dwBx/oXVYif8Lm7TzBr0CZ8qstqwRPpwC13F3HTcEf88Q1cxDa1tH5heO7GEfM4NRORzPAZ1v3GGV7Rj9Q+ta9GZwNugFK8DaDHZ/1CR7aITql8Ah4Tv9IFhA4P0VDmVWy8V/2l8ZmQL413w88Bz3yKgHPQ/w5XsQ/9Gz/j6nvTMxrPA0nfoTJ94ZlnmfRuB0z64N30PvzW1D94UT+zDks/+dH0lT/pkG+BO+UbZJL8b+GGTnsZL17z4dScF7BXvmWcUltuG6w/e/elWCBe3o1/dSNd7RSXNdsXlv/6jdql3UsZ7j2+oGu6QWW4YIss8rONbeXJlWLYMc1qbzjEKenBvQzD0EVMm9MDpf0xwvBbkjDfTzfhw/d8rD+rX2f5ev8=:D9E6";

    private static final String logo_2 = "^FO512,192^GFA,01152,01152,00012,:Z64:" +
            "eJztkUFKBDEQRX86E2agRUeYgOBGT6BLwU164T4DU3uP4BEis/Aa4zlmM0fppUcYbOg2oauq+waCWKvHS6iq/AB/rFZJ0dBOuYJVXsAEvQ7cCV9HrIXv+3I0VkO7hfB7911Je3hyzBZ22DO7PLiZxiak2VgefFH8aeTNzK+Kb0dem21AHPnGDAmv4onU43hMj+zzPtP9U63eBDP53Fx9ltrnCXiTfTYzXwMH8T4a9pdYfsmeHo4k0BrP3Z7f5fKeL+yXqa8+xDcRnr39POCW8ynP9ZwbzkAvTDD6kV2yZ/VbH4WvhqEVdkRB2OZ8hEtuWg/txC7gv36nfgDK2jZc:047B" +
            "^FO348,192^GFA,00768,00768,00008,:Z64:" +
            "eJzdkTFuwzAMRb8sCw6KwM4QrU2QOSg6dquHevcg7j1BzuCpvUZvkhzFYydn9ZT0k/IJOhXV8vAEipS+gL+zDhlBeuMKa+MGwbgHBmXXolV+zroFx/oNWVTX7xVZQtKWXKO5aYMtG5bWrkaxtHPkM92T/eJ7dQ5oQwfHAe/VDc5cknkxTbjQsXiFJ/OAMjuLzdt8HiPcRfv3ix9RDOqiJXjB7kvnJcSkDzziOjV63yASyUc/P+zIyGIh648BJ42n0zOAH+FnfX9C0MD8hGZUlzexfF7vd8srilh+Ne+f80056POQGX/xV/9l/QBSVjIV:871E" +
            "^FO160,192^GFA,01152,01152,00012,:Z64:" +
            "eJztkDFOw0AQRf/aCUQEYUfyNgjhlClTIkCKj7DFzl0oENqSY3CElCulSI7iY6RKGGBm7BNQIH719GzPjD/wl/M+oKfOeIm1cYfGOKFQdB5OuWh7fvSTCc/RQbN2/7EUrh2FILzG5qgLAnysbe0DZqO1E+ED+2tZO/aJ/Y14+AZT4dQecWGegvn5Lpfq3cjfYfANavP3gHmPYX6ZcKn38EXmscWVefr65Ds93pLeGUBBC92W+/ysPXiKUfil6levwpF7Vv94C5eF+Xed9lklVL3wlAvt1GdsknpaEDTn88mYKBo/7bIx92ZcDK/zRfjP7+UT49su6w==:8176" +
            "^FO0,192^GFA,00768,00768,00008,:Z64:" +
            "eJzVkbFuwjAQhn8npCCoSCrFC0IkIyNj1VZKHsGD7106IOSRx+ARGC0xwKPkMZhC7y7hCbq0Xj59lu/s+w38vXUcYKlV1tgpW5TKgERgLIwwqTreAiZ8Xgpm1fVUMwtDzjF3aO7SwMH6Qtu9Yza2mzBv7K/c7umBfckOWyJjhuqOF3Vy6otLTMXN6GsMXqJQ3wDqFkN9GjCV/nyDOs6Yq5McQYdDkPscyMmA5/Qav+S9lrxnfufdds/0PK/4xwomSjyl1AB5QN4xMx5Q5s8jGskjozfSvB6PXknklZ+XOObrlEk/5u5//3X/bv0AQ3wu6w==:13E8";

    private static final String end = "^PQ1,0,1,Y^XZ";

    private static final String BAR = "^BY2,3,37^FT${h},258^BCB,,Y,N" +
            "^FD>;${code}^FS";

    private static final String QR = "^FT${h1},100^BQN,2,4" +
            "^FH\\^FDLA,${code}^FS" +
            "^FT${h2},102^A0B,17,16^FH\\^FD${code}^FS";

    private static final String NUM = "${code}";
    private static final String H = "${h}";
    private static final String H1 = "${h1}";
    private static final String H2 = "${h2}";

    /*public static void main(String[] args) {
//        ZplPrinter p = new ZplPrinter("\\\\192.168.0.12\\ZDesigner 105SLPlus-300dpi ZPL");
        ZplPrinter p = new ZplPrinter("ZDesigner ZT210-200dpi ZPL");
        //1.打印單個條碼
        String bar0 = "7150000036";//條碼內容
//        String bar0Zpl = "^FO110,110^BY6,3.0,280^BCN,,Y,N,N^FD${data}^FS";//條碼樣式模板
//        p.setCharB("name", 20, 60, 30, 30);
//        p.setText("型號", 40, 60, 30, 30, 5, 1, 1, 24);
        p.setBegin(1);
        p.setText2Img("中中中中中中中中中1", 20, 0, 22, "宋體");
        p.setText2Img("中中中中中中中中中1", 48, 0, 22, "宋體");
        p.setText2Img("中中中中中中中中中1", 76, 0, 22, "宋體");
        p.setText2Img("中中中中中中中中中2", 194, 0, 22, "宋體");
        p.setText2Img("中中中中中中中中中2", 222, 0, 22, "宋體");
        p.setText2Img("中中中中中中中中中2", 250, 0, 22, "宋體");
        p.setText2Img("中中中中中中中中中3", 368, 0, 22, "宋體");
        p.setText2Img("中中中中中中中中中3", 396, 0, 22, "宋體");
        p.setText2Img("中中中中中中中中中3", 424, 0, 22, "宋體");
        p.setText2Img("中中中中中中中中中4", 542, 0, 22, "宋體");
        p.setText2Img("中中中中中中中中中4", 570, 0, 22, "宋體");
        p.setText2Img("中中中中中中中中中4", 598, 0, 22, "宋體");
        p.setLogo(1);
        p.setEnd(1);
//        p.setChinese("型號",50,60,30,30);
//        String bar0Zpl = "^FO102,0^BY2,3.0,40^BCB,,Y,N,N^FD${data}^FS";//條碼樣式模板
//        p.setBarcode(bar0, bar0Zpl);
        p.setMB();
        String zpl = p.getZpl();
        System.out.println(zpl);
        boolean result1 = p.print(zpl);//打印
        if (result1) {
            return;
        }

        p.resetZpl();//注意要清除上次的打印信息
    }*/

    /**
     * add LOGO
     *
     * @param i 樣式
     */
    public void setLogo(int i) {
        if (i == 1) {
            content = content + logo_1;
        } else if (i == 2){
            content = content + logo_2;
        }
    }

    /**
     * setEnd
     */
    public void setEnd() {
        content = content + end;
    }

    /**
     * setBegin
     */
    public void setBegin() {
        content = content + begin;
    }

    public void setText2Img(String str, int x, int y, int size, int style) {
        content += Font2ZplGF.getFontHex(bSubstring(str, 17), x, y, size, style);
    }

    /**
     * 構造方法
     *
     * @param printerURI 打印機路徑
     */
    public ZplPrinter(String printerURI) {
        if (printerURI != null) {
            //打印機完整路徑
            //初始化打印機
            PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
            if (services != null && services.length > 0) {
                for (PrintService service : services) {
                    if (printerURI.equals(service.getName())) {
                        printService = service;
                        break;
                    }
                }
            }
            if (printService == null) {
                //循環出所有的打印機
                if (services != null && services.length > 0) {
                    System.out.println("可用的打印機列表:");
                    for (PrintService service : services) {
                        System.out.println("[" + service.getName() + "]");
                    }
                }
                throw new RuntimeException("沒有找到打印機:[" + printerURI + "]");
            } else {
                System.out.println("找到打印機:[" + printerURI + "]");
                System.out.println("打印機名稱:[" + printService.getAttribute(PrinterName.class).getValue() + "]");
            }
        }
    }

    /**
     * 獲取完整的ZPL
     *
     * @return
     */
    public String getZpl() {
        return content;
    }

    /**
     * 重置ZPL指令,當需要打印多張紙的時候需要調用。
     */
    public void resetZpl() {
        content = "";
    }

    /**
     * 打印
     *
     * @param zpl      完整的ZPL
     * @param quantity
     */
    public boolean print(String zpl, Integer quantity) {
        if (printService == null) {
            return false;
        }
        byte[] by = zpl.getBytes();
        // 直連打印
        DocPrintJob job = printService.createPrintJob();
        DocFlavor flavor = DocFlavor.BYTE_ARRAY.AUTOSENSE;
        Doc doc = new SimpleDoc(by, flavor, null);
        try {
            job.print(doc, null);
            System.out.println("已打印\n" + zpl);
            return true;
        } catch (PrintException e) {
            e.printStackTrace();
            return false;
        }
        // 複雜打印
        /*PrinterJob job = PrinterJob.getPrinterJob();
        PDDocument document = null;
        try {
            document = PDDocument.load(by);
            job.setPrintService(printService);
            // 設置紙張屬性
            Paper paper = new Paper();
            double paperHigh = 250.56;
            double paperWidth = 100.8;
            Double marginLeft = 0.0;
            Double marginRight = 0.0;
            Double marginTop = 0.0;
            Double marginBottom = 0.0;
            // 設置紙張寬高 以1/72英寸爲單位 紙張寬250mm 則paperWidth爲 25cm/2.54*72
            paper.setSize(paperWidth, paperHigh);
            // 設置可成像區域
            paper.setImageableArea(marginLeft, marginTop, paperWidth - (marginLeft + marginRight), paperHigh - (marginTop + marginBottom));

            // 設置紙張及縮放
            PDFPrintable pdfPrintable = new PDFPrintable(document, Scaling.ACTUAL_SIZE);
            // 設置多頁打印
            Book book = new Book();
            PageFormat pageFormat = new PageFormat();
            // 設置打印方向 橫向
            pageFormat.setOrientation(PageFormat.LANDSCAPE);
            // 設置紙張
            pageFormat.setPaper(paper);
            book.append(pdfPrintable, pageFormat, document.getNumberOfPages());
            job.setPageable(book);
            // 設置打印份數
            job.setCopies(quantity);
            // 添加打印屬性
            HashPrintRequestAttributeSet pars = new HashPrintRequestAttributeSet();
            // 設置單雙頁
            pars.add(Sides.DUPLEX);
            job.print(pars);
            return true;
        } catch (Exception e) {
            return false;
        } finally {
            if (document != null) {
                try {
                    document.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }*/
    }

    /**
     * 條形碼
     *
     * @param assetNum
     * @param h
     */
    public void setBar(String assetNum, int h) {
        if (assetNum.length() == 13) {
            assetNum = assetNum.replace(String.valueOf(assetNum.charAt(12)), ">6" + assetNum.charAt(12));
        }
        content = content + BAR.replace(NUM, assetNum).replace(H, String.valueOf(h));
    }

    /**
     * 按字節截取
     *
     * @param s
     * @param length
     * @return
     * @throws Exception
     */
    public static String bSubstring(String s, int length) {
        if (s == null) {
            return "";
        }
        try {
            byte[] bytes = s.getBytes("Unicode");
            int n = 0; // 表示當前的字節數
            int i = 2; // 要截取的字節數,從第3個字節開始
            for (; i < bytes.length && n < length; i++) {
                // 奇數位置,如3、5、7等,爲UCS2編碼中兩個字節的第二個字節
                if (i % 2 == 1) {
                    n++; // 在UCS2第二個字節時n加1
                } else {
                    // 當UCS2編碼的第一個字節不等於0時,該UCS2字符爲漢字,一個漢字算兩個字節
                    if (bytes[i] != 0) {
                        n++;
                    }
                }
            }
            // 如果i爲奇數時,處理成偶數
            if (i % 2 == 1) {
                // 該UCS2字符是漢字時,去掉這個截一半的漢字
                if (bytes[i - 1] != 0)
                    i = i - 1;
                    // 該UCS2字符是字母或數字,則保留該字符
                else
                    i = i + 1;
            }
            String newStr = new String(bytes, 0, i, "Unicode");
            StringBuilder newS = new StringBuilder(newStr);
            if (getWordCount(newStr) < 17) {
                for (int j = 0; j < 17 - getWordCount(newStr); j++) {
                    newS.append(" ");
                }
            }
            return newS.toString();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 二維碼
     *
     * @param assetNum
     * @param x5
     * @param x6
     */
    public void setQr(String assetNum, int x5, int x6) {
        content = content + QR.replace(NUM, assetNum).replace(H1, String.valueOf(x5)).replace(H2, String.valueOf(x6)).replace(NUM, assetNum);
    }

    /**
     * 獲取字符串的字節長度
     *
     * @param s
     * @return
     */
    public static int getWordCount(String s) {
        int length = 0;
        for (int i = 0; i < s.length(); i++) {
            int ascii = Character.codePointAt(s, i);
            if (ascii >= 0 && ascii <= 255)
                length++;
            else
                length += 2;

        }
        return length;
    }
}

漢字轉zpl圖片類:

/**
 * 漢字轉zpl圖片
 */
public class Font2ZplGF {

    private static BufferedImage source = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
    private static Graphics2D gs = source.createGraphics();

    //	public static String getFontZpl(String content, int x, int y, int size, String fontName) {
    //		return String.format("^FO%d,%d^A1N,%d,%d^FD%s^FS", x, y, size, size, content);
    //	}

    public static String getFontHexWithWidth(String content, int x, int y, int width,
                                             int maxHeight, String fontName) {
        if (content == null || "".equals(content))
            return "";
        Font f = null;
        width = (width + 7) / 8 * 8;
        int size = width / content.length();
        int retryFlag = 1;
        if (size > maxHeight) {
            size = maxHeight;
            if ("宋體".equals(fontName)) {
                f = new Font("simsun", Font.PLAIN, size);
            } else if ("黑體".equals(fontName)) {
                f = new Font("simhei", Font.BOLD, size);
            } else {
                f = new Font("simsun", Font.PLAIN, size);
            }
        } else {
            while (true) {
                if ("宋體".equals(fontName)) {
                    f = new Font("simsun", Font.PLAIN, size);
                } else if ("黑體".equals(fontName)) {
                    f = new Font("simhei", Font.BOLD, size);
                } else {
                    f = new Font("simsun", Font.PLAIN, size);
                }
                gs.setFont(f);
                FontMetrics fontMetrics = gs.getFontMetrics();
                Rectangle2D stringBounds = fontMetrics.getStringBounds(content, gs);
                int nw = (int) stringBounds.getWidth();

                if (nw > width) {
                    size--;
                    if (retryFlag == 1) {
                        break;
                    }
                    retryFlag = 0;

                } else {
                    if (size >= maxHeight)
                        break;
                    size++;
                    retryFlag = 1;
                }
            }
        }
        int height = size;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = image.createGraphics();
        g2.setFont(f);
        g2.setColor(Color.BLACK);
        g2.drawString(content, 1, (int) (height * 0.88));

        g2.dispose();

        return "^FO" + x + "," + y +
                getImage(image) + "^FS";
    }

    public static String getFontHex(String content, int x, int y, int size, int style) {
        if (content == null || "".equals(content))
            return "";
        Font f = getSelfDefinedFont("font/SIMHEI.TTF", style, size);
//        f = new Font("simhei", Font.BOLD, size);

        gs.setFont(f);
        FontMetrics fontMetrics = gs.getFontMetrics();
        Rectangle2D stringBounds = fontMetrics.getStringBounds(content, gs);
        int width = (int) stringBounds.getWidth();
        width = (width + 7) / 8 * 8;
        int height = width;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
//        image = modifyImageRatio(image,270);

        Graphics2D g2 = image.createGraphics();

        g2.setFont(f);
        g2.setColor(Color.BLACK);
        // 圖片旋轉
        g2.rotate(Math.toRadians(270), width / 2, height / 2);
        g2.drawString(content, 1, (int) (size * 0.88));
        g2.dispose();

        StringBuilder zpl = new StringBuilder("^FO").append(x).append(",").append(y)
                .append(getImage(image)).append("^FS");

        return zpl.toString();

    }

    /**
     * 自定義字體
     * @param filepath 字體文件路徑
     * @param style 樣式
     * @param size 大小
     * @return
     */
    private static Font getSelfDefinedFont(String filepath, int style, float size){

        Font font = null;
        try{
            Resource resource = new ClassPathResource(filepath);
            font = Font.createFont(Font.TRUETYPE_FONT, resource.getInputStream());
            font = font.deriveFont(style, size);
        } catch (FontFormatException | IOException e){
            throw new RuntimeException(e);
        }
        return font;
    }

    /**
     * 旋轉圖片爲指定角度
     *
     * @param bufferedimage 目標圖像
     * @param degree        旋轉角度
     * @return
     */
    public static BufferedImage rotateImage(final BufferedImage bufferedimage,
                                            final int degree) {
        int w = bufferedimage.getWidth();
        int h = bufferedimage.getHeight();
        int type = bufferedimage.getColorModel().getTransparency();
        BufferedImage img;
        Graphics2D graphics2d;
        (graphics2d = (img = new BufferedImage(w, h, type))
                .createGraphics()).setRenderingHint(
                RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        graphics2d.rotate(Math.toRadians(degree), w / 2, h / 2);
        graphics2d.drawImage(bufferedimage, 0, 0, null);
        graphics2d.dispose();
        return img;
    }

    /**
     * @param mini  貼圖
     * @param ratio 旋轉角度
     * @return
     */
    public static BufferedImage modifyImageRatio(BufferedImage mini, int ratio) {
        int src_width = mini.getWidth();
        int src_height = mini.getHeight();
        //針對圖片旋轉重新計算圖的寬*高
        Rectangle rect_des = CalcRotatedSize(new Rectangle(new Dimension(
                src_width, src_height)), ratio);
        //設置生成圖片的寬*高,色彩度
        BufferedImage res = new BufferedImage(rect_des.width, rect_des.height, BufferedImage.TYPE_INT_RGB);
        //創建畫布
        Graphics2D g2 = res.createGraphics();
        res = g2.getDeviceConfiguration().createCompatibleImage(rect_des.width, rect_des.height, Transparency.TRANSLUCENT);
        g2 = res.createGraphics();
        //重新設定原點座標
        g2.translate((rect_des.width - src_width) / 2,
                (rect_des.height - src_height) / 2);
        //執行圖片旋轉,rotate裏包含了translate,並還原了原點座標
        g2.rotate(Math.toRadians(ratio), src_width / 2, src_height / 2);
        g2.drawImage(mini, null, null);
        g2.dispose();
        return res;
    }

    private static Rectangle CalcRotatedSize(Rectangle src, int angel) {
        if (angel >= 90) {
            if (angel / 90 % 2 == 1) {
                int temp = src.height;
                src.height = src.width;
                src.width = temp;
            }
            angel = angel % 90;
        }

        double r = Math.sqrt(src.height * src.height + src.width * src.width) / 2;
        double len = 2 * Math.sin(Math.toRadians(angel) / 2) * r;
        double angel_alpha = (Math.PI - Math.toRadians(angel)) / 2;
        double angel_dalta_width = Math.atan((double) src.height / src.width);
        double angel_dalta_height = Math.atan((double) src.width / src.height);

        int len_dalta_width = (int) (len * Math.cos(Math.PI - angel_alpha
                - angel_dalta_width));
        int len_dalta_height = (int) (len * Math.cos(Math.PI - angel_alpha
                - angel_dalta_height));
        int des_width = src.width + len_dalta_width * 2;
        int des_height = src.height + len_dalta_height * 2;
        return new Rectangle(new Dimension(des_width, des_height));
    }

    private static final char[] HEX = "0123456789ABCDEF".toCharArray();

    public static String printImage(BufferedImage image, int x, int y) {
        if (image.getWidth() % 8 != 0) {
            BufferedImage i = new BufferedImage(((image.getWidth() + 7) / 8) * 8,
                    image.getHeight(), BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2 = i.createGraphics();
            g2.drawImage(image, null, 0, 0);
            g2.dispose();
            image = i;
        }
        StringBuilder zpl = new StringBuilder("^FO").append(x).append(",").append(y)
                .append(getImage(image)).append("^FS");
        return zpl.toString();
    }

    private static String getImage(BufferedImage i) {
        int w = i.getWidth();
        int h = i.getHeight();
        boolean black[] = getBlackPixels(i, w, h);
        int hex[] = getHexValues(black);

        String data = ints2Hex(hex);

        int bytes = data.length() / 2;
        int perRow = bytes / h;

        return "^GFA," + bytes + "," + bytes + "," + perRow + "," + data;

    }


    private static String flipRows(String hex, int height) {
        String flipped = "";
        int width = hex.length() / height;

        for (int i = 0; i < height; i++) {
            flipped += new StringBuilder(hex.substring(i * width, (i + 1) * width)).reverse()
                    .toString();
        }
        return flipped;
    }

    /**
     * Returns an array of ones or zeros. boolean is used instead of int for memory considerations.
     *
     * @param bi
     * @param w
     * @param h
     * @return
     */
    private static boolean[] getBlackPixels(BufferedImage bi, int w, int h) {
        int[] rgbPixels = ((DataBufferInt) bi.getRaster().getDataBuffer()).getData();
        int i = 0;
        boolean[] pixels = new boolean[rgbPixels.length];
        for (int rgbpixel : rgbPixels) {
            pixels[i++] = isBlack(rgbpixel);
        }

        return pixels;
    }

    private static boolean isBlack(int rgbPixel) {
        int a = (rgbPixel & 0xFF000000) >>> 24;
        if (a < 127) {
            return false; // assume pixels that are less opaque than the luma threshold should be considered to be white
        }
        int r = (rgbPixel & 0xFF0000) >>> 16;
        int g = (rgbPixel & 0xFF00) >>> 8;
        int b = rgbPixel & 0xFF;
        int luma = ((r * 299) + (g * 587) + (b * 114)) / 1000; //luma formula
        return luma < 127;
    }

    private static int[] getHexValues(boolean[] black) {
        int[] hex = new int[(int) (black.length / 8)];
        // Convert every eight zero's to a full byte, in decimal
        for (int i = 0; i < hex.length; i++) {
            for (int k = 0; k < 8; k++) {
                hex[i] += (black[8 * i + k] ? 1 : 0) << 7 - k;
            }
        }
        return hex;
    }

    public static String getHexString(byte[] b) throws Exception {
        String result = "";
        for (int i = 0; i < b.length; i++) {
            result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1);
        }
        return result;
    }

    private static String ints2Hex(int[] ints) {
        char[] hexChars = new char[ints.length * 2];
        for (int i = 0; i < ints.length; ++i) {
            hexChars[i * 2] = HEX[(ints[i] & 0xF0) >> 4];
            hexChars[i * 2 + 1] = HEX[ints[i] & 0x0F];
        }
        return new String(hexChars);
    }
}

這中間主要的問題就是漢字問題

因爲打印機中沒有你設置的字體,所以打印出來的標籤漢字不會顯示,這裏也看了網上蠻多方法都沒有用,最後只好採取了漢字轉圖片打印圖片的方式

zpl指令轉pdf打印

本地驅動打印很ok,但是服務器不支持這種方式,新的問題來了。由於這種斑馬打印機不能聯網,根據實際需求也不會給打印機裝上很貴的網卡。

所以只有一條路了,回到當初,導出pdf,客戶自己本機打印pdf

zpl指令怎麼轉成pdf呢?

還好網上的資源給力:
http://labelary.com/service.html

這裏提供了接口供zpl指令轉成pdf,只需通過http調用就好了,經測試ok

/**
 * zpl指令轉pdf
 */
public class Zpl2Pdf {
    /*public static void main(String[] args) {
        File file = new File("label.pdf");

        String zpl = "CT~~CD,~CC^~CT~^XA~TA000~JSN^LT0^MNW^MTT^PON^PMN^LH0,0^JMA^PR3,3~SD28^JUS^LRN^CI0^XZ^XA^MMT^PW695^LL0280^LS0^FO352,160^GFA,01152,01152,00012,:Z64:eJztj7FtwzAQRT9pGRJYqUjgxgZdCq5TptAIatJnBBWC1RggRxFcETdFVsgmLl0IUOTk7qQNUiS/enwk/5HAf34nxwW3b/VKLxs1MuUIK7g5lCbKwiUIZz7UUlRYGqSoNOHeav0piW/xUj6pr1Do2Ffsdax68/DZyvODTPQjcvGO1GPXN+pBSc+Xu1F9AfXwWPVAez7W3kY4eY95XzwG9TeExrMfQIP41oRO/nW011QxX/xnc2GuXERiPj/Pd5hP310/8RH5jdnNYyNz3iHUzJZA4s00TZAQkXLfd8pWjz8u4K/kCwjBNFw=:F8AD^FO544,160^GFA,00768,00768,00008,:Z64:eJztkbFtwzAQRT/p0AoEB1CTLkBUGtwhAEdw4y6BV3Dh0gY4CkvhptAIaoJUKryBAri1Zd6XMoHbHAh8PH7e8XgE/mOOOMnr9g+5YYBK1QLPqqtDWNL3yapUp6tRrb1ETQhuPLdMl5TZtPjuAjmhZrkBe5YjO+Vq5nzBEocBb8qe/ISvPpBLifTdOJBrm+jvzeSv2WCBI6b8Errl8DKzjcpFWFz0SCah35prq/3EUlJS3v38Drnf5KOy6T7huszNWnOA4waFvs/npfwRcdL3lykXzPF+KW6cl0jD+fV9x3mJJOpuDFTbTHM2m4d/7oG4AwAhQRY=:C6CC^FO192,160^GFA,00768,00768,00008,:Z64:eJzlkTFqAzEQRf8oGyuocrFkUxjSBlcpU65v4Ca4tI/ggGFL6ygqlzmFr2AI5BoOBFyFzfwdN2kDqfwZ+DwN+oxGwNUpXPx1eUE/qCAt/Q7I9LcjpmNfeQQszu81PauSBc0w8evKgAniKjpnBlS4yXJgnHON+Isf5MM56MhT6XawAZ6gigK8YDXIlpwT+VkOtyAHA6uZnGBl/X7kGjvIhvnJ+3hcMj8h9My/Rzw7z9UmQCPDOhqHrCEZx8UWDecvhQmQzxZ7vrcfC/LFMs0ZaNof1yd6Ui302HWt70vHfcrw7XtOxb1q//Jb/6wfQRs9bQ==:060F^FO0,192^GFA,01152,01152,00012,:Z64:eJztjzFOAzEQRb83axFppTXFbkMRpYxygihIsEdwYV8HuUQUHCFyiahSRpti9yiUOQJCaIMRM+McASSmen4z+jMG/mhdZyxe44XOjSUK4Q6KUT0/YuSHdqlFMWaIS+JSe8tBcwyfG4lvHfsNbvq5+AalrF2glrXZx+SLC88HjfUOs0A+3cM+VMcX8cpbmccwiS/hZL7GKudAct5MyPmqg+Z7VMweVvwJD8GQt2g79gfcH/hfVnnXsK8+pjviRtuG/bYK6Ilb/Z31U+YJeCfW6Rz2V3uYSKxcWkw8m8J5JIb3nhG3x1649U64Op+EkceBNf7rl9UXFGw0lw==:0DFA^FT412,135^BQN,2,4^FH\\^FDLA,7150000036^FS^FT574,127^BQN,2,4^FH\\^FDLA,7150000036^FS^FT522,124^A0B,14,14^FH\\^FD1234567891123^FS^FT234,136^BQN,2,4^FH\\^FDLA,7150000036^FS^FT685,117^A0B,14,14^FH\\^FD1234567891123^FS^FT54,133^BQN,2,4^FH\\^FDLA,7150000036^FS^FT350,128^A0B,14,14^FH\\^FD1234567891123^FS^FT173,126^A0B,14,14^FH\\^FD1234567891123^FS^PQ1,0,1,Y^XZ";

        String url = "http://api.labelary.com/v1/printers/8dpmm/labels/3.42x1.37/0/";
        for (int i = 0; i < 2; i++) {
            getPdf(url,file,zpl);
            url = "http://api.labelary.com/v1/printers/8dpmm/labels/3.42x1.37/1/";
        }

    }*/
    public static List<InputStream> bitchGetPdf(String url, List<String> zpls) {
        return bitchSend(url,zpls);
    }

    private static List<InputStream> bitchSend(String url0, List<String> zpls) {
        List<InputStream> inputStreamList = new ArrayList<>(zpls.size());
        zpls.forEach(zpl -> inputStreamList.add(send(url0,zpl)));
        return inputStreamList;
    }

    public static InputStream getPdf(String url, String zpl) {
        return send(url,zpl);
        /*try {
            // 獲取http客戶端
            CloseableHttpClient client = HttpClients.createDefault();
            // get method
            HttpPost httpPost = new HttpPost(url);

//            URIBuilder uriBuilder = new URIBuilder(url);
            // 第一種添加參數的形式
//            uriBuilder.addParameter("name", "root");
//            uriBuilder.addParameter("password", "123456");
            // 第二種添加參數的形式
            List<NameValuePair> list = new LinkedList<>();
            BasicNameValuePair param1 = new BasicNameValuePair("mode", "raw");
            BasicNameValuePair param2 = new BasicNameValuePair("raw", zpl);
            list.add(param1);
            list.add(param2);

            httpPost.setEntity(new UrlEncodedFormEntity(list, "UTF-8"));
//            uriBuilder.setParameters(list);
            // 通過httpget方式來實現我們的get請求
//            HttpPost httpPost = new HttpPost(uriBuilder.build());

            // 傳輸的類型
            httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
//            httpPost.addHeader("Content-Length", String.valueOf(zpl.length()));
            httpPost.addHeader("Accept", "application/pdf");
            // 通過client調用execute方法,得到我們的執行結果就是一個response,所有的數據都封裝在response裏面了

            CloseableHttpResponse response = client.execute(httpPost);

            // HttpEntity
            // 是一箇中間的橋樑,在httpClient裏面,是連接我們的請求與響應的一箇中間橋樑,所有的請求參數都是通過HttpEntity攜帶過去的
            // 所有的響應的數據,也全部都是封裝在HttpEntity裏面
            if (response.getStatusLine().getStatusCode() == 200) {
                HttpEntity entity = response.getEntity();
                return entity.getContent();
            } else {
                throw new RuntimeException(response.getStatusLine().toString());
            }
        } catch (IOException ex) {
            ex.printStackTrace();
            throw new CommonException(ex);
        }*/
    }

    private static InputStream send(String url0, String param) {
        URL url;
        HttpURLConnection connection;
        try {
            //創建連接
            url = new URL(url0);
            connection = (HttpURLConnection) url.openConnection();
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setRequestMethod("POST");
            connection.setUseCaches(false);
            connection.setInstanceFollowRedirects(true);
            connection.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
            connection.setRequestProperty("Connection", "Keep-Alive");
            connection.setRequestProperty("Accept","application/pdf");
            connection.setConnectTimeout(30000);
            connection.setReadTimeout(30000);
            //POST請求
            DataOutputStream out = new DataOutputStream(connection.getOutputStream());
            JSONObject body = new JSONObject();
            body.put("mode", "raw");
            body.put("raw", param);

            out.write(body.toString().getBytes("UTF-8"));
            out.flush();
            out.close();
            return connection.getInputStream();
            //讀取響應
            /*BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String lines;
            StringBuffer sb = new StringBuffer("");
            while ((lines = reader.readLine()) != null) {
                lines = new String(lines.getBytes(), "utf-8");
                sb.append(lines);
            }
            System.out.println(sb);
            reader.close();*/
        } catch (IOException e) {
            throw new RuntimeException("網絡超時,請重試:" + e.toString());
        }
    }

    public static String convertStreamToString(InputStream is) {
        StringBuilder sb1 = new StringBuilder();
        byte[] bytes = new byte[4096];
        int size = 0;

        try {
            while ((size = is.read(bytes)) > 0) {
                String str = new String(bytes, 0, size, "UTF-8");
                sb1.append(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb1.toString();
    }

    private static byte[] byteMergerAll(byte[]... values) {
        int length_byte = 0;
        for (byte[] value : values) {
            length_byte += value.length;
        }
        byte[] all_byte = new byte[length_byte];
        int countLength = 0;
        for (byte[] b : values) {
            System.arraycopy(b, 0, all_byte, countLength, b.length);
            countLength += b.length;
        }
        return all_byte;
    }

}

本來想着提高效率通過list的並行流去http請求,但是沒想到會報429,於是又改回了正常的循環。

最後,由於我這邊是根據4個資產生成一個完成的zpl指令,然後得到一個pdf,實際場景中可能會選擇許多資產,這樣就會生成許多PDF,存在一個pdf合併問題。

這裏可以參考我的另外一邊博客:
https://blog.csdn.net/hunan961/article/details/104903890

最後導出來的pdf非常完美,打印的效果也非常棒。

操作效果可以看我提供給用戶的操作文檔:
http://note.youdao.com/noteshare?id=9a49420d5b614409eea821a36ae6e064&sub=D8B629F5AD634DC0A4C6A964549A38CD

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章