小票打印機(jī)缴渊, 目前主要有這么幾個(gè)寬度規(guī)格 58mm 70mm 80mm覆获,常見(jiàn)的58mm和80mm為用得比較多帆赢, 對(duì)于模板的設(shè)計(jì)來(lái)說(shuō)下硕,其中的區(qū)別基本上只在于每行可以顯示多少個(gè)字符。58mm的小票熱敏紙羽资,一行最大可以顯示32個(gè)半角字符淘菩,而80mm的則可以顯示48個(gè)半角字符。
EscPos 為目前主流小票打印機(jī)都支持的一種打印機(jī)控制命令集, 而其很重要的一個(gè)特點(diǎn)是無(wú)需驅(qū)動(dòng)支持潮改。 有以下兩種方式可以向一臺(tái)打印機(jī)發(fā)送這種命令狭郑。
USB連接的情況下,直接向 LPT1 這個(gè)設(shè)備寫(xiě)入命令(或者COM口)汇在。在windows下翰萨,外接設(shè)備被映射到系統(tǒng)中,其實(shí)也有其自己的路徑糕殉, 不過(guò)這個(gè)路徑和一般的文件路徑不一樣罷了亩鬼, 如果從Linux的/dev/sda0 => /mnt/sda0 這樣來(lái)理解可能會(huì)好理解點(diǎn)。那么使用CreateFile(設(shè)備路徑), 獲取一個(gè)緩沖區(qū)地址阿蝶,然后往這個(gè)緩沖區(qū)寫(xiě)入數(shù)據(jù)雳锋,數(shù)據(jù)就會(huì)被發(fā)送到設(shè)備,以實(shí)現(xiàn)通訊
網(wǎng)線連接羡洁。一般的小票打印機(jī)都可以插網(wǎng)線玷过,同時(shí)開(kāi)放了9100這個(gè)端口, 可以通過(guò)socket建立連接筑煮,向這個(gè)端口發(fā)送數(shù)據(jù)冶匹,也同樣可以和設(shè)備建立通訊連接
以上為一些基本,接下來(lái)分享如何設(shè)計(jì)模板咆瘟。
由于小票的適用范圍, 其實(shí)可以歸結(jié)為以下幾點(diǎn):
小票可以看作是一行一行的字符拼接成完整的小票诽里, 按照布局上來(lái)說(shuō)袒餐, 那么就像是一個(gè)只有一欄的表格, 只要決定了每一行是什么樣子谤狡, 整體的樣子就有了
由于小票一般都不是很寬灸眼,那么一個(gè)字符或者一個(gè)字符串在當(dāng)前行的某個(gè)具體位置并不重要,只需要 居左 居中 居右 三種位置屬性即可墓懂,即使有焰宣, 橫向的布局也應(yīng)該是橫向的填充布局
小票不需要很多很花的樣式
。捕仔。匕积。
基于以上幾點(diǎn), 將小票分為三個(gè)部分
- 頭
- 體
- 尾
頭的部分榜跌,主要顯示標(biāo)題闪唆、打印時(shí)間、單號(hào)等信息
體的部分钓葫,主要顯示項(xiàng)目明細(xì)悄蕾,從一個(gè)List里按順序輸出每行即可
尾的部分,一些其他內(nèi)容础浮,可以是文本帆调,可以是鏈接
按照上面github的這個(gè)庫(kù)里所說(shuō)奠骄, 把內(nèi)容類型分為 文本 條形碼 二維碼 和圖片,黑白的情況下番刊,圖片并不是很有必要
"header": [
{
"text": "{$shopname}",
"size": 2,
"bold": true,
"format": 1,
"line": 2,
"underline": true,
"type": 0
},
]
以一個(gè)json數(shù)組存儲(chǔ)頭的部分內(nèi)容含鳞,不同的類型的內(nèi)容擁有不同的屬性,最關(guān)鍵的是占位符撵枢,{$shopname}
這樣的字符 可以使用 \{\$(.+?)\}
這個(gè)正則來(lái)匹配民晒, 匹配到之后,根據(jù)每個(gè)占位符的內(nèi)容不同锄禽,替換成不同的實(shí)際數(shù)據(jù)潜必,如果不使用占位符,也可以直接填入完整的數(shù)據(jù)沃但。這個(gè)數(shù)組里的元素磁滚,會(huì)按照順序一行一行地打印出來(lái),即這個(gè)數(shù)組里每個(gè)json對(duì)象都代表著一行宵晚。
"goods": [
{
"name": "商品名",
"width": 24,
"format": 0,
"variable": "name"
}
],
這里的每一個(gè)對(duì)象垂攘, 表示的是明細(xì)部分的一列, 標(biāo)識(shí)出了列名淤刃,列寬晒他,列標(biāo)題的位置,還有一個(gè)則是表示該列的數(shù)據(jù)存取的字段名逸贾,例如后面拿到了name的數(shù)據(jù)陨仅,那么去這個(gè)模板里查找,來(lái)指定其格式和位置
"bill": [
{
"text": "實(shí)收現(xiàn)金",
"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
}
],
這里的結(jié)構(gòu)和header還有footer的是一樣的铝侵,你當(dāng)然也可以加入其他類型的內(nèi)容灼伤,比如adv 廣告什么的。
模板的解析咪鲜,對(duì)于java等語(yǔ)言來(lái)說(shuō)很是簡(jiǎn)單狐赡,因?yàn)樗麄兌加袆?dòng)態(tài)類型這樣的東西。但是對(duì)于Delphi來(lái)說(shuō)疟丙,TObject如果不在TJson.JsonToObject的時(shí)候指定其類型颖侄,那么反序列化是沒(méi)用的。不過(guò)可以使用TJsonObject或者TJsonArray來(lái)替代享郊,對(duì)于動(dòng)態(tài)的json发皿,就不能像其他語(yǔ)言那么方便地直接操作對(duì)象了,用TJsonObject的方法來(lái)替代即可拂蝎。
例如穴墅, goods的子對(duì)象里有一個(gè)type
字段,根據(jù)這個(gè)字段,可以判斷出當(dāng)前這個(gè)對(duì)象的類型玄货,有了具體的類型皇钞,
if goods[i].GetValue<Integer>('type') =0 then
begin
thegoods := TJson.JsonToObject<TText>(goods[i].ToJson());
end;
這樣就可以獲得具體的實(shí)例了.
最后,也是關(guān)鍵的一步就是打印了松捉,當(dāng)把整個(gè)模板轉(zhuǎn)換為一行行的字符串之后夹界,不能直接發(fā)送。首先是需要異步發(fā)送隘世, 否則阻塞主線程必然不是個(gè)好實(shí)踐可柿,然后,每個(gè)字符串需要按順序發(fā)送丙者。
那么需要一個(gè)有序的隊(duì)列來(lái)處理整個(gè)發(fā)送复斥。Delphi自帶的TQueue是個(gè)好的選擇,不過(guò)由于其存儲(chǔ)的是指針械媒,那么可以對(duì)其進(jìn)行一層封裝目锭,例如把一個(gè)包含每一行字符串的 Record的地址Push入隊(duì)列,這里需要注意纷捞,每次Push需要 New(TPrintObj), 否則最終隊(duì)列中都是同樣的內(nèi)容了痢虹。
另外還有幾個(gè)隊(duì)列可以選擇
http://blog.sina.com.cn/s/blog_722bc92e0101gngd.html
這個(gè)隊(duì)列據(jù)說(shuō)很快
DIOCP v5中有一個(gè)TSafeQueue,也比較看好
還有一個(gè)
http://blog.qdac.cc/?p=148
無(wú)鎖隊(duì)列也可以進(jìn)行嘗試主儡。
當(dāng)然了奖唯,一味追求效率沒(méi)有必要,那一點(diǎn)點(diǎn)時(shí)間的提升糜值,不如拿來(lái)保證整個(gè)服務(wù)的穩(wěn)定