@bricks-soft/capacitor-thermal-printer 0.0.0-stage → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BricksSoftCapacitorThermalPrinter.podspec +17 -0
- package/LICENSE +201 -0
- package/Package.swift +13 -0
- package/README.md +80 -2
- package/android/build.gradle +40 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/com/brickssoft/thermalprinter/ThermalPrinterPlugin.java +463 -0
- package/dist/esm/definitions.d.ts +73 -0
- package/dist/esm/definitions.js +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/web.d.ts +13 -0
- package/dist/esm/web.js +33 -0
- package/dist/plugin.cjs.js +65 -0
- package/dist/plugin.js +68 -0
- package/ios/Sources/ThermalPrinter/EscPosBarcode.swift +121 -0
- package/ios/Sources/ThermalPrinter/EscPosBitmap.swift +178 -0
- package/ios/Sources/ThermalPrinter/EscPosBluetooth.swift +341 -0
- package/ios/Sources/ThermalPrinter/EscPosBluetoothConnection.swift +298 -0
- package/ios/Sources/ThermalPrinter/EscPosBluetoothRules.swift +78 -0
- package/ios/Sources/ThermalPrinter/EscPosCharsetEncoding.swift +115 -0
- package/ios/Sources/ThermalPrinter/EscPosCoreBluetoothLink.swift +287 -0
- package/ios/Sources/ThermalPrinter/EscPosDeviceConnection.swift +47 -0
- package/ios/Sources/ThermalPrinter/EscPosJavaCompat.swift +213 -0
- package/ios/Sources/ThermalPrinter/EscPosKnownPrinterStore.swift +44 -0
- package/ios/Sources/ThermalPrinter/EscPosPrinter.swift +55 -0
- package/ios/Sources/ThermalPrinter/EscPosPrinterCommands.swift +367 -0
- package/ios/Sources/ThermalPrinter/EscPosPrinterSize.swift +53 -0
- package/ios/Sources/ThermalPrinter/EscPosQRCode.swift +70 -0
- package/ios/Sources/ThermalPrinter/EscPosQueue.swift +30 -0
- package/ios/Sources/ThermalPrinter/EscPosTcpConnection.swift +265 -0
- package/ios/Sources/ThermalPrinter/PrinterTextParser.swift +622 -0
- package/ios/Sources/ThermalPrinter/ThermalPrinterPlugin.swift +85 -0
- package/ios/Sources/ThermalPrinter/ThermalPrinterService.swift +380 -0
- package/package.json +61 -4
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
struct EscPosError: Error, Equatable {
|
|
4
|
+
enum Kind: Equatable {
|
|
5
|
+
case connection
|
|
6
|
+
case parser
|
|
7
|
+
case encoding
|
|
8
|
+
case barcode
|
|
9
|
+
case runtime
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let kind: Kind
|
|
13
|
+
let message: String
|
|
14
|
+
|
|
15
|
+
static func connection(_ message: String) -> EscPosError { EscPosError(kind: .connection, message: message) }
|
|
16
|
+
static func parser(_ message: String) -> EscPosError { EscPosError(kind: .parser, message: message) }
|
|
17
|
+
static func encoding(_ message: String) -> EscPosError { EscPosError(kind: .encoding, message: message) }
|
|
18
|
+
static func barcode(_ message: String) -> EscPosError { EscPosError(kind: .barcode, message: message) }
|
|
19
|
+
static func runtime(_ message: String) -> EscPosError { EscPosError(kind: .runtime, message: message) }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
struct JavaString: Equatable {
|
|
23
|
+
var units: [UInt16]
|
|
24
|
+
|
|
25
|
+
init(_ string: String) {
|
|
26
|
+
units = Array(string.utf16)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
init<S: Sequence>(units: S) where S.Element == UInt16 {
|
|
30
|
+
self.units = Array(units)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
var length: Int { units.count }
|
|
34
|
+
|
|
35
|
+
var isEmpty: Bool { units.isEmpty }
|
|
36
|
+
|
|
37
|
+
var string: String { String(decoding: units, as: UTF16.self) }
|
|
38
|
+
|
|
39
|
+
func indexOf(_ needle: String, from: Int = 0) -> Int {
|
|
40
|
+
let pattern = Array(needle.utf16)
|
|
41
|
+
let start = max(0, from)
|
|
42
|
+
if pattern.isEmpty {
|
|
43
|
+
return start <= length ? start : length
|
|
44
|
+
}
|
|
45
|
+
guard pattern.count <= length else { return -1 }
|
|
46
|
+
var index = start
|
|
47
|
+
let first = pattern[0]
|
|
48
|
+
while index + pattern.count <= length {
|
|
49
|
+
if units[index] == first && units[index..<(index + pattern.count)].elementsEqual(pattern) {
|
|
50
|
+
return index
|
|
51
|
+
}
|
|
52
|
+
index += 1
|
|
53
|
+
}
|
|
54
|
+
return -1
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func contains(_ needle: String) -> Bool {
|
|
58
|
+
indexOf(needle) != -1
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func substring(_ begin: Int, _ end: Int? = nil) throws -> JavaString {
|
|
62
|
+
let finish = end ?? length
|
|
63
|
+
guard begin >= 0, finish <= length, begin <= finish else {
|
|
64
|
+
throw EscPosError.runtime("begin \(begin), end \(finish), length \(length)")
|
|
65
|
+
}
|
|
66
|
+
return JavaString(units: units[begin..<finish])
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func trim() -> JavaString {
|
|
70
|
+
var start = 0
|
|
71
|
+
var finish = length
|
|
72
|
+
while start < finish && units[start] <= 0x20 {
|
|
73
|
+
start += 1
|
|
74
|
+
}
|
|
75
|
+
while finish > start && units[finish - 1] <= 0x20 {
|
|
76
|
+
finish -= 1
|
|
77
|
+
}
|
|
78
|
+
return JavaString(units: units[start..<finish])
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func lowercased() -> JavaString { JavaString(string.lowercased()) }
|
|
82
|
+
|
|
83
|
+
func uppercased() -> JavaString { JavaString(string.uppercased()) }
|
|
84
|
+
|
|
85
|
+
static func == (lhs: JavaString, rhs: String) -> Bool {
|
|
86
|
+
lhs.units.elementsEqual(rhs.utf16)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
static func splitLines(_ text: JavaString) -> [JavaString] {
|
|
90
|
+
var parts: [JavaString] = []
|
|
91
|
+
var start = 0
|
|
92
|
+
var index = 0
|
|
93
|
+
var matched = false
|
|
94
|
+
let units = text.units
|
|
95
|
+
while index < units.count {
|
|
96
|
+
if units[index] == 0x0A {
|
|
97
|
+
parts.append(JavaString(units: units[start..<index]))
|
|
98
|
+
index += 1
|
|
99
|
+
start = index
|
|
100
|
+
matched = true
|
|
101
|
+
} else if units[index] == 0x0D && index + 1 < units.count && units[index + 1] == 0x0A {
|
|
102
|
+
parts.append(JavaString(units: units[start..<index]))
|
|
103
|
+
index += 2
|
|
104
|
+
start = index
|
|
105
|
+
matched = true
|
|
106
|
+
} else {
|
|
107
|
+
index += 1
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if !matched {
|
|
111
|
+
return [text]
|
|
112
|
+
}
|
|
113
|
+
parts.append(JavaString(units: units[start..<units.count]))
|
|
114
|
+
while let last = parts.last, last.isEmpty {
|
|
115
|
+
parts.removeLast()
|
|
116
|
+
}
|
|
117
|
+
return parts
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
enum JavaMath {
|
|
122
|
+
static func round(_ value: Float) -> Int {
|
|
123
|
+
if value.isNaN { return 0 }
|
|
124
|
+
return saturatingInt32((Double(value) + 0.5).rounded(.down))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static func round(_ value: Double) -> Int {
|
|
128
|
+
if value.isNaN { return 0 }
|
|
129
|
+
let rounded = (value + 0.5).rounded(.down)
|
|
130
|
+
if rounded >= Double(Int64.max) { return Int(Int64.max) }
|
|
131
|
+
if rounded <= Double(Int64.min) { return Int(Int64.min) }
|
|
132
|
+
return Int(rounded)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static func toInt(_ value: Float) -> Int {
|
|
136
|
+
if value.isNaN { return 0 }
|
|
137
|
+
return saturatingInt32(Double(value).rounded(.towardZero))
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static func toInt(_ value: Double) -> Int {
|
|
141
|
+
if value.isNaN { return 0 }
|
|
142
|
+
return saturatingInt32(value.rounded(.towardZero))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
static func floorDiv(_ numerator: Float, _ denominator: Float) -> Int {
|
|
146
|
+
toInt((numerator / denominator).rounded(.down))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static func parseFloat(_ string: String) -> Float? {
|
|
150
|
+
var text = JavaString(string).trim().string
|
|
151
|
+
if let last = text.last, "fFdD".contains(last) {
|
|
152
|
+
text.removeLast()
|
|
153
|
+
}
|
|
154
|
+
if text.isEmpty || text.lowercased().hasPrefix("inf") || text.lowercased().hasPrefix("+inf") || text.lowercased().hasPrefix("-inf") {
|
|
155
|
+
return nil
|
|
156
|
+
}
|
|
157
|
+
return Float(text)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static func parseDouble(_ string: String) -> Double? {
|
|
161
|
+
var text = JavaString(string).trim().string
|
|
162
|
+
if let last = text.last, "fFdD".contains(last) {
|
|
163
|
+
text.removeLast()
|
|
164
|
+
}
|
|
165
|
+
if text.isEmpty { return nil }
|
|
166
|
+
return Double(text)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private static func saturatingInt32(_ value: Double) -> Int {
|
|
170
|
+
if value >= Double(Int32.max) { return Int(Int32.max) }
|
|
171
|
+
if value <= Double(Int32.min) { return Int(Int32.min) }
|
|
172
|
+
return Int(value)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
enum EscPosHex {
|
|
177
|
+
private static let digits = Array("0123456789abcdef".utf8)
|
|
178
|
+
|
|
179
|
+
static func string(_ bytes: [UInt8]) -> String {
|
|
180
|
+
var output = [UInt8]()
|
|
181
|
+
output.reserveCapacity(bytes.count * 2)
|
|
182
|
+
for byte in bytes {
|
|
183
|
+
output.append(digits[Int(byte >> 4)])
|
|
184
|
+
output.append(digits[Int(byte & 0x0F)])
|
|
185
|
+
}
|
|
186
|
+
return String(decoding: output, as: UTF8.self)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
static func bytes(_ hex: JavaString) throws -> [UInt8] {
|
|
190
|
+
let count = hex.length / 2
|
|
191
|
+
var bytes = [UInt8](repeating: 0, count: count)
|
|
192
|
+
let units = hex.units
|
|
193
|
+
for index in 0..<count {
|
|
194
|
+
let high = units[index * 2]
|
|
195
|
+
let low = units[index * 2 + 1]
|
|
196
|
+
guard let highValue = value(high), let lowValue = value(low) else {
|
|
197
|
+
let pair = JavaString(units: [high, low]).string
|
|
198
|
+
throw EscPosError.runtime("For input string: \"\(pair)\" under radix 16")
|
|
199
|
+
}
|
|
200
|
+
bytes[index] = UInt8(highValue << 4 | lowValue)
|
|
201
|
+
}
|
|
202
|
+
return bytes
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private static func value(_ unit: UInt16) -> UInt8? {
|
|
206
|
+
switch unit {
|
|
207
|
+
case 0x30...0x39: return UInt8(unit - 0x30)
|
|
208
|
+
case 0x41...0x46: return UInt8(unit - 0x41 + 10)
|
|
209
|
+
case 0x61...0x66: return UInt8(unit - 0x61 + 10)
|
|
210
|
+
default: return nil
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
final class EscPosKnownPrinterStore {
|
|
4
|
+
static func applicationSupportFile() -> URL {
|
|
5
|
+
let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
|
|
6
|
+
?? URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
|
7
|
+
return base
|
|
8
|
+
.appendingPathComponent("ThermalPrinter", isDirectory: true)
|
|
9
|
+
.appendingPathComponent("known-bluetooth-printers.json")
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let fileURL: URL
|
|
13
|
+
private let lock = NSLock()
|
|
14
|
+
|
|
15
|
+
init(fileURL: URL = EscPosKnownPrinterStore.applicationSupportFile()) {
|
|
16
|
+
self.fileURL = fileURL
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func load() -> [String: String] {
|
|
20
|
+
lock.lock()
|
|
21
|
+
defer { lock.unlock() }
|
|
22
|
+
return read()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func remember(identifier: UUID, name: String) {
|
|
26
|
+
lock.lock()
|
|
27
|
+
defer { lock.unlock() }
|
|
28
|
+
var printers = read()
|
|
29
|
+
printers[identifier.uuidString] = name
|
|
30
|
+
guard let data = try? JSONSerialization.data(withJSONObject: printers, options: [.sortedKeys]) else { return }
|
|
31
|
+
try? FileManager.default.createDirectory(at: fileURL.deletingLastPathComponent(), withIntermediateDirectories: true)
|
|
32
|
+
try? data.write(to: fileURL, options: [.atomic])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private func read() -> [String: String] {
|
|
36
|
+
guard let data = try? Data(contentsOf: fileURL),
|
|
37
|
+
let object = try? JSONSerialization.jsonObject(with: data),
|
|
38
|
+
let printers = object as? [String: String]
|
|
39
|
+
else {
|
|
40
|
+
return [:]
|
|
41
|
+
}
|
|
42
|
+
return printers
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
final class EscPosPrinter: EscPosPrinterSize {
|
|
4
|
+
private(set) var printer: EscPosPrinterCommands?
|
|
5
|
+
|
|
6
|
+
init(connection: EscPosDeviceConnection?, printerDpi: Int, printerWidthMM: Float, printerNbrCharactersPerLine: Int, charsetEncoding: EscPosCharsetEncoding? = nil) throws {
|
|
7
|
+
let commands = connection.map { EscPosPrinterCommands(printerConnection: $0, charsetEncoding: charsetEncoding) }
|
|
8
|
+
try super.init(printerDpi: printerDpi, printerWidthMM: printerWidthMM, printerNbrCharactersPerLine: printerNbrCharactersPerLine)
|
|
9
|
+
if let commands = commands {
|
|
10
|
+
printer = try commands.connect()
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
var encoding: EscPosCharsetEncoding? { printer?.charsetEncoding }
|
|
15
|
+
|
|
16
|
+
@discardableResult
|
|
17
|
+
func disconnectPrinter() -> EscPosPrinter {
|
|
18
|
+
printer?.disconnect()
|
|
19
|
+
printer = nil
|
|
20
|
+
return self
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@discardableResult
|
|
24
|
+
func printFormattedText(_ text: String, dotsFeedPaper: Int) throws -> EscPosPrinter {
|
|
25
|
+
guard let printer = printer, printerNbrCharactersPerLine != 0 else { return self }
|
|
26
|
+
|
|
27
|
+
let linesParsed = try PrinterTextParser(printer: self).setFormattedText(JavaString(text)).parse()
|
|
28
|
+
|
|
29
|
+
try printer.reset()
|
|
30
|
+
|
|
31
|
+
for line in linesParsed {
|
|
32
|
+
var lastElement: PrinterTextParserElement?
|
|
33
|
+
for column in line.columns {
|
|
34
|
+
for element in column.elements {
|
|
35
|
+
try element.print(printer)
|
|
36
|
+
lastElement = element
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if lastElement is PrinterTextParserString {
|
|
40
|
+
try printer.newLine()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try printer.feedPaper(dotsFeedPaper)
|
|
45
|
+
return self
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@discardableResult
|
|
49
|
+
func printFormattedTextAndCut(_ text: String, dotsFeedPaper: Int) throws -> EscPosPrinter {
|
|
50
|
+
guard let printer = printer, printerNbrCharactersPerLine != 0 else { return self }
|
|
51
|
+
try printFormattedText(text, dotsFeedPaper: dotsFeedPaper)
|
|
52
|
+
try printer.cutPaper()
|
|
53
|
+
return self
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
final class EscPosPrinterCommands {
|
|
4
|
+
static let lineFeed: UInt8 = 0x0A
|
|
5
|
+
|
|
6
|
+
static let resetPrinter: [UInt8] = [0x1B, 0x40]
|
|
7
|
+
|
|
8
|
+
static let textAlignLeft: [UInt8] = [0x1B, 0x61, 0x00]
|
|
9
|
+
static let textAlignCenter: [UInt8] = [0x1B, 0x61, 0x01]
|
|
10
|
+
static let textAlignRight: [UInt8] = [0x1B, 0x61, 0x02]
|
|
11
|
+
|
|
12
|
+
static let textWeightNormal: [UInt8] = [0x1B, 0x45, 0x00]
|
|
13
|
+
static let textWeightBold: [UInt8] = [0x1B, 0x45, 0x01]
|
|
14
|
+
|
|
15
|
+
static let lineSpacing24: [UInt8] = [0x1B, 0x33, 0x18]
|
|
16
|
+
static let lineSpacing30: [UInt8] = [0x1B, 0x33, 0x1E]
|
|
17
|
+
|
|
18
|
+
static let textSizeNormal: [UInt8] = [0x1D, 0x21, 0x00]
|
|
19
|
+
static let textSizeDoubleHeight: [UInt8] = [0x1D, 0x21, 0x01]
|
|
20
|
+
static let textSizeDoubleWidth: [UInt8] = [0x1D, 0x21, 0x10]
|
|
21
|
+
static let textSizeBig: [UInt8] = [0x1D, 0x21, 0x11]
|
|
22
|
+
static let textSizeBig2: [UInt8] = [0x1D, 0x21, 0x22]
|
|
23
|
+
static let textSizeBig3: [UInt8] = [0x1D, 0x21, 0x33]
|
|
24
|
+
static let textSizeBig4: [UInt8] = [0x1D, 0x21, 0x44]
|
|
25
|
+
static let textSizeBig5: [UInt8] = [0x1D, 0x21, 0x55]
|
|
26
|
+
static let textSizeBig6: [UInt8] = [0x1D, 0x21, 0x66]
|
|
27
|
+
|
|
28
|
+
static let textUnderlineOff: [UInt8] = [0x1B, 0x2D, 0x00]
|
|
29
|
+
static let textUnderlineOn: [UInt8] = [0x1B, 0x2D, 0x01]
|
|
30
|
+
static let textUnderlineLarge: [UInt8] = [0x1B, 0x2D, 0x02]
|
|
31
|
+
|
|
32
|
+
static let textDoubleStrikeOff: [UInt8] = [0x1B, 0x47, 0x00]
|
|
33
|
+
static let textDoubleStrikeOn: [UInt8] = [0x1B, 0x47, 0x01]
|
|
34
|
+
|
|
35
|
+
static let textColorBlack: [UInt8] = [0x1B, 0x72, 0x00]
|
|
36
|
+
static let textColorRed: [UInt8] = [0x1B, 0x72, 0x01]
|
|
37
|
+
|
|
38
|
+
static let textColorReverseOff: [UInt8] = [0x1D, 0x42, 0x00]
|
|
39
|
+
static let textColorReverseOn: [UInt8] = [0x1D, 0x42, 0x01]
|
|
40
|
+
|
|
41
|
+
static let barcodeTypeUPCA = 65
|
|
42
|
+
static let barcodeTypeUPCE = 66
|
|
43
|
+
static let barcodeTypeEAN13 = 67
|
|
44
|
+
static let barcodeTypeEAN8 = 68
|
|
45
|
+
static let barcodeTypeITF = 70
|
|
46
|
+
static let barcodeType128 = 73
|
|
47
|
+
|
|
48
|
+
static let barcodeTextPositionNone = 0
|
|
49
|
+
static let barcodeTextPositionAbove = 1
|
|
50
|
+
static let barcodeTextPositionBelow = 2
|
|
51
|
+
|
|
52
|
+
let printerConnection: EscPosDeviceConnection
|
|
53
|
+
let charsetEncoding: EscPosCharsetEncoding
|
|
54
|
+
var useEscAsteriskCommand = false
|
|
55
|
+
|
|
56
|
+
private var currentTextSize: [UInt8] = []
|
|
57
|
+
private var currentTextColor: [UInt8] = []
|
|
58
|
+
private var currentTextReverseColor: [UInt8] = []
|
|
59
|
+
private var currentTextBold: [UInt8] = []
|
|
60
|
+
private var currentTextUnderline: [UInt8] = []
|
|
61
|
+
private var currentTextDoubleStrike: [UInt8] = []
|
|
62
|
+
|
|
63
|
+
init(printerConnection: EscPosDeviceConnection, charsetEncoding: EscPosCharsetEncoding? = nil) {
|
|
64
|
+
self.printerConnection = printerConnection
|
|
65
|
+
self.charsetEncoding = charsetEncoding ?? EscPosCharsetEncoding(name: "windows-1252", escPosCharsetId: 6)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static func initGSv0Command(bytesByLine: Int, bitmapHeight: Int) -> [UInt8] {
|
|
69
|
+
let xH = bytesByLine / 256
|
|
70
|
+
let xL = bytesByLine - xH * 256
|
|
71
|
+
let yH = bitmapHeight / 256
|
|
72
|
+
let yL = bitmapHeight - yH * 256
|
|
73
|
+
var imageBytes = [UInt8](repeating: 0, count: 8 + bytesByLine * bitmapHeight)
|
|
74
|
+
imageBytes[0] = 0x1D
|
|
75
|
+
imageBytes[1] = 0x76
|
|
76
|
+
imageBytes[2] = 0x30
|
|
77
|
+
imageBytes[3] = 0x00
|
|
78
|
+
imageBytes[4] = UInt8(truncatingIfNeeded: xL)
|
|
79
|
+
imageBytes[5] = UInt8(truncatingIfNeeded: xH)
|
|
80
|
+
imageBytes[6] = UInt8(truncatingIfNeeded: yL)
|
|
81
|
+
imageBytes[7] = UInt8(truncatingIfNeeded: yH)
|
|
82
|
+
return imageBytes
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static func bitmapToBytes(_ bitmap: EscPosBitmap) -> [UInt8] {
|
|
86
|
+
let bitmapWidth = bitmap.width
|
|
87
|
+
let bitmapHeight = bitmap.height
|
|
88
|
+
let bytesByLine = Int((Float(bitmapWidth) / 8).rounded(.up))
|
|
89
|
+
var imageBytes = initGSv0Command(bytesByLine: bytesByLine, bitmapHeight: bitmapHeight)
|
|
90
|
+
|
|
91
|
+
var index = 8
|
|
92
|
+
var greyscaleCoefficientInit = 0
|
|
93
|
+
let gradientStep = 6
|
|
94
|
+
let colorLevelStep = 765.0 / Double(15 * gradientStep + gradientStep - 1)
|
|
95
|
+
|
|
96
|
+
for posY in 0..<bitmapHeight {
|
|
97
|
+
var greyscaleCoefficient = greyscaleCoefficientInit
|
|
98
|
+
let greyscaleLine = posY % gradientStep
|
|
99
|
+
var column = 0
|
|
100
|
+
while column < bitmapWidth {
|
|
101
|
+
var byte = 0
|
|
102
|
+
for bit in 0..<8 {
|
|
103
|
+
let posX = column + bit
|
|
104
|
+
if posX < bitmapWidth {
|
|
105
|
+
let color = bitmap.pixel(posX, posY)
|
|
106
|
+
let red = Int((color >> 16) & 255)
|
|
107
|
+
let green = Int((color >> 8) & 255)
|
|
108
|
+
let blue = Int(color & 255)
|
|
109
|
+
if Double(red + green + blue) < Double(greyscaleCoefficient * gradientStep + greyscaleLine) * colorLevelStep {
|
|
110
|
+
byte |= 1 << (7 - bit)
|
|
111
|
+
}
|
|
112
|
+
greyscaleCoefficient += 5
|
|
113
|
+
if greyscaleCoefficient > 15 {
|
|
114
|
+
greyscaleCoefficient -= 16
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
imageBytes[index] = UInt8(truncatingIfNeeded: byte)
|
|
119
|
+
index += 1
|
|
120
|
+
column += 8
|
|
121
|
+
}
|
|
122
|
+
greyscaleCoefficientInit += 2
|
|
123
|
+
if greyscaleCoefficientInit > 15 {
|
|
124
|
+
greyscaleCoefficientInit = 0
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return imageBytes
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static func convertGSv0ToEscAsterisk(_ bytes: [UInt8]) -> [[UInt8]] {
|
|
131
|
+
let xL = Int(bytes[4])
|
|
132
|
+
let xH = Int(bytes[5])
|
|
133
|
+
let yL = Int(bytes[6])
|
|
134
|
+
let yH = Int(bytes[7])
|
|
135
|
+
let bytesByLine = xH * 256 + xL
|
|
136
|
+
let dotsByLine = bytesByLine * 8
|
|
137
|
+
let nH = dotsByLine / 256
|
|
138
|
+
let nL = dotsByLine % 256
|
|
139
|
+
let imageHeight = yH * 256 + yL
|
|
140
|
+
let imageLineHeightCount = Int((Double(imageHeight) / 24.0).rounded(.up))
|
|
141
|
+
let imageBytesSize = 6 + bytesByLine * 24
|
|
142
|
+
|
|
143
|
+
var returnedBytes: [[UInt8]] = [lineSpacing24]
|
|
144
|
+
for lineIndex in 0..<imageLineHeightCount {
|
|
145
|
+
let pxBaseRow = lineIndex * 24
|
|
146
|
+
var imageBytes = [UInt8](repeating: 0, count: imageBytesSize)
|
|
147
|
+
imageBytes[0] = 0x1B
|
|
148
|
+
imageBytes[1] = 0x2A
|
|
149
|
+
imageBytes[2] = 0x21
|
|
150
|
+
imageBytes[3] = UInt8(truncatingIfNeeded: nL)
|
|
151
|
+
imageBytes[4] = UInt8(truncatingIfNeeded: nH)
|
|
152
|
+
for byteIndex in 5..<imageBytes.count {
|
|
153
|
+
let imgByte = byteIndex - 5
|
|
154
|
+
let byteRow = imgByte % 3
|
|
155
|
+
let pxColumn = imgByte / 3
|
|
156
|
+
let bitColumn = 1 << (7 - pxColumn % 8)
|
|
157
|
+
let pxRow = pxBaseRow + byteRow * 8
|
|
158
|
+
for bit in 0..<8 {
|
|
159
|
+
let indexBytes = bytesByLine * (pxRow + bit) + pxColumn / 8 + 8
|
|
160
|
+
if indexBytes >= bytes.count {
|
|
161
|
+
break
|
|
162
|
+
}
|
|
163
|
+
let isBlack = (Int(bytes[indexBytes]) & bitColumn) == bitColumn
|
|
164
|
+
if isBlack {
|
|
165
|
+
imageBytes[byteIndex] |= UInt8(truncatingIfNeeded: 1 << (7 - bit))
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
imageBytes[imageBytes.count - 1] = lineFeed
|
|
170
|
+
returnedBytes.append(imageBytes)
|
|
171
|
+
}
|
|
172
|
+
returnedBytes.append(lineSpacing30)
|
|
173
|
+
return returnedBytes
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static func qrCodeDataToBytes(_ data: String, size: Int) throws -> [UInt8] {
|
|
177
|
+
let matrix = try EscPosQRCode.matrix(for: data)
|
|
178
|
+
let width = matrix.count
|
|
179
|
+
let height = matrix.count
|
|
180
|
+
guard width > 0 else {
|
|
181
|
+
return initGSv0Command(bytesByLine: 0, bitmapHeight: 0)
|
|
182
|
+
}
|
|
183
|
+
let coefficient = JavaMath.round(Float(size) / Float(width))
|
|
184
|
+
let imageWidth = width * coefficient
|
|
185
|
+
let imageHeight = height * coefficient
|
|
186
|
+
let bytesByLine = Int((Float(imageWidth) / 8).rounded(.up))
|
|
187
|
+
|
|
188
|
+
if coefficient < 1 {
|
|
189
|
+
return initGSv0Command(bytesByLine: 0, bitmapHeight: 0)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
var imageBytes = initGSv0Command(bytesByLine: bytesByLine, bitmapHeight: imageHeight)
|
|
193
|
+
var index = 8
|
|
194
|
+
for y in 0..<height {
|
|
195
|
+
var lineBytes = [UInt8](repeating: 0, count: bytesByLine)
|
|
196
|
+
var x = -1
|
|
197
|
+
var multipleX = coefficient
|
|
198
|
+
var isBlack = false
|
|
199
|
+
for byteIndex in 0..<bytesByLine {
|
|
200
|
+
var byte = 0
|
|
201
|
+
for bit in 0..<8 {
|
|
202
|
+
if multipleX == coefficient {
|
|
203
|
+
x += 1
|
|
204
|
+
isBlack = x < width && matrix[y][x]
|
|
205
|
+
multipleX = 0
|
|
206
|
+
}
|
|
207
|
+
if isBlack {
|
|
208
|
+
byte |= 1 << (7 - bit)
|
|
209
|
+
}
|
|
210
|
+
multipleX += 1
|
|
211
|
+
}
|
|
212
|
+
lineBytes[byteIndex] = UInt8(truncatingIfNeeded: byte)
|
|
213
|
+
}
|
|
214
|
+
for _ in 0..<coefficient {
|
|
215
|
+
imageBytes.replaceSubrange(index..<(index + lineBytes.count), with: lineBytes)
|
|
216
|
+
index += lineBytes.count
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return imageBytes
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@discardableResult
|
|
223
|
+
func connect() throws -> EscPosPrinterCommands {
|
|
224
|
+
try printerConnection.connect()
|
|
225
|
+
return self
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
func disconnect() {
|
|
229
|
+
printerConnection.disconnect()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private func requireConnection() throws {
|
|
233
|
+
guard printerConnection.isConnected else {
|
|
234
|
+
printerConnection.clearBuffer()
|
|
235
|
+
throw EscPosError.connection("Unable to send data to device.")
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@discardableResult
|
|
240
|
+
func reset() throws -> EscPosPrinterCommands {
|
|
241
|
+
try requireConnection()
|
|
242
|
+
printerConnection.write(EscPosPrinterCommands.resetPrinter)
|
|
243
|
+
return self
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@discardableResult
|
|
247
|
+
func setAlign(_ align: [UInt8]) throws -> EscPosPrinterCommands {
|
|
248
|
+
try requireConnection()
|
|
249
|
+
printerConnection.write(align)
|
|
250
|
+
return self
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@discardableResult
|
|
254
|
+
func printText(
|
|
255
|
+
_ text: JavaString,
|
|
256
|
+
textSize: [UInt8]? = nil,
|
|
257
|
+
textColor: [UInt8]? = nil,
|
|
258
|
+
textReverseColor: [UInt8]? = nil,
|
|
259
|
+
textBold: [UInt8]? = nil,
|
|
260
|
+
textUnderline: [UInt8]? = nil,
|
|
261
|
+
textDoubleStrike: [UInt8]? = nil
|
|
262
|
+
) throws -> EscPosPrinterCommands {
|
|
263
|
+
try requireConnection()
|
|
264
|
+
|
|
265
|
+
let size = textSize ?? EscPosPrinterCommands.textSizeNormal
|
|
266
|
+
let color = textColor ?? EscPosPrinterCommands.textColorBlack
|
|
267
|
+
let reverseColor = textReverseColor ?? EscPosPrinterCommands.textColorReverseOff
|
|
268
|
+
let bold = textBold ?? EscPosPrinterCommands.textWeightNormal
|
|
269
|
+
let underline = textUnderline ?? EscPosPrinterCommands.textUnderlineOff
|
|
270
|
+
let doubleStrike = textDoubleStrike ?? EscPosPrinterCommands.textDoubleStrikeOff
|
|
271
|
+
|
|
272
|
+
let textBytes = try charsetEncoding.encode(text)
|
|
273
|
+
printerConnection.write(charsetEncoding.command)
|
|
274
|
+
|
|
275
|
+
if currentTextSize != size {
|
|
276
|
+
printerConnection.write(size)
|
|
277
|
+
currentTextSize = size
|
|
278
|
+
}
|
|
279
|
+
if currentTextDoubleStrike != doubleStrike {
|
|
280
|
+
printerConnection.write(doubleStrike)
|
|
281
|
+
currentTextDoubleStrike = doubleStrike
|
|
282
|
+
}
|
|
283
|
+
if currentTextUnderline != underline {
|
|
284
|
+
printerConnection.write(underline)
|
|
285
|
+
currentTextUnderline = underline
|
|
286
|
+
}
|
|
287
|
+
if currentTextBold != bold {
|
|
288
|
+
printerConnection.write(bold)
|
|
289
|
+
currentTextBold = bold
|
|
290
|
+
}
|
|
291
|
+
if currentTextColor != color {
|
|
292
|
+
printerConnection.write(color)
|
|
293
|
+
currentTextColor = color
|
|
294
|
+
}
|
|
295
|
+
if currentTextReverseColor != reverseColor {
|
|
296
|
+
printerConnection.write(reverseColor)
|
|
297
|
+
currentTextReverseColor = reverseColor
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
printerConnection.write(textBytes)
|
|
301
|
+
return self
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
@discardableResult
|
|
305
|
+
func printImage(_ image: [UInt8]) throws -> EscPosPrinterCommands {
|
|
306
|
+
try requireConnection()
|
|
307
|
+
let bytesToPrint = useEscAsteriskCommand ? EscPosPrinterCommands.convertGSv0ToEscAsterisk(image) : [image]
|
|
308
|
+
for bytes in bytesToPrint {
|
|
309
|
+
printerConnection.write(bytes)
|
|
310
|
+
try printerConnection.send()
|
|
311
|
+
}
|
|
312
|
+
return self
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@discardableResult
|
|
316
|
+
func printBarcode(_ barcode: EscPosBarcode) throws -> EscPosPrinterCommands {
|
|
317
|
+
try requireConnection()
|
|
318
|
+
let code = barcode.code
|
|
319
|
+
let barcodeLength = barcode.codeLength
|
|
320
|
+
var barcodeCommand: [UInt8] = [0x1D, 0x6B, UInt8(truncatingIfNeeded: barcode.barcodeType), UInt8(truncatingIfNeeded: barcodeLength)]
|
|
321
|
+
for index in 0..<barcodeLength {
|
|
322
|
+
barcodeCommand.append(UInt8(truncatingIfNeeded: code.units[index]))
|
|
323
|
+
}
|
|
324
|
+
printerConnection.write([0x1D, 0x48, UInt8(truncatingIfNeeded: barcode.textPosition)])
|
|
325
|
+
printerConnection.write([0x1D, 0x77, UInt8(truncatingIfNeeded: barcode.colWidth)])
|
|
326
|
+
printerConnection.write([0x1D, 0x68, UInt8(truncatingIfNeeded: barcode.height)])
|
|
327
|
+
printerConnection.write(barcodeCommand)
|
|
328
|
+
return self
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
@discardableResult
|
|
332
|
+
func newLine(_ align: [UInt8]? = nil) throws -> EscPosPrinterCommands {
|
|
333
|
+
try requireConnection()
|
|
334
|
+
printerConnection.write([EscPosPrinterCommands.lineFeed])
|
|
335
|
+
try printerConnection.send()
|
|
336
|
+
if let align = align {
|
|
337
|
+
printerConnection.write(align)
|
|
338
|
+
}
|
|
339
|
+
return self
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
@discardableResult
|
|
343
|
+
func feedPaper(_ dots: Int) throws -> EscPosPrinterCommands {
|
|
344
|
+
try requireConnection()
|
|
345
|
+
if dots > 0 {
|
|
346
|
+
printerConnection.write([0x1B, 0x4A, UInt8(truncatingIfNeeded: dots)])
|
|
347
|
+
try printerConnection.send(dots)
|
|
348
|
+
}
|
|
349
|
+
return self
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
@discardableResult
|
|
353
|
+
func cutPaper() throws -> EscPosPrinterCommands {
|
|
354
|
+
try requireConnection()
|
|
355
|
+
printerConnection.write([0x1D, 0x56, 0x01])
|
|
356
|
+
try printerConnection.send(100)
|
|
357
|
+
return self
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
@discardableResult
|
|
361
|
+
func openCashBox() throws -> EscPosPrinterCommands {
|
|
362
|
+
try requireConnection()
|
|
363
|
+
printerConnection.write([0x1B, 0x70, 0x00, 0x3C, 0xFF])
|
|
364
|
+
try printerConnection.send(100)
|
|
365
|
+
return self
|
|
366
|
+
}
|
|
367
|
+
}
|