@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.
Files changed (35) hide show
  1. package/BricksSoftCapacitorThermalPrinter.podspec +17 -0
  2. package/LICENSE +201 -0
  3. package/Package.swift +13 -0
  4. package/README.md +80 -2
  5. package/android/build.gradle +40 -0
  6. package/android/src/main/AndroidManifest.xml +8 -0
  7. package/android/src/main/java/com/brickssoft/thermalprinter/ThermalPrinterPlugin.java +463 -0
  8. package/dist/esm/definitions.d.ts +73 -0
  9. package/dist/esm/definitions.js +1 -0
  10. package/dist/esm/index.d.ts +3 -0
  11. package/dist/esm/index.js +23 -0
  12. package/dist/esm/web.d.ts +13 -0
  13. package/dist/esm/web.js +33 -0
  14. package/dist/plugin.cjs.js +65 -0
  15. package/dist/plugin.js +68 -0
  16. package/ios/Sources/ThermalPrinter/EscPosBarcode.swift +121 -0
  17. package/ios/Sources/ThermalPrinter/EscPosBitmap.swift +178 -0
  18. package/ios/Sources/ThermalPrinter/EscPosBluetooth.swift +341 -0
  19. package/ios/Sources/ThermalPrinter/EscPosBluetoothConnection.swift +298 -0
  20. package/ios/Sources/ThermalPrinter/EscPosBluetoothRules.swift +78 -0
  21. package/ios/Sources/ThermalPrinter/EscPosCharsetEncoding.swift +115 -0
  22. package/ios/Sources/ThermalPrinter/EscPosCoreBluetoothLink.swift +287 -0
  23. package/ios/Sources/ThermalPrinter/EscPosDeviceConnection.swift +47 -0
  24. package/ios/Sources/ThermalPrinter/EscPosJavaCompat.swift +213 -0
  25. package/ios/Sources/ThermalPrinter/EscPosKnownPrinterStore.swift +44 -0
  26. package/ios/Sources/ThermalPrinter/EscPosPrinter.swift +55 -0
  27. package/ios/Sources/ThermalPrinter/EscPosPrinterCommands.swift +367 -0
  28. package/ios/Sources/ThermalPrinter/EscPosPrinterSize.swift +53 -0
  29. package/ios/Sources/ThermalPrinter/EscPosQRCode.swift +70 -0
  30. package/ios/Sources/ThermalPrinter/EscPosQueue.swift +30 -0
  31. package/ios/Sources/ThermalPrinter/EscPosTcpConnection.swift +265 -0
  32. package/ios/Sources/ThermalPrinter/PrinterTextParser.swift +622 -0
  33. package/ios/Sources/ThermalPrinter/ThermalPrinterPlugin.swift +85 -0
  34. package/ios/Sources/ThermalPrinter/ThermalPrinterService.swift +380 -0
  35. package/package.json +61 -4
