小票打印机, 目前主要有这么几个宽度规格 58mm 70mm 80mm,常见的58mm和80mm为用得比较多, 对于模板的设计来说,其中的区别基本上只在于每行可以显示多少个字符。58mm的小票热敏纸,一行最大可以显示32个半角字符,而80mm的则可以显示48个半角字符。
EscPos 为目前主流小票打印机都支持的一种打印机控制命令集, 而其很重要的一个特点是无需驱动支持。 有以下两种方式可以向一台打印机发送这种命令。
USB连接的情况下,直接向 LPT1 这个设备写入命令(或者COM口)。在windows下,外接设备被映射到系统中,其实也有其自己的路径, 不过这个路径和一般的文件路径不一样罢了, 如果从Linux的/dev/sda0 => /mnt/sda0 这样来理解可能会好理解点。那么使用CreateFile(设备路径), 获取一个缓冲区地址,然后往这个缓冲区写入数据,数据就会被发送到设备,以实现通讯
网线连接。一般的小票打印机都可以插网线,同时开放了9100这个端口, 可以通过socket建立连接,向这个端口发送数据,也同样可以和设备建立通讯连接
以上为一些基本,接下来分享如何设计模板。
由于小票的适用范围, 其实可以归结为以下几点:
小票可以看作是一行一行的字符拼接成完整的小票, 按照布局上来说, 那么就像是一个只有一栏的表格, 只要决定了每一行是什么样子, 整体的样子就有了
由于小票一般都不是很宽,那么一个字符或者一个字符串在当前行的某个具体位置并不重要,只需要 居左 居中 居右 三种位置属性即可,即使有, 横向的布局也应该是横向的填充布局
小票不需要很多很花的样式
。。。
基于以上几点, 将小票分为三个部分
- 头
- 体
- 尾
头的部分,主要显示标题、打印时间、单号等信息
体的部分,主要显示项目明细,从一个List里按顺序输出每行即可
尾的部分,一些其他内容,可以是文本,可以是链接
按照上面github的这个库里所说, 把内容类型分为 文本 条形码 二维码 和图片,黑白的情况下,图片并不是很有必要
"header": [
{
"text": "{$shopname}",
"size": 2,
"bold": true,
"format": 1,
"line": 2,
"underline": true,
"type": 0
},
]
以一个json数组存储头的部分内容,不同的类型的内容拥有不同的属性,最关键的是占位符,{$shopname}
这样的字符 可以使用 \{\$(.+?)\}
这个正则来匹配, 匹配到之后,根据每个占位符的内容不同,替换成不同的实际数据,如果不使用占位符,也可以直接填入完整的数据。这个数组里的元素,会按照顺序一行一行地打印出来,即这个数组里每个json对象都代表着一行。
"goods": [
{
"name": "商品名",
"width": 24,
"format": 0,
"variable": "name"
}
],
这里的每一个对象, 表示的是明细部分的一列, 标识出了列名,列宽,列标题的位置,还有一个则是表示该列的数据存取的字段名,例如后面拿到了name的数据,那么去这个模板里查找,来指定其格式和位置
"bill": [
{
"text": "实收现金",
"size": 3,
"bold": true,
"format": 1,
"line": 2,
"underline": false,
"type": 0
},
{
"text": "{$cash}",
"size": 3,
"bold": true,
"format": 1,
"line": 2,
"underline": false,
"type": 0
}
],
这里的结构和header还有footer的是一样的,你当然也可以加入其他类型的内容,比如adv 广告什么的。
模板的解析,对于java等语言来说很是简单,因为他们都有动态类型这样的东西。但是对于Delphi来说,TObject如果不在TJson.JsonToObject的时候指定其类型,那么反序列化是没用的。不过可以使用TJsonObject或者TJsonArray来替代,对于动态的json,就不能像其他语言那么方便地直接操作对象了,用TJsonObject的方法来替代即可。
例如, goods的子对象里有一个type
字段,根据这个字段,可以判断出当前这个对象的类型,有了具体的类型,
if goods[i].GetValue<Integer>('type') =0 then
begin
thegoods := TJson.JsonToObject<TText>(goods[i].ToJson());
end;
这样就可以获得具体的实例了.
最后,也是关键的一步就是打印了,当把整个模板转换为一行行的字符串之后,不能直接发送。首先是需要异步发送, 否则阻塞主线程必然不是个好实践,然后,每个字符串需要按顺序发送。
那么需要一个有序的队列来处理整个发送。Delphi自带的TQueue是个好的选择,不过由于其存储的是指针,那么可以对其进行一层封装,例如把一个包含每一行字符串的 Record的地址Push入队列,这里需要注意,每次Push需要 New(TPrintObj), 否则最终队列中都是同样的内容了。
另外还有几个队列可以选择
http://blog.sina.com.cn/s/blog_722bc92e0101gngd.html
这个队列据说很快
DIOCP v5中有一个TSafeQueue,也比较看好
还有一个
http://blog.qdac.cc/?p=148
无锁队列也可以进行尝试。
当然了,一味追求效率没有必要,那一点点时间的提升,不如拿来保证整个服务的稳定