Menyimpan Template simple-escp Di Database

Ada beberapa pengguna aplikasi yang sering kali ingin mengubah hasil percetakan di printer dari waktu ke waktu. Bila kode program percetakan disimpan di dalam aplikasi, ini berarti saya harus mengubah kode program setiap kali ada perubahan layout. Hal ini lama-lama bisa merepotkan! Oleh sebab itu, pada artikel Memakai simple-escp Di Griffon, saya mendefinisikan template percetakan dalam bentuk file JSON. Setiap kali ada yang ingin merubah layout percetakan, mereka bisa meng-edit file JSON ini sendiri tanpa harus menunggu saya.

Solusi di atas bekerja dengan baik bila template laporan terletak di server yang dipakai bersama (misalnya aplikasi web). Bagaimana dengan aplikasi desktop yang dikembangkan dengan menggunakan Griffon dan simple-jpa? Saya dapat menyimpan template JSON untuk simple-escp ke dalam database. Dengan demikian, perubahan template yang dibuat oleh satu pengguna tetap dapat dilihat dan dipakai oleh pengguna lainnya.

Sebagai contoh, saya bisa membuat sebuah entity untuk mewakili template simple-escp seperti berikut ini:

@DomainClass @Entity @Canonical
class TemplateFaktur {

    @NotBlank
    String nama

    @Lob
    String isi

}

Saya memakai annotation @Lob pada field isi untuk menandakan bahwa kolom tersebut dapat di-isi dengan banyak karaketer (large object type). Pada Hibernate JPA dan database MySQL Server, kombinasi ini akan menghasilkan field dengan tipe longtext yang dapat menampung hingga maksimum 4 GB karakter (bandingkan dengan VARCHAR yang menampung maksimal 255 karakter).

Untuk mencetak template simple-escp berdasarkan TemplateFaktur yang sudah disimpan di database, saya dapat menggunakan kode program seperti berikut ini:

TemplateFaktur template = findTemplateFakturByNama(model.nama)
JsonTemplate template = new JsonTemplate(template.isi)
PrintPreviewPane printPreviewPane = view.printPreviewPane
printPreviewPane.display(template, DataSources.from(model.dataSource, model.options))

Sekarang, saya perlu membuat sebuah MVC untuk mengedit template yang ada. Agar pengguna bisa lebih nyaman dalam meng-edit template, saya akan menggunakan groovy.ui.ConsoleTextEditor bawaan Groovy. ConsoleTextEditor mengandung sebuah JTextPane yang dilengkapi dengan syntax highlighting sehingga sangat berguna untuk menampilkan dokumen teks yang memiliki syntax seperti bahasa pemograman. Sebagai contoh, saya bisa mendefinisikan view seperti berikut ini:

actions {
    action(id: 'cari', name: 'Cari', closure: controller.cari)
    action(id: 'simpan', name: 'Simpan', closure: controller.simpan)
    action(id: 'reset', name: 'Reset', closure: controller.reset)
}

panel(id: 'mainPanel') {
    borderLayout()

    panel(constraints:PAGE_START) {
        flowLayout(alignment: FlowLayout.LEFT)
        comboBox(id: 'namaTemplateFaktur', model: model.namaTemplateFaktur)
        button(action: cari)
    }

    widget(new ConsoleTextEditor(), id: 'inputEditor', constraints: CENTER)

    panel(constraints: PAGE_END) {
        flowLayout(alignment: FlowLayout.LEFT)
        button(action: simpan)
        button(action: reset)
    }
}

Untuk menampilkan template dari database untuk di-edit, saya dapat menggunakan kode program seperti berikut ini di controller:

def cari = {
    execInsideUISync {
        String namaTemplateFaktur = model.namaTemplateFaktur.selectedItem
        if (namaTemplateFaktur) {
            String isi = findTemplateFakturBy(namaTemplateFaktur)?.isi
            TextEditor textEditor = view.inputEditor.textEditor
            DefaultStyledDocument doc = new DefaultStyledDocument()
            doc.setDocumentFilter(new SimpleEscpFilter(doc))
            doc.insertString(0, isi?: '', null)
            textEditor.setDocument(doc)
            textEditor.caretPosition = 0
        }
    }
}

Secara default, ConsoleTextEditor akan melakukan syntax highlighting berdasarkan format Groovy. Karena simple-escp memakai format yang berbeda, saya akan mendefinisikan sebuah DocumentFilter baru yang saya sebut sebagai SimpleEscpFilter yang isinya seperti berikut ini:

class SimpleEscpFilter extends StructuredSyntaxDocumentFilter {

    public static final String VARIABLES = /(?ms:${.*?})/
    public static final String FUNCTIONS = /(?ms:%{.*?})/
    public static final String CODE = /(?ms:{{.*?}})/