package/dist/plugin.js ADDED
@@ -0,0 +1,68 @@
1
+ var capacitorThermalPrinter = (function (exports, core) {
2
+ 'use strict';
3
+
4
+ const native = core.registerPlugin('ThermalPrinter', {
5
+ web: () => Promise.resolve().then(function () { return web; }).then(m => new m.ThermalPrinterWeb()),
6
+ });
7
+ const unwrap = (request) => request.then(result => result.value).catch(error => {
8
+ if (error && typeof error === 'object' && error.data && typeof error.data === 'object') {
9
+ Object.assign(error, error.data);
10
+ }
11
+ throw error;
12
+ });
13
+ const ThermalPrinter = {
14
+ listPrinters: data => unwrap(native.listPrinters(data)),
15
+ printFormattedText: data => unwrap(native.printFormattedText(data)),
16
+ printFormattedTextAndCut: data => unwrap(native.printFormattedTextAndCut(data)),
17
+ getEncoding: data => unwrap(native.getEncoding(data)),
18
+ disconnectPrinter: data => unwrap(native.disconnectPrinter(data)),
19
+ requestPermissions: data => unwrap(native.requestPermissions(data)),
20
+ requestBTPermissions: data => unwrap(native.requestBTPermissions(data)),
21
+ requestUSBPermissions: data => unwrap(native.requestUSBPermissions(data)),
22
+ bitmapToHexadecimalString: data => unwrap(native.bitmapToHexadecimalString(data)),
23
+ bytesToHexadecimalString: data => unwrap(native.bytesToHexadecimalString(data)),
24
+ };
25
+
26
+ class ThermalPrinterWeb extends core.WebPlugin {
27
+ async listPrinters() {
28
+ throw this.unavailable('Thermal printing is unavailable on the web.');
29
+ }
30
+ async printFormattedText() {
31
+ throw this.unavailable('Thermal printing is unavailable on the web.');
32
+ }
33
+ async printFormattedTextAndCut() {
34
+ throw this.unavailable('Thermal printing is unavailable on the web.');
35
+ }
36
+ async getEncoding() {
37
+ throw this.unavailable('Thermal printing is unavailable on the web.');
38
+ }
39
+ async disconnectPrinter() {
40
+ throw this.unavailable('Thermal printing is unavailable on the web.');
41
+ }
42
+ async requestPermissions() {
43
+ throw this.unavailable('Thermal printing is unavailable on the web.');
44
+ }
45
+ async requestBTPermissions() {
46
+ throw this.unavailable('Thermal printing is unavailable on the web.');
47
+ }
48
+ async requestUSBPermissions() {
49
+ throw this.unavailable('Thermal printing is unavailable on the web.');
50
+ }
51
+ async bitmapToHexadecimalString() {
52
+ throw this.unavailable('Thermal printing is unavailable on the web.');
53
+ }
54
+ async bytesToHexadecimalString() {
55
+ throw this.unavailable('Thermal printing is unavailable on the web.');
56
+ }
57
+ }
58
+
59
+ var web = /*#__PURE__*/Object.freeze({
60
+ __proto__: null,
61
+ ThermalPrinterWeb: ThermalPrinterWeb
62
+ });
63
+
64
+ exports.ThermalPrinter = ThermalPrinter;
65
+
66
+ return exports;
67
+
68
+ })({}, capacitorExports);
@@ -0,0 +1,121 @@
1
+ import Foundation
2
+
3
+ class EscPosBarcode {
4
+ let barcodeType: Int
5
+ var code: JavaString
6
+ let height: Int
7
+ let textPosition: Int
8
+ private(set) var colWidth = 0
9
+
10
+ var codeLength: Int { code.length }
11
+ var colsCount: Int { (codeLength + 5) * 11 }
12
+
13
+ init(printerSize: EscPosPrinterSize, barcodeType: Int, code: JavaString, widthMM: Float, heightMM: Float, textPosition: Int) throws {
14
+ self.barcodeType = barcodeType
15
+ self.code = code
16
+ self.height = printerSize.mmToPx(heightMM)
17
+ self.textPosition = textPosition
18
+ }
19
+
20
+ func computeColWidth(printerSize: EscPosPrinterSize, widthMM requestedWidthMM: Float) throws {
21
+ var widthMM = requestedWidthMM
22
+ if widthMM == 0 {
23
+ widthMM = printerSize.printerWidthMM * 0.7
24
+ }
25
+ let wantedPxWidth = widthMM > printerSize.printerWidthMM ? printerSize.printerWidthPx : printerSize.mmToPx(widthMM)
26
+ var width = JavaMath.round(Double(wantedPxWidth) / Double(colsCount))
27
+ if width * colsCount > printerSize.printerWidthPx {
28
+ width -= 1
29
+ }
30
+ if width == 0 {
31
+ throw EscPosError.barcode("Barcode is too long for the paper size.")
32
+ }
33
+ colWidth = width
34
+ }
35
+
36
+ static func make(type: String, printerSize: EscPosPrinterSize, code: JavaString, widthMM: Float, heightMM: Float, textPosition: Int) throws -> EscPosBarcode {
37
+ let barcode: EscPosBarcode
38
+ switch type {
39
+ case "ean8":
40
+ barcode = try EscPosBarcodeNumber(printerSize: printerSize, barcodeType: EscPosPrinterCommands.barcodeTypeEAN8, code: code, widthMM: widthMM, heightMM: heightMM, textPosition: textPosition, fixedLength: 8)
41
+ case "ean13":
42
+ barcode = try EscPosBarcodeNumber(printerSize: printerSize, barcodeType: EscPosPrinterCommands.barcodeTypeEAN13, code: code, widthMM: widthMM, heightMM: heightMM, textPosition: textPosition, fixedLength: 13)
43
+ case "upca":
44
+ barcode = try EscPosBarcodeNumber(printerSize: printerSize, barcodeType: EscPosPrinterCommands.barcodeTypeUPCA, code: code, widthMM: widthMM, heightMM: heightMM, textPosition: textPosition, fixedLength: 12)
45
+ case "upce":
46
+ barcode = try EscPosBarcodeUPCE(printerSize: printerSize, code: code, widthMM: widthMM, heightMM: heightMM, textPosition: textPosition)
47
+ case "128":
48
+ barcode = try EscPosBarcode(printerSize: printerSize, barcodeType: EscPosPrinterCommands.barcodeType128, code: code, widthMM: widthMM, heightMM: heightMM, textPosition: textPosition)
49
+ try barcode.computeColWidth(printerSize: printerSize, widthMM: widthMM)
50
+ default:
51
+ throw EscPosError.parser("Invalid barcode attribute : type")
52
+ }
53
+ return barcode
54
+ }
55
+ }
56
+
57
+ final class EscPosBarcodeNumber: EscPosBarcode {
58
+ private let fixedLength: Int
59
+
60
+ override var codeLength: Int { fixedLength }
61
+ override var colsCount: Int { codeLength * 7 + 11 }
62
+
63
+ init(printerSize: EscPosPrinterSize, barcodeType: Int, code: JavaString, widthMM: Float, heightMM: Float, textPosition: Int, fixedLength: Int) throws {
64
+ self.fixedLength = fixedLength
65
+ try super.init(printerSize: printerSize, barcodeType: barcodeType, code: code, widthMM: widthMM, heightMM: heightMM, textPosition: textPosition)
66
+ try computeColWidth(printerSize: printerSize, widthMM: widthMM)
67
+ try checkCode()
68
+ }
69
+
70
+ private func checkCode() throws {
71
+ let checkedLength = codeLength - 1
72
+ if code.length < checkedLength {
73
+ throw EscPosError.barcode("Code is too short for the barcode type.")
74
+ }
75
+ let digits = try code.substring(0, checkedLength)
76
+ var total = 0
77
+ for index in 0..<checkedLength {
78
+ let position = checkedLength - 1 - index
79
+ guard let digit = EscPosBarcodeNumber.digit(digits.units[position]) else {
80
+ throw EscPosError.barcode("Invalid barcode number")
81
+ }
82
+ total += index % 2 == 0 ? 3 * digit : digit
83
+ }
84
+ var key = String(10 - (total % 10))
85
+ if key.count == 2 {
86
+ key = "0"
87
+ }
88
+ code = JavaString(digits.string + key)
89
+ }
90
+
91
+ static func digit(_ unit: UInt16) -> Int? {
92
+ guard let scalar = Unicode.Scalar(unit),
93
+ scalar.properties.generalCategory == .decimalNumber,
94
+ let value = scalar.properties.numericValue
95
+ else {
96
+ return nil
97
+ }
98
+ return Int(value)
99
+ }
100
+ }
101
+
102
+ final class EscPosBarcodeUPCE: EscPosBarcode {
103
+ override var codeLength: Int { 6 }
104
+ override var colsCount: Int { codeLength * 7 + 16 }
105
+
106
+ init(printerSize: EscPosPrinterSize, code: JavaString, widthMM: Float, heightMM: Float, textPosition: Int) throws {
107
+ try super.init(printerSize: printerSize, barcodeType: EscPosPrinterCommands.barcodeTypeUPCE, code: code, widthMM: widthMM, heightMM: heightMM, textPosition: textPosition)
108
+ try computeColWidth(printerSize: printerSize, widthMM: widthMM)
109
+ try checkCode()
110
+ }
111
+
112
+ private func checkCode() throws {
113
+ if code.length < codeLength {
114
+ throw EscPosError.barcode("Code is too short for the barcode type.")
115
+ }
116
+ code = try code.substring(0, codeLength)
117
+ for unit in code.units where EscPosBarcodeNumber.digit(unit) == nil {
118
+ throw EscPosError.barcode("Invalid barcode number")
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,178 @@
1
+ import CoreGraphics
2
+ import Foundation
3
+ import ImageIO
4
+
5
+ struct EscPosBitmap {
6
+ let width: Int
7
+ let height: Int
8
+ let pixels: [UInt32]
9
+
10
+ init(width: Int, height: Int, pixels: [UInt32]) {
11
+ self.width = width
12
+ self.height = height
13
+ self.pixels = pixels
14
+ }
15
+
16
+ func pixel(_ x: Int, _ y: Int) -> UInt32 {
17
+ pixels[y * width + x]
18
+ }
19
+
20
+ static func decode(_ data: Data) -> EscPosBitmap? {
21
+ guard let source = CGImageSourceCreateWithData(data as CFData, nil),
22
+ CGImageSourceGetCount(source) > 0,
23
+ let image = CGImageSourceCreateImageAtIndex(source, 0, [kCGImageSourceShouldCache: false] as CFDictionary)
24
+ else {
25
+ return nil
26
+ }
27
+ return EscPosBitmap(image: image)
28
+ }
29
+
30
+ init?(image: CGImage) {
31
+ let width = image.width
32
+ let height = image.height
33
+ guard width > 0, height > 0 else { return nil }
34
+ let isGray = image.colorSpace?.model == .monochrome
35
+ let hasAlpha: Bool
36
+ switch image.alphaInfo {
37
+ case .none, .noneSkipFirst, .noneSkipLast:
38
+ hasAlpha = false
39
+ default:
40
+ hasAlpha = true
41
+ }
42
+ if isGray && !hasAlpha, let gray = EscPosBitmap.renderGray(image) {
43
+ self.init(width: width, height: height, pixels: gray.map { value in
44
+ let level = UInt32(value)
45
+ return 0xFF00_0000 | level << 16 | level << 8 | level
46
+ })
47
+ return
48
+ }
49
+ guard let rgba = EscPosBitmap.renderRGBA(image, width: width, height: height) else { return nil }
50
+ var pixels = [UInt32](repeating: 0, count: width * height)
51
+ for index in 0..<(width * height) {
52
+ let alpha = UInt32(rgba[index * 4 + 3])
53
+ var red = UInt32(rgba[index * 4])
54
+ var green = UInt32(rgba[index * 4 + 1])
55
+ var blue = UInt32(rgba[index * 4 + 2])
56
+ if alpha == 0 {
57
+ red = 0
58
+ green = 0
59
+ blue = 0
60
+ } else if alpha < 255 {
61
+ red = min(255, (red * 255 + alpha / 2) / alpha)
62
+ green = min(255, (green * 255 + alpha / 2) / alpha)
63
+ blue = min(255, (blue * 255 + alpha / 2) / alpha)
64
+ }
65
+ pixels[index] = alpha << 24 | red << 16 | green << 8 | blue
66
+ }
67
+ self.init(width: width, height: height, pixels: pixels)
68
+ }
69
+
70
+ func scaled(width newWidth: Int, height newHeight: Int) -> EscPosBitmap {
71
+ guard newWidth > 0, newHeight > 0, let image = cgImage() else {
72
+ return EscPosBitmap(width: max(newWidth, 0), height: max(newHeight, 0), pixels: [UInt32](repeating: 0, count: max(newWidth, 0) * max(newHeight, 0)))
73
+ }
74
+ let bytesPerRow = newWidth * 4
75
+ var buffer = [UInt8](repeating: 0, count: bytesPerRow * newHeight)
76
+ let drawn: Bool = buffer.withUnsafeMutableBytes { raw in
77
+ guard let context = CGContext(
78
+ data: raw.baseAddress,
79
+ width: newWidth,
80
+ height: newHeight,
81
+ bitsPerComponent: 8,
82
+ bytesPerRow: bytesPerRow,
83
+ space: CGColorSpaceCreateDeviceRGB(),
84
+ bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue
85
+ ) else { return false }
86
+ context.interpolationQuality = .medium
87
+ context.setBlendMode(.copy)
88
+ context.draw(image, in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
89
+ return true
90
+ }
91
+ guard drawn, let scaledImage = EscPosBitmap.makeImage(rgba: buffer, width: newWidth, height: newHeight),
92
+ let result = EscPosBitmap(image: scaledImage)
93
+ else {
94
+ return EscPosBitmap(width: newWidth, height: newHeight, pixels: [UInt32](repeating: 0, count: newWidth * newHeight))
95
+ }
96
+ return result
97
+ }
98
+
99
+ private func cgImage() -> CGImage? {
100
+ var rgba = [UInt8](repeating: 0, count: width * height * 4)
101
+ for index in 0..<(width * height) {
102
+ let pixel = pixels[index]
103
+ let alpha = (pixel >> 24) & 0xFF
104
+ rgba[index * 4] = UInt8((((pixel >> 16) & 0xFF) * alpha + 127) / 255)
105
+ rgba[index * 4 + 1] = UInt8((((pixel >> 8) & 0xFF) * alpha + 127) / 255)
106
+ rgba[index * 4 + 2] = UInt8(((pixel & 0xFF) * alpha + 127) / 255)
107
+ rgba[index * 4 + 3] = UInt8(alpha)
108
+ }
109
+ return EscPosBitmap.makeImage(rgba: rgba, width: width, height: height)
110
+ }
111
+
112
+ private static func makeImage(rgba: [UInt8], width: Int, height: Int) -> CGImage? {
113
+ guard let provider = CGDataProvider(data: Data(rgba) as CFData) else { return nil }
114
+ return CGImage(
115
+ width: width,
116
+ height: height,
117
+ bitsPerComponent: 8,
118
+ bitsPerPixel: 32,
119
+ bytesPerRow: width * 4,
120
+ space: CGColorSpaceCreateDeviceRGB(),
121
+ bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue),
122
+ provider: provider,
123
+ decode: nil,
124
+ shouldInterpolate: false,
125
+ intent: .defaultIntent
126
+ )
127
+ }
128
+
129
+ private static func renderRGBA(_ image: CGImage, width: Int, height: Int) -> [UInt8]? {
130
+ let space: CGColorSpace
131
+ if let imageSpace = image.colorSpace, imageSpace.model == .rgb {
132
+ space = imageSpace
133
+ } else {
134
+ space = CGColorSpace(name: CGColorSpace.sRGB) ?? CGColorSpaceCreateDeviceRGB()
135
+ }
136
+ let bytesPerRow = width * 4
137
+ var buffer = [UInt8](repeating: 0, count: bytesPerRow * height)
138
+ let drawn: Bool = buffer.withUnsafeMutableBytes { raw in
139
+ guard let context = CGContext(
140
+ data: raw.baseAddress,
141
+ width: width,
142
+ height: height,
143
+ bitsPerComponent: 8,
144
+ bytesPerRow: bytesPerRow,
145
+ space: space,
146
+ bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue
147
+ ) else { return false }
148
+ context.interpolationQuality = .none
149
+ context.setBlendMode(.copy)
150
+ context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
151
+ return true
152
+ }
153
+ return drawn ? buffer : nil
154
+ }
155
+
156
+ private static func renderGray(_ image: CGImage) -> [UInt8]? {
157
+ guard let space = image.colorSpace else { return nil }
158
+ let width = image.width
159
+ let height = image.height
160
+ var buffer = [UInt8](repeating: 0, count: width * height)
161
+ let drawn: Bool = buffer.withUnsafeMutableBytes { raw in
162
+ guard let context = CGContext(
163
+ data: raw.baseAddress,
164
+ width: width,
165
+ height: height,
166
+ bitsPerComponent: 8,
167
+ bytesPerRow: width,
168
+ space: space,
169
+ bitmapInfo: CGImageAlphaInfo.none.rawValue
170
+ ) else { return false }
171
+ context.interpolationQuality = .none
172
+ context.setBlendMode(.copy)
173
+ context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
174
+ return true
175
+ }
176
+ return drawn ? buffer : nil
177
+ }
178
+ }