    SimpleEscpFilter(DefaultStyledDocument document) {
        super(document)

        StyleContext styleContext = StyleContext.getDefaultStyleContext()
        Style defaultStyle = styleContext.getStyle(StyleContext.DEFAULT_STYLE)

        Style variables = styleContext.addStyle(VARIABLES, defaultStyle)
        StyleConstants.setForeground(variables, Color.GREEN.darker().darker())
        getRootNode().putStyle(VARIABLES, variables)

        Style functions = styleContext.addStyle(FUNCTIONS, defaultStyle)
        StyleConstants.setForeground(functions, Color.BLUE.darker().darker())
        getRootNode().putStyle(FUNCTIONS, functions)

        Style code = styleContext.addStyle(CODE, defaultStyle)
        StyleConstants.setForeground(code, Color.MAGENTA.darker().darker())
        getRootNode().putStyle(CODE, code)
    }

}

StructuredSyntaxDocumentFilter adalah DocumentFilter bawaan Groovy yang melakukan syntax highlighting berdasarkan ekspresi Regex. Pada implementasi di atas, saya memberikan pewarnaan yang berbeda untuk setiap komponen dalam template simple-escp. Bila saya menjalankan program, saya akan memperoleh hasil seperti pada gambar berikut ini:

Tampilan editor

Tampilan editor

Bila pengguna men-klik tombol simpan, saya dapat menyimpan perubahan dengan kode program seperti berikut ini:

def simpan = {
    TemplateFaktur templateFaktur = findTemplateFakturByNama(namaTemplateFaktur)
    if (!templateFaktur) {
       templateFaktur = new TemplateFaktur(nama: model.namaTemplateFaktur.selectedItem)
       persist(templateFaktur)
    }
    templateFaktur.isi = view.inputEditor.textEditor.text
}

Sekarang, saya tidak perlu khawatir lagi harus men-deploy ulang aplikasi hanya karena perubahan kecil di layout percetakan. Bila pengguna tidak memiliki tim IT yang memahami syntax simple-escp, setidaknya saya masih bisa mengirim email berisi template yang sudah dimodifikasi untuk di-copy paste oleh mereka. Ini masih jauh lebih baik daripada harus men-deploy ulang aplikasi 😀

Iklan

Memakai simple-escp Pada Aplikasi Web

Pengguna bisa mencetak melalui browser dengan memilih menu File, Print.. di browser atau melalui JavaScript window.print(). Percetakan yang dilakukan dengan cara seperti ini adalah percetakan graphic mode. Bagaimana bila yang diinginkan adalah percetakan text mode? Pada percetakan text mode, posisi bisa ditentukan secara lebih akurat dan pengaturan halaman dapat dilakukan dalam satuan baris dan karakter. simple-escp adalah salah satu library Java yang dapat dipakai untuk keperluan ini. Untuk memakai simple-escp pada aplikasi web, saya dapat menyertakannya sebagai applet. Applet adalah kode program Java yang akan dikerjakan di sisi client di browser (sama seperti Adobe Flash, Microsoft Silverlight, ActiveX, dan sebagainya). Salah satu fitur andalan applet adalah kode program JavaScript di halaman HTML yang sama dapat dipakai untuk memanipulasi applet. Begitu juga sebaliknya, applet juga dapat memanggil kode program JavaScript yang dideklarasikan pada halaman HTML yang sama.

Untuk membuat applet, saya dapat menggunakan framework Griffon. Saya akan mulai dengan membuat proyek baru dengan memberikan perintah:

griffon create-app simple-escp-applet

Karena applet ini akan memanggil simple-escp, saya perlu mengubah bagian griffon.project.dependency.resolution dari griffon-app/conf/BuildConfig.groovy menjadi seperti berikut ini:

griffon.project.dependency.resolution = {
    // inherit Griffon' default dependencies
    inherits("global") {
    }
    log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
    repositories {
        griffonHome()
        mavenRepo "http://dl.bintray.com/jockihendry/maven"
    }
    dependencies {
        compile 'jockihendry:simple-escp:0.4'
    }
}

Saya juga perlu mengubah bagian webstart agar nilai codebase merujuk ke lokasi URL di server nanti, seperti pada contoh berikut ini:

production {
    ...
    griffon {
       ...
       webstart {
          codebase = "http://localhost/cetak"
       }
    }
}

Selain itu, saya akan memanggil JavaScript yang ada di HTML melalui Applet melalui netscape.javascript.JSObject. Class ini terletak pada plugin.jar dan tidak tersedia pada saat kompilasi. Untuk itu, saya men-copy file tersebut dari C:\Program Files\Java\jdk\jre\lib ke folder lib di proyek Griffon.

Karena saya perlu melakukan signing pada applet yang dihasilkan Griffon, maka saya mengubah bagian signingkey di environment production menjadi seperti berikut ini:

production {
   signingkey {
      params {                
         storepass = 'thesolidsnake'
         keypass = 'thesolidsnake'
         lazy = false // sign, regardless of existing signatures
      }
   }
   ...
}

Sekarang, saya siap untuk membuat kode program. Satu hal yang menjadi kendala adalah DataSource. Saat ini simple-escp menerima DataSource dalam bentuk Map atau JavaBean object yang merupakan tipe data Java. Kedua data source tersebut tidak dapat dipakai di JavaScript. Untuk itu, saya perlu membuat DataSource baru yang bisa membaca data dalam bentuk object JavaScript di halaman HTML yang sama. simple-escp memungkinkan pengguna membuat implementasi DataSource sendiri seperti yang terlihat pada kode program berikut ini (saya meletakkannya di src\main\datasource\JSONDataSource.groovy):

package datasource;

import simple.escp.data.DataSource;
import simple.escp.exception.InvalidPlaceholder;
import javax.json.Json;
import javax.json.JsonNumber;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonValue;
import java.io.StringReader;

public class JSONDataSource implements DataSource {

    private String jsonString;
    private JsonObject json;

    public JSONDataSource(String jsonString) {
        this.jsonString = jsonString;
        try (JsonReader reader = Json.createReader(new StringReader(jsonString))) {
            json = reader.readObject();
        }
    }

    @Override
    public boolean has(String s) {
        return json.containsKey(s);
    }

    @Override
    public Object get(String member) throws InvalidPlaceholder {
        JsonValue value = json.get(member);
        if (value.getValueType() == JsonValue.ValueType.ARRAY) {
            return value;
        } else if (value.getValueType() == JsonValue.ValueType.NUMBER) {
            return ((JsonNumber) value).bigDecimalValue();
        } else {
            return value.toString();
        }
    }

    @Override
    public Object getSource() {
        return jsonString;
    }

    @Override
    public String[] getMembers() {
        return json.keySet().toArray(new String[0]);
    }
}

Saya kemudian mengubah isi dari SimpleEscpAppletView.groovy menjadi seperti berikut ini:

package simple.escp.applet

import java.awt.BorderLayout

application() {

    panel(id: 'mainPanel', layout: new BorderLayout()) {

    }

}

Sebagai langkah terakhir dalam membuat applet, saya mengubah kode program SimpleEscpAppletController.groovy menjadi seperti berikut ini:

package simple.escp.applet

import datasource.JSONDataSource
import simple.escp.fill.FillJob
import simple.escp.json.JsonTemplate
import simple.escp.swing.PrintPreviewPane
import sun.plugin.javascript.JSObject
import javax.swing.JPanel
import java.awt.BorderLayout

class SimpleEscpAppletController {

    def model
    def view

    void mvcGroupInit(Map args) {
        def window = JSObject.getWindow(app)
        def report = new JsonTemplate(window.eval("JSON.stringify(template);")).parse()
        def dataSource = window.eval("JSON.stringify(source);");
        def result = new FillJob(report, new JSONDataSource(dataSource)).fill()
        PrintPreviewPane pane = new PrintPreviewPane(result,
            report.pageFormat.pageLength, report.pageFormat.pageWidth)
        JPanel mainPanel = view.mainPanel
        mainPanel.add(pane, BorderLayout.CENTER)
    }

}

Untuk menghasilkan applet, saya memberikan perintah berikut ini:

griffon package applet

Griffon secara otomatis akan menghasilkan file distribusi applet pada folder dist/applet. Pada folder ini, selain file JAR, saya juga akan menemukan file applet.jnlp yang dapat dipakai untuk memanggil applet di HTML. Seluruh file JAR juga sudah di-sign dengan key yang di-generate untuk keperluan sementara. Saya juga dapat menemukan file applet.html yang berisi contoh pemanggilan applet.

Berikutnya, saya memindahkan seluruh file JAR (dan juga versi yang sudah di-compress dalam bentuk .jar.pack.gz), file gambar, file JNLP dan file HTML ke lokasi deployment di webserver. Sebagai contoh, karena saya memakai NGINX, saya akan memindahkan file tersebut ke folder html/cetak. Saya kemudian mengubah file applet.html menjadi seperti berikut ini:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>Latihan Cetak</title>
    <script>
    var template = {
        pageFormat: {
            pageLength: 10,
            pageWidth: 50,
            usePageLengthFromPrinter: false
        },
        template: {
            header: [" PT. XYZ                                    HAL: %{PAGE_NO}"],
            detail: [
                " Nomor Faktur: ${nomorFaktur:20}",
                {
                    "table": "listItemFaktur",
                    "border": true,
                    "columns": [
                        { caption: "Nama Barang", source: "namaBarang", width: 25, wrap: true },
                        { caption: "Qty", source: "qty::right", width: 5 },
                        { caption: "Harga", source: "harga.bigDecimalValue()::right::number", width: 15 }
                    ]
                }
            ]
        }
    };

    var source = {
        nomorFaktur: 'FA-1234-556677-XX-BB-CC',
        listItemFaktur: [
            {namaBarang: 'Plantronics Backbet Go 2 With Charging Case', qty: 1, harga: 13750000},
            {namaBarang: 'CORT Gitar Akustik AD810 - Natural Satin', qty: 1, harga: 14900000},
            {namaBarang: 'SAMSON Monitor Speaker System MediaOne 3A', qty: 1, harga: 14250000}
        ]
    };

  </script>
</head>
<body>

<h1>Latihan Cetak</h1>

<p>Halaman ini akan memanggil applet yang mencetak ke printer dot matrix dengan bantuan JavaScript.</p>

<APPLET CODEBASE='http://localhost/cetak'
        CODE='griffon.swing.SwingApplet'
        ARCHIVE='griffon-swing-runtime-1.4.0.jar,griffon-rt-1.5.0.jar,groovy-all-2.2.1.jar,javax.json-1.0.4.jar,javax.json-api-1.0.jar,jcl-over-slf4j-1.7.5.jar,jul-to-slf4j-1.7.5.jar,log4j-1.2.17.jar,plugin.jar,simple-escp-0.4.jar,simple-escp-applet-0.1.jar,slf4j-api-1.7.5.jar,slf4j-log4j12-1.7.5.jar'
        WIDTH='800' HEIGHT='500'>
    <PARAM NAME="java_arguments" VALUE="-Djnlp.packEnabled=true">
    <PARAM NAME='jnlp_href' VALUE='http://localhost/cetak/applet.jnlp'>
    <PARAM NAME='dragggable' VALUE='true'>
    <PARAM NAME='image' VALUE='griffon.png'>
    <PARAM NAME='boxmessage' VALUE='Loading Simple-escp-applet'>
    <PARAM NAME='boxbgcolor' VALUE='#FFFFFF'>
    <PARAM NAME='boxfgcolor' VALUE='#000000'>
    <PARAM NAME='codebase_lookup' VALUE='false'>
</APPLET>

</body>
</html>

Pada HTML di atas, saya mendeklarasikan dua variabel JavaScript yang wajib ada, yaitu variabel template dan source. Kedua variabel ini akan dibaca oleh applet dan masing-masing diterjemahkan menjadi JsonTemplate dan JSONDataSource. Pada kasus yang lebih kompleks, ini variabel source biasanya akan dihasilkan oleh sisi server. Sebagai contoh, bila bahasa pemograman di sisi server adalah PHP, saya dapat menggunakan function json_encode() untuk menghasilkan object JavaScript dari array atau object PHP yang mengimplementasikan JsonSerializable.

Sekarang, saya akan mencoba membuka halaman http://localhost/cetak/applet.html melalui browser pada sistem operasi yang telah memiliki instalasi Java. Saya menemukan dialog peringatan seperti berikut ini:

Peringatan di browser pada saat menjalankan applet

Peringatan di browser pada saat menjalankan applet

Karena saya memang ingin mengaktifkan Java di browser, maka saya memilih Later. Setelah itu, akan kembali muncul pesan peringatan lainnya seperti pada gambar berikut ini:

Peringatan saat menjalankan applet yang tidak sertifikatnya tidak diverifikasi

Peringatan saat menjalankan applet yang tidak sertifikatnya tidak diverifikasi

Pesan peringatan ini muncul karena JAR saya di-sign sendiri tanpa verifikasi dari pihak terpercaya seperti Verisign, GoDaddy dan sejenisnya (yang memungut biaya verifikasi). Pesan keamanan ini diberikan karena applet yang di-sign oleh publisher tak dikenal memiliki akses yang lebih leluasa dibandingkan applet yang tidak di-sign sama sekali, misalnya baca/tulis file serta melakukan percetakan.

Saya memberi tanda centang pada I accept the risk and want to run this application. dan men-klik tombol Run. Tampilan browser saya akan terlihat seperti pada gambar berikut ini:

Tampilan di browser

Tampilan di browser

Bila saya men-klik tombol Print dari PrintPreviewPane milik simple-escp, maka percetakan akan dilakukan pada text mode ke printer dot matrix yang sedang terpilih.

Memakai simple-escp Di Griffon

Sebagai latihan, saya akan mulai dengan membuat proyek Griffon baru dengan memberikan perintah:

griffon create-app latihan

Untuk menambahkan library simple-jpa, saya membuka file griffon-app\conf\BuildConfig.groovy dan mengubah bagian griffon.project.dependency.resolution sehingga menjadi seperti yang terlihat berikut ini:

griffon.project.dependency.resolution = {
    // inherit Griffon' default dependencies
    inherits("global") {
    }
    log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
    repositories {
        griffonHome()
        mavenRepo "http://dl.bintray.com/jockihendry/maven"
    }
    dependencies {
        compile 'jockihendry:simple-escp:0.4'
    }
}

Binary simple-escp telah tersedia di Bintray sehingga dapat di-download oleh Griffon secara otomatis.

Agar lebih nyaman membuat kode program, saya akan menghasilkan proyek IntelliJ IDEA dengan memberikan perintah berikut ini:

griffon integrate-with --idea

Sekarang, saya bisa membuka proyek Griffon tersebut di IntelliJ IDEA. Saya kemudian menambahkan file template JSON di lokasi griffon-app\resources dengan nama template.json yang isinya seperti berikut ini:

{
     "pageFormat": {
         "pageLength": 10,
         "pageWidth": 50,
         "usePageLengthFromPrinter": false
     },
     "template": {
         "header": [ "  PT. XYZ                              HAL: %{PAGE_NO}" ],
         "detail": [
             " Nomor Faktur: ${nomorFaktur:20}",
             {
                "table": "listItemFaktur",
                "border": true,
                "columns": [
                    { "caption": "Nama Barang", "source": "namaBarang", "width": 25, "wrap": true },
                    { "caption": "Qty", "source": "qty::right", "width": 5 },
                    { "caption": "Harga", "source": "harga::right::number", "width": 15 }
                ]
             }
         ]
     }
 }

Berikutnya, saya mengubah file griffon-app\views\latihan\LatihanController.groovy sehingga menjadi seperti berikut ini:

package latihan

import simple.escp.Template
import simple.escp.json.JsonTemplate
import simple.escp.swing.PrintPreviewPane
import javax.swing.JFrame

class LatihanController {

    def model
    def view

    void mvcGroupInit(Map args) {
        def source = [nomorFaktur: 'FA-1234-556677-XX-BB-CC']
        source['listItemFaktur'] = [
            [namaBarang: 'Plantronics Backbeat Go 2 With Charging Case', qty: 1, harga: 13750000],
            [namaBarang: 'CORT Gitar Akustik AD810 - Natural Satin', qty: 1, harga: 14900000],
            [namaBarang: 'SAMSON Monitor Speaker System MediaOne 3A', qty: 1, harga: 14250000]
        ]
        Template template = new JsonTemplate(app.getResourceAsStream("template.json"))
        PrintPreviewPane previewPane = new PrintPreviewPane(template, source, null)
        JFrame startingFrame = app.windowManager.getStartingWindow()
        startingFrame.contentPane.removeAll()
        startingFrame.add(previewPane)
    }

}

Bila saya menjalankan proyek dengan memberikan perintah:

griffon run-app

Saya akan memperoleh hasil seperti pada gambar berikut ini:

Tampilan saat aplikasi dijalankan

Tampilan saat aplikasi dijalankan

Mencetak Ke Printer Dot-Matrix Di Java Dengan simple-escp

Salah satu teknologi lama yang masih bertahan sampai saat ini adalah printer dot matrix. Salah satu alasan utama
penggunaan printer tersebut adalah untuk mencetak pada kertas karbon yang secara otomatis membuat duplikasi ke beberapa halaman berbeda. Berdasarkan data dari Wikipedia, hingga tahun 2013, hanya ada 1 perusahaan di Canada, 1 perusahaan di United Kingdom, dan 2 perusahaan di Amerika yang masih memproduksi kertas karbon. Di India (dan juga Indonesia), penggunaan kertas karbon untuk mengisi formulir masih sering dijumpai. Teknologi kertas karbon sebenarnya sudah kalah bersaing menghadapi mesin fotokopi (photocopying), printer laser yang semakin murah dan penggunaan formulir elektronik.

Printer dot matrix mencetak karakter berdasarkan font internal (dalam RAM printer) dan bekerja dengan sangat baik di DOS. Hal ini karana karakter ganti baris di DOS yang berupa 2 karakter, CR + LF, sehingga file DOS dapat langsung dicetak ke printer (saat itu belum ada driver terpisah untuk masing-masing printer). Tradisi ini juga dibawa ke sistem operasi Windows (bandingkan dengan Linux dan Unix yang hanya memakai karakter LF untuk ganti baris).

Seiring dengan perkembangan printer inkjet yang dapat mencetak piksel secara fleksibel dan lebih sunyi, sistem operasi modern mulai beralih mendukung mereka. Printer inkjet memiliki bahasa percetakan yang lebih mudah distandarisasikan. Sebagai contoh, sistem operasi bisa memakai font di sisi software yang berlaku untuk semua printer (di Windows, ini bisa dijumpai di folder C:\Windows\Fonts). Dukungan software pun beralih meninggalkan dot matrix dengan asumsi bahwa teknologi dot matrix akan mati dengan sendirinya.

Sampai sekarang, masih ada produsen yang tetap memproduksi printer dot matrix dan memasarkannya dengan harga yang mahal (akibat monopoli). Lalu apakah printer dot matrix tersebut dapat dipakai pada sistem operasi modern? Yup! Bisa! Mereka dilengkapi dengan driver yang akan menerjemahkan gambar dan dokumen untuk dicetak pada modus grafis oleh printer sehingga dapat bekerja pada software modern seperti Microsoft Office. Percetakan graphic mode bukanlah percetakan text mode (atau raw mode) yang mencetak karakter per karakter. Percetakan graphic mode lebih lambat dan pengaturan posisi karakter lebih sulit dilakukan dibandingkan pada text mode.

Salah satu solusi untuk mendapatkan posisi percetakan yang rapi di graphic mode adalah memakai font bawaan printer. Microsoft Word, misalnya, akan menampilkan font bawaan printer bila printer dot matrix dipilih sebagai default printer. Mengubah printer dot matrix tersebut menjadi default printer adalah sesuatu yang wajib karena font disimpan di dalam RAM printer (disebut sebagai printer device font), bukan di C:\Windows\Fonts.

Lalu, apakah saya dapat menggunakan printer device font di iReport atau JasperReport? Sayang sekali, jawabannya tidak! Java dirancang untuk tidak terikat pada satu platform tertentu. Memakai printer device font bukan hanya terikat pada sistem operasi, tapi juga terikat pada ketersediaan sebuah hardware printer merk tertentu. Pada komputer yang tidak memiliki printer ini, tampilan bisa jadi berantakan. Dengan demikian, saya tidak dapat memakai JasperReport untuk menghasilkan laporan text mode yang memiliki posisi dan ukuran yang bisa saya kendalikan secara mudah.

Walaupun demikian, saya masih dapat memakai Java Print Service (JPS) untuk mencetak text mode di Java. Hanya saja, tanpa kemampuan templating seperti di JasperReport, ini akan menjadi sebuah tantangan tersendiri. Untuk mengatasi masalah ini, saya akan menggunakan simple-escp yang menyediakan fasilitas templating dan preview khusus untuk text mode. simple-escp juga menyediakan cara mudah untuk memberikan perintah ESC/P (Epson Standard Code for Printers) untuk melakukan pengaturan pada printer. Dokumentasi untuk simple-escp dapat dibaca di http://jockihendry.github.io/simple-escp/. Pada artikel ini, saya akan mencoba membuat sebuah program Java sederhana yang mencetak ke printer dot-matrix dengan menggunakan simple-escp.

Langkah pertama yang saya lakukan adalah men-download file JAR simple-escp di lokasi https://github.com/JockiHendry/simple-escp/releases. Berikutnya, saya akan membuat proyek baru di IntelliJ IDEA dengan memilih menu File, New Project. Pada dialog yang muncul, saya memilih Java dan men-klik tombol Next dua kali, mengisi nama proyek dan men-klik tombol Finish.

Saya kemudian men-klik kanan nama proyek dan memilih menu Open Module Settings. Pada dialog yang muncul, saya men-klik tombol tambah, lalu memilih Jar or Directories… untuk menambahkan JAR milik simple-escp sehingga dialog terlihat seperti pada gambar berikut ini:

Menambah library simple-escp

Menambah library simple-escp

Saya kemudian men-klik tombol Ok dua kali.

Berikutnya, saya akan membuat sebuah kode program yang akan melakukan percetakan. Untuk itu, saya men-klik kanan folder src dan memilih menu New, Java Class. Saya memberi nama Main pada class tersebut dan mengisinya seperti pada kode program berikut ini:

import simple.escp.SimpleEscp;
import simple.escp.util.EscpUtil;

public class Main {

    public static void main(String[] args) {
        SimpleEscp simpleEscp = new SimpleEscp("EPSON LX-310 ESC/P");
        simpleEscp.print(EscpUtil.escSelectUnderline() +
            "Ini akan digarisbawahi" + EscpUtil.escCancelUnderline());
    }

}

Setelah itu, saya men-klik bagian yang kosong pada kode program class tersebut dan memilih menu Run Main.main().

Begitu program dijalankan, ia akan langsung mencetak ke printer yang bernama EPSON LX-310 ESC/P. Tulisan yang dicetak akan memiliki garis bawah. Untuk melihat daftar nama printer di Windows, klik Start Menu di pojok kiri bawah layar dan pilih Devices and Printers.

Biasanya akan ada 1 printer yang dianggap sebagai default printer yang menjadi target percetakan. Bila seandainya printer dot matrix yang ingin saya pakai sudah di-set sebagai default printer di sistem operasi, maka saya dapat mengganti perintah new SimpleEscp("NAMA_PRINTER") menjadi new SimpleEscp(). Membuat instance SimpleEscp tanpa parameter akan secara otomatis memakai default printer.

Rasanya tidak cukup bila hanya bisa mencetak dan memberikan perintah ESC/P saja. Kebutuhan percetakan bisa saja lebih kompleks dari ini. Beruntungnya, simple-escp menyediakan fasilitas template dalam bentuk JSON. Untuk membuat template JSON, saya men-klik kanan folder src dan memilih menu New, File. Saya kemudian memberi nama file berupa template.json dan men-klik tombol Ok.

Saya mengisi file template.json menjadi seperti berikut ini:

{
     "pageFormat": {
         "pageLength": 10,
         "pageWidth": 50,
         "usePageLengthFromPrinter": false
     },
     "template": {                    
         "header": [ "  PT. XYZ                              HAL: %{PAGE_NO}" ],
         "detail": [ 
             " Ini adalah baris %{LINE_NO} dari halaman %{PAGE_NO} ",
             " ini adalah baris %{LINE_NO} dari halaman %{PAGE_NO} "
         ]
     }
 }

File JSON di simple-escp terdiri atas 2 bagian, pageFormat dan template. Pada pageFormat, saya dapat menentukan pengaturan seperti ukuran halaman (panjang dalam baris dan lebar dalam jumlah karakter). Pada contoh di atas, saya juga memberi nilai usePageLengthFromPrinter dengan false agar ukuran halaman yang tersimpan di RAM printer diabaikan dan ukuran efektif yang berlaku adalah yang telah saya tentukan (10 baris x 50 karakter). Ini akan mempengaruhi posisi akhir ketika menekan tombol Tear Off di printer.

Pada bagian template, saya dapat memakai key berupa firstPage, header, detail, footer, dan lastPage. Pada template di atas, saya hanya mendeklarasikan section header dan detail. Isi dari header akan selalu dicetak di setiap awal halaman.

simple-escp mendukung function dalam syntax seperti %{...}. Pada contoh di atas, %{PAGE_NO} akan diganti menjadi nomor halaman (mulai dari 1) dan %{LINE_NO} akan diganti menjadi nomor baris (mulai dari 1).

Untuk mencetak JSON template di atas, saya dapat membuat kode program seperti berikut ini:

import simple.escp.SimpleEscp;
import simple.escp.Template;
import simple.escp.fill.FillJob;
import simple.escp.json.JsonTemplate;
import simple.escp.util.EscpUtil;

public class Main {

    public static void main(String[] args) {
        try {
            SimpleEscp simpleEscp = new SimpleEscp("EPSON LX-310 ESC/P");
            Template template = new JsonTemplate(Main.class.getResourceAsStream("template.json"));
            String hasil = new FillJob(template.parse()).fill();
            simpleEscp.print(hasil);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

}

Pada kode program di atas, saya memakai FillJob untuk menerjemahkan JsonTemplate menjadi sebuah String biasa. Setelah itu, saya mencetak String tersebut dengan simpleEscp.print().

Selain function, simple-escp juga mendukung placeholder dalam syntax seperti ${...}. Placeholder dapat dianggap seperti variabel yang akan diganti nilainya berdasarkan sumber data yang dipakai. Sumber data yang dipakai oleh simple-escp ditangani oleh salah satu implementasi interface DataSource. Saat ini, simple-escp dilengkapi dengan BeanDataSource untuk membaca nilai dari sebuah object dan MapDataSource untuk membaca nilai dari sebuah Map.

Sebagai latihan, saya akan mengubah isi file template.json agar memakai placeholder:

{
     "pageFormat": {
         "pageLength": 10,
         "pageWidth": 50,
         "usePageLengthFromPrinter": false
     },
     "template": {
         "header": [ "  PT. XYZ                              HAL: %{PAGE_NO}" ],
         "detail": [
             " Nomor Faktur: ${nomorFaktur:20}",
             {
                "table": "listItemFaktur",
                "border": true,
                "columns": [
                    { "caption": "Nama Barang", "source": "namaBarang", "width": 15, "wrap": true },
                    { "caption": "Qty", "source": "qty::right", "width": 10 },
                    { "caption": "Harga", "source": "harga::right", "width": 20 }
                ]
             }
         ]
     }
 }

Placeholder seperti ${nomorFaktur:20} akan diganti dengan nilai nomorFaktur dari DataSource. Bila nilai
tersebut melebihi batas 20 karakter, maka isinya akan dipotong sehingga nilai dari placeholder tidak akan pernah
lebih dari 20 karakter. Bila kurang dari 20 karakter, maka sisa ruang sisa akan di-isi dengan spasi.

Pada template di atas, saya juga membuat sebuah table yang terdiri atas 3 kolom. Isi dari tabel tersebut akan diambil dari nilai listItemFaktur di DataSource. Nilai dari listItemFaktur wajib harus berupa Collection, misalnya sebuah List. Penggunaan "wrap": true pada kolom akan menyebabkan nilai yang melebihi batas kolom tidak dipotong, melainkan dicetak pada baris berikutnya. Nilai "source" seperti "qty::right" akan menyebabkan nilai dicetak dengan rata kanan.

Berikutnya, karena saya ingin sumber data laporan diambil dari objek, maka saya membuat class Faktur yang isinya seperti berikut ini:

import java.util.ArrayList;
import java.util.List;

public class Faktur {

    private String nomorFaktur;
    private List<ItemFaktur> listItemFaktur = new ArrayList<ItemFaktur>();

    public Faktur(String nomorFaktur) {
        this.nomorFaktur = nomorFaktur;
    }

    public String getNomorFaktur() {
        return nomorFaktur;
    }

    public List<ItemFaktur> getListItemFaktur() {
        return listItemFaktur;
    }

    public void tambahItemFaktur(ItemFaktur itemFaktur) {
        this.listItemFaktur.add(itemFaktur);
    }
}

Class tersebut akan membutuhkan class ItemFaktur yang isinya seperti berikut ini:

import java.math.BigDecimal;

public class ItemFaktur {

    private String namaBarang;
    private Integer qty;
    private BigDecimal harga;

    public ItemFaktur(String namaBarang, Integer qty, BigDecimal harga) {
        this.namaBarang = namaBarang;
        this.qty = qty;
        this.harga = harga;
    }

    public String getNamaBarang() {
        return namaBarang;
    }

    public Integer getQty() {
        return qty;
    }

    public BigDecimal getHarga() {
        return harga;
    }

}

Sekarang, saya dapat mendeklarasikan sebuah objek yang menjadi sumber data seperti berikut ini:

Faktur faktur = new Faktur("FA-1234-556677-XX-BB-CC");
faktur.tambahItemFaktur(new ItemFaktur("Plantronics Backbeat Go 2 With Charging Case",
    1, new BigDecimal("13750000")));
faktur.tambahItemFaktur(new ItemFaktur("CORT Gitar Akustik AD810 - Natural Satin",
    1, new BigDecimal("14900000")));
faktur.tambahItemFaktur(new ItemFaktur("SAMSON Monitor Speaker System MediaOne 3A",
    1, new BigDecimal("14250000")));

Untuk menghasilkan String dan mencetak laporan, saya dapat menggunakan cara singkat seperti berikut ini:

SimpleEscp simpleEscp = new SimpleEscp("EPSON LX-310 ESC/P");            
Template template = new JsonTemplate(Main.class.getResourceAsStream("template.json"));            
simpleEscp.print(template, DataSources.from(faktur));

Factory DataSources akan menghasilkan class DataSource secara otomatis (dalam hal ini adalah BeanDataSource). Bila kode program di atas dijalankan, maka printer akan langsung mencetak tabel sesuai dengan isi dari objek faktur.

Ada kalanya lebih baik memberikan tampilan preview bagi pengguna sebelum mencetak. Untuk kebutuhan ini, simple-escp dilengkapi dengan komponen PrintPreviewPane yang merupakan sebuah JPanel untuk menampilkan preview percetakan. Sebagai latihan, untuk memakai PrintPreviewPane, saya mengubah kode program menjadi berikut ini:

import simple.escp.Template;
import simple.escp.json.JsonTemplate;
import simple.escp.swing.PrintPreviewPane;
import javax.swing.*;
import java.awt.*;
import java.math.BigDecimal;

public class Main extends JFrame {

    public Main() {
        super("Latihan");

        Faktur faktur = new Faktur("FA-1234-556677-XX-BB-CC");
        faktur.tambahItemFaktur(new ItemFaktur("Plantronics Backbeat Go 2 With Charging Case",
                1, new BigDecimal("13750000")));
        faktur.tambahItemFaktur(new ItemFaktur("CORT Gitar Akustik AD810 - Natural Satin",
                1, new BigDecimal("14900000")));
        faktur.tambahItemFaktur(new ItemFaktur("SAMSON Monitor Speaker System MediaOne 3A",
                1, new BigDecimal("14250000")));

        Template template  = null;
        try {
            template = new JsonTemplate(Main.class.getResourceAsStream("template.json"));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        PrintPreviewPane preview = new PrintPreviewPane(template, null, faktur);
        setLayout(new BorderLayout());
        add(preview, BorderLayout.CENTER);

        pack();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
    }

    public static void main(String[] args) {
        new Main();
    }

}

Bila program dijalankan, akan muncul tampilan seperti pada gambar berikut ini:

Tampilan PrintPreviewPanel

Tampilan PrintPreviewPanel

Pengguna kini dapat menentukan sendiri kapan memulai percetakan dengan men-klik tombol Print pada PrintPreviewPane.