expo-hiu-print 0.1.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/.eslintrc.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ root: true,
3
+ extends: ['universe/native', 'universe/web'],
4
+ ignorePatterns: ['build'],
5
+ };
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # expo-hiu-print
2
+
3
+ Connect printer
4
+
5
+ # API documentation
6
+
7
+ - [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/hiu-print/)
8
+ - [Documentation for the main branch](https://docs.expo.dev/versions/unversioned/sdk/hiu-print/)
9
+
10
+ # Installation in managed Expo projects
11
+
12
+ For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](#api-documentation). If you follow the link and there is no documentation available then this library is not yet usable within managed projects — it is likely to be included in an upcoming Expo SDK release.
13
+
14
+ # Installation in bare React Native projects
15
+
16
+ For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing.
17
+
18
+ ### Add the package to your npm dependencies
19
+
20
+ ```
21
+ npm install expo-hiu-print
22
+ ```
23
+
24
+ ### Configure for Android
25
+
26
+
27
+
28
+
29
+ ### Configure for iOS
30
+
31
+ Run `npx pod-install` after installing the npm package.
32
+
33
+ # Contributing
34
+
35
+ Contributions are very welcome! Please refer to guidelines described in the [contributing guide]( https://github.com/expo/expo#contributing).
@@ -0,0 +1,47 @@
1
+ apply plugin: 'com.android.library'
2
+
3
+ group = 'expo.modules.hiuprint'
4
+ version = '0.1.0'
5
+
6
+ def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
7
+ apply from: expoModulesCorePlugin
8
+ applyKotlinExpoModulesCorePlugin()
9
+ useCoreDependencies()
10
+ useExpoPublishing()
11
+
12
+ // If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
13
+ // The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
14
+ // Most of the time, you may like to manage the Android SDK versions yourself.
15
+ def useManagedAndroidSdkVersions = false
16
+ if (useManagedAndroidSdkVersions) {
17
+ useDefaultAndroidSdkVersions()
18
+ } else {
19
+ buildscript {
20
+ // Simple helper that allows the root project to override versions declared by this library.
21
+ ext.safeExtGet = { prop, fallback ->
22
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
23
+ }
24
+ }
25
+ project.android {
26
+ compileSdkVersion = safeExtGet("compileSdkVersion", 36)
27
+ defaultConfig {
28
+ minSdkVersion = safeExtGet("minSdkVersion", 24)
29
+ targetSdkVersion = safeExtGet("targetSdkVersion", 36)
30
+ }
31
+ }
32
+ }
33
+
34
+ android {
35
+ namespace = "expo.modules.hiuprint"
36
+ defaultConfig {
37
+ versionCode = 1
38
+ versionName = "0.1.0"
39
+ }
40
+ lintOptions {
41
+ abortOnError = false
42
+ }
43
+ }
44
+
45
+ dependencies {
46
+ implementation 'com.google.zxing:core:3.5.1'
47
+ }
@@ -0,0 +1,2 @@
1
+ <manifest>
2
+ </manifest>
@@ -0,0 +1,175 @@
1
+ package expo.modules.hiuprint
2
+
3
+ import android.graphics.*
4
+ import android.text.StaticLayout
5
+ import android.text.Layout
6
+ import android.text.TextPaint
7
+ import com.google.zxing.BarcodeFormat
8
+ import com.google.zxing.qrcode.QRCodeWriter
9
+
10
+ class BitmapRenderer(private val paperWidthPx: Int) {
11
+
12
+ fun render(elements: List<Map<String, Any>>): Bitmap {
13
+ val notoSans = Typeface.create("NotoSans", Typeface.NORMAL)
14
+ val notoSansBold = Typeface.create("NotoSans", Typeface.BOLD)
15
+ val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
16
+ color = Color.BLACK
17
+ textSize = 24f
18
+ typeface = notoSans
19
+ style = Paint.Style.FILL
20
+ isFakeBoldText = true // Make text bolder for thermal printers
21
+ strokeWidth = 2f // Increase stroke width for more solid text
22
+ }
23
+ val bitmap = Bitmap.createBitmap(paperWidthPx, 2000, Bitmap.Config.ARGB_8888)
24
+ val canvas = Canvas(bitmap)
25
+ canvas.drawColor(Color.WHITE)
26
+
27
+ var yOffset = 0f
28
+
29
+ elements.forEach { element ->
30
+ when (element["type"]) {
31
+ "text" -> {
32
+ val text = element["value"] as String
33
+ val align = element["align"] as String? ?: "left"
34
+ val bold = element["bold"] as Boolean? ?: false
35
+ val size = element["size"] as String? ?: "normal"
36
+
37
+ textPaint.typeface = if (bold) Typeface.create("NotoSans", Typeface.BOLD) else notoSans
38
+ textPaint.textSize = when (size) {
39
+ "small" -> 20f
40
+ "large" -> 32f
41
+ else -> 24f
42
+ }
43
+ textPaint.textAlign = when (align) {
44
+ "center" -> Paint.Align.CENTER
45
+ "right" -> Paint.Align.RIGHT
46
+ else -> Paint.Align.LEFT
47
+ }
48
+
49
+ val x = when (align) {
50
+ "center" -> paperWidthPx / 2f
51
+ "right" -> paperWidthPx.toFloat()
52
+ else -> 0f
53
+ }
54
+ canvas.drawText(text, x, yOffset + textPaint.textSize, textPaint)
55
+ yOffset += textPaint.textSize + 10
56
+ }
57
+ "line" -> {
58
+ canvas.drawLine(0f, yOffset, paperWidthPx.toFloat(), yOffset, textPaint)
59
+ yOffset += 10
60
+ }
61
+ "table" -> {
62
+ val headersRaw = element["headers"] as List<*>
63
+ val headers = headersRaw.map {
64
+ if (it is Map<*, *>) (it["text"] as? String) ?: "" else it.toString()
65
+ }
66
+ val headerAligns = headersRaw.map {
67
+ if (it is Map<*, *>) (it["align"] as? String) ?: "left" else "left"
68
+ }
69
+ val headerBold = headersRaw.map {
70
+ if (it is Map<*, *>) (it["bold"] as? Boolean) ?: false else false
71
+ }
72
+ val rowsRaw = element["rows"] as List<*>
73
+ val rows = rowsRaw.map { row ->
74
+ (row as List<*>).map {
75
+ if (it is Map<*, *>) (it["text"] as? String) ?: "" else it.toString()
76
+ }
77
+ }
78
+ val rowAligns = rowsRaw.map { row ->
79
+ (row as List<*>).map {
80
+ if (it is Map<*, *>) (it["align"] as? String) ?: "left" else "left"
81
+ }
82
+ }
83
+ val widthPercent = (element["widthPercent"] as? Number)?.toFloat() ?: 1f
84
+ val tableWidthPx = (paperWidthPx * widthPercent).toInt()
85
+ val alignTable = element["align"] as? String ?: "left"
86
+ val xOffset = when (alignTable) {
87
+ "center" -> (paperWidthPx - tableWidthPx) / 2f
88
+ "right" -> (paperWidthPx - tableWidthPx).toFloat()
89
+ else -> 0f
90
+ }
91
+ val columnWidth = tableWidthPx / headers.size
92
+
93
+ fun drawWrappedText(text: String, x: Float, y: Float, maxWidth: Float, paint: TextPaint): Float {
94
+ val staticLayout = StaticLayout(
95
+ text,
96
+ paint,
97
+ maxWidth.toInt(),
98
+ Layout.Alignment.ALIGN_NORMAL,
99
+ 1.0f,
100
+ 0.0f,
101
+ false
102
+ )
103
+ canvas.save()
104
+ canvas.translate(x, y)
105
+ staticLayout.draw(canvas)
106
+ canvas.restore()
107
+ return staticLayout.height.toFloat()
108
+ }
109
+
110
+ headers.forEachIndexed { index, header ->
111
+ textPaint.typeface = if (headerBold[index]) notoSansBold else notoSans
112
+ textPaint.textAlign = when (headerAligns[index]) {
113
+ "center" -> Paint.Align.CENTER
114
+ "right" -> Paint.Align.RIGHT
115
+ else -> Paint.Align.LEFT
116
+ }
117
+ val x = xOffset + when (headerAligns[index]) {
118
+ "center" -> index * columnWidth + columnWidth / 2f
119
+ "right" -> (index + 1) * columnWidth.toFloat()
120
+ else -> index * columnWidth.toFloat()
121
+ }
122
+ drawWrappedText(header, x, yOffset + textPaint.textSize, columnWidth.toFloat(), textPaint)
123
+ }
124
+ yOffset += textPaint.textSize + 10
125
+
126
+ rows.forEachIndexed { rowIdx, row ->
127
+ var maxRowHeight = textPaint.textSize
128
+ val rowRaw = rowsRaw[rowIdx] as List<*>
129
+ row.forEachIndexed { colIdx, cell ->
130
+ val cellRaw = rowRaw[colIdx]
131
+ val isBold = if (cellRaw is Map<*, *>) (cellRaw["bold"] as? Boolean) ?: false else false
132
+ textPaint.typeface = if (isBold) notoSansBold else notoSans
133
+ textPaint.textAlign = when (rowAligns[rowIdx][colIdx]) {
134
+ "center" -> Paint.Align.CENTER
135
+ "right" -> Paint.Align.RIGHT
136
+ else -> Paint.Align.LEFT
137
+ }
138
+ val x = xOffset + when (rowAligns[rowIdx][colIdx]) {
139
+ "center" -> colIdx * columnWidth + columnWidth / 2f
140
+ "right" -> (colIdx + 1) * columnWidth.toFloat()
141
+ else -> colIdx * columnWidth.toFloat()
142
+ }
143
+ val cellHeight = drawWrappedText(cell, x, yOffset + textPaint.textSize, columnWidth.toFloat(), textPaint)
144
+ if (cellHeight > maxRowHeight) maxRowHeight = cellHeight
145
+ }
146
+ yOffset += maxRowHeight + 10
147
+ }
148
+ }
149
+ "qr" -> {
150
+ val data = element["data"] as String
151
+ val qrCode = QRCodeWriter().encode(data, BarcodeFormat.QR_CODE, 200, 200)
152
+ val qrBitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888)
153
+ for (x in 0 until 200) {
154
+ for (y in 0 until 200) {
155
+ qrBitmap.setPixel(x, y, if (qrCode.get(x, y)) Color.BLACK else Color.WHITE)
156
+ }
157
+ }
158
+ canvas.drawBitmap(qrBitmap, (paperWidthPx - 200) / 2f, yOffset, textPaint)
159
+ yOffset += 210
160
+ }
161
+ "feed" -> {
162
+ val lines = (element["lines"] as? Number)?.toInt() ?: 0
163
+ yOffset += lines * 24f
164
+ }
165
+ "cut" -> {
166
+ // ESC/POS cut command: GS V 0
167
+ // Do not crop or affect bitmap rendering here. Handle cut in ESC/POS or printer logic only.
168
+ // No change to yOffset or bitmap cropping.
169
+ }
170
+ }
171
+ }
172
+
173
+ return Bitmap.createBitmap(bitmap, 0, 0, paperWidthPx, yOffset.toInt())
174
+ }
175
+ }
@@ -0,0 +1,27 @@
1
+ package expo.modules.hiuprint
2
+
3
+ import android.graphics.Bitmap
4
+
5
+ object BitmapToESCPOS {
6
+ fun convert(bitmap: Bitmap): ByteArray {
7
+ val width = bitmap.width
8
+ val height = bitmap.height
9
+ val bytes = mutableListOf<Byte>()
10
+
11
+ bytes.addAll(byteArrayOf(0x1D, 0x76, 0x30, 0x00, (width / 8).toByte(), 0x00, (height and 0xFF).toByte(), (height shr 8).toByte()).toList())
12
+
13
+ for (y in 0 until height) {
14
+ for (x in 0 until width step 8) {
15
+ var byte = 0
16
+ for (bit in 0..7) {
17
+ if (x + bit < width && bitmap.getPixel(x + bit, y) == -0x1000000) {
18
+ byte = byte or (1 shl (7 - bit))
19
+ }
20
+ }
21
+ bytes.add(byte.toByte())
22
+ }
23
+ }
24
+
25
+ return bytes.toByteArray()
26
+ }
27
+ }
@@ -0,0 +1,59 @@
1
+ package expo.modules.hiuprint
2
+
3
+ import expo.modules.kotlin.modules.Module
4
+ import expo.modules.kotlin.modules.ModuleDefinition
5
+ import expo.modules.hiuprint.BitmapRenderer
6
+ import expo.modules.hiuprint.BitmapToESCPOS
7
+ import expo.modules.hiuprint.NetworkPrinter
8
+
9
+ class ExpoHiuPrintModule : Module() {
10
+ override fun definition() = ModuleDefinition {
11
+ Name("ExpoHiuPrint")
12
+
13
+ AsyncFunction("print") { job: Map<String, Any> ->
14
+ val printers = job["printers"] as List<Map<String, Any>>
15
+ val elements = job["elements"] as List<Map<String, Any>>
16
+ try {
17
+ printers.forEach { printer ->
18
+ val host = printer["host"] as String
19
+ val port = (printer["port"] as Double).toInt()
20
+ val paperWidth = (printer["paperWidth"] as Double).toInt()
21
+
22
+ val renderer = BitmapRenderer(if (paperWidth == 58) 384 else 576)
23
+ val bitmap = renderer.render(elements)
24
+ val escposData = BitmapToESCPOS.convert(bitmap)
25
+
26
+ // Check if there is a cut element
27
+ val hasCut = elements.any { it["type"] == "cut" }
28
+
29
+ val networkPrinter = NetworkPrinter(host, port)
30
+ networkPrinter.send(escposData, cut = hasCut)
31
+ }
32
+ } catch (e: Exception) {
33
+ throw e
34
+ }
35
+ }
36
+
37
+ AsyncFunction("openCashBox") { job: Map<String, Any> ->
38
+ val printers = job["printers"] as List<Map<String, Any>>
39
+ try {
40
+ printers.forEach { printer ->
41
+ val host = printer["host"] as String
42
+ val port = (printer["port"] as Double).toInt()
43
+ val networkPrinter = NetworkPrinter(host, port)
44
+ networkPrinter.openCashBox()
45
+ }
46
+ } catch (e: Exception) {
47
+ throw e
48
+ }
49
+ }
50
+
51
+ AsyncFunction("checkPrinterStatus") { printer: Map<String, Any> ->
52
+ val host = printer["host"] as? String ?: return@AsyncFunction "disconnected"
53
+ val port = (printer["port"] as? Double)?.toInt() ?: 9100
54
+ val networkPrinter = NetworkPrinter(host, port)
55
+ val isConnected = networkPrinter.checkPrinterStatus()
56
+ return@AsyncFunction if (isConnected) "connected" else "disconnected"
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,43 @@
1
+ package expo.modules.hiuprint
2
+
3
+ import java.io.OutputStream
4
+ import java.net.Socket
5
+
6
+ class NetworkPrinter(private val host: String, private val port: Int) {
7
+ fun send(data: ByteArray, cut: Boolean = false) {
8
+ Socket(host, port).use { socket ->
9
+ val outputStream: OutputStream = socket.getOutputStream()
10
+ outputStream.write(data)
11
+ outputStream.flush()
12
+ if (cut) {
13
+ // Gửi thêm 3 dòng feed (ESC d 3) để đẩy giấy ra trước khi cắt
14
+ outputStream.write(byteArrayOf(0x1B, 0x64, 0x03))
15
+ outputStream.flush()
16
+ // ESC/POS cut command: GS V 0
17
+ outputStream.write(byteArrayOf(0x1D, 0x56, 0x00))
18
+ outputStream.flush()
19
+ }
20
+ }
21
+ }
22
+
23
+ fun openCashBox() {
24
+ Socket(host, port).use { socket ->
25
+ val outputStream: OutputStream = socket.getOutputStream()
26
+ // ESC/POS open cash drawer command: DLE DC4 n m t
27
+ // Most common: 0x10 0x14 0x01 0x00 0x00
28
+ outputStream.write(byteArrayOf(0x10, 0x14, 0x01, 0x00, 0x00))
29
+ outputStream.flush()
30
+ }
31
+ }
32
+
33
+ fun checkPrinterStatus(): Boolean {
34
+ return try {
35
+ Socket(host, port).use { socket ->
36
+ // If we can open a socket, the printer is reachable
37
+ true
38
+ }
39
+ } catch (e: Exception) {
40
+ false
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "platforms": ["apple", "android", "web"],
3
+ "apple": {
4
+ "modules": ["ExpoHiuPrintModule"]
5
+ },
6
+ "android": {
7
+ "modules": ["expo.modules.hiuprint.ExpoHiuPrintModule"]
8
+ }
9
+ }
package/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./src/api";
2
+ export { default as ExpoHiuPrintModule } from "./src/ExpoHiuPrintModule";
3
+ export * from "./src/types";
4
+ //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC"}
package/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./src/api";
2
+ export { default as ExpoHiuPrintModule } from "./src/ExpoHiuPrintModule";
3
+ export * from "./src/types";
4
+
@@ -0,0 +1,233 @@
1
+ import UIKit
2
+
3
+ class BitmapRenderer {
4
+ let paperWidthPx: Int
5
+
6
+ init(paperWidthPx: Int) {
7
+ self.paperWidthPx = paperWidthPx
8
+ }
9
+
10
+ // Hàm helper để tạo font
11
+ private func getFont(size: String = "normal", bold: Bool = false) -> UIFont {
12
+ let fontSize: CGFloat = (size == "small") ? 20 : (size == "large") ? 32 : 24
13
+ return bold ? UIFont.boldSystemFont(ofSize: fontSize) : UIFont.systemFont(ofSize: fontSize)
14
+ }
15
+
16
+ private func getAttributes(font: UIFont, align: String) -> [NSAttributedString.Key: Any] {
17
+ let paragraph = NSMutableParagraphStyle()
18
+ paragraph.alignment = (align == "center") ? .center : (align == "right") ? .right : .left
19
+ paragraph.lineBreakMode = .byWordWrapping
20
+ return [
21
+ .font: font,
22
+ .foregroundColor: UIColor.black,
23
+ .paragraphStyle: paragraph
24
+ ]
25
+ }
26
+
27
+ // Tính chiều cao cần thiết cho một đoạn text
28
+ private func measureTextHeight(text: String, width: CGFloat, font: UIFont) -> CGFloat {
29
+ let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
30
+ let boundingBox = text.boundingRect(with: constraintRect,
31
+ options: [.usesLineFragmentOrigin, .usesFontLeading],
32
+ attributes: [.font: font],
33
+ context: nil)
34
+ return ceil(boundingBox.height)
35
+ }
36
+
37
+ // Tính tổng chiều cao cần thiết cho toàn bộ bill
38
+ private func calculateTotalHeight(elements: [[String: Any]]) -> CGFloat {
39
+ var totalHeight: CGFloat = 0
40
+
41
+ for element in elements {
42
+ guard let type = element["type"] as? String else { continue }
43
+
44
+ if type == "text" {
45
+ let text = element["value"] as? String ?? ""
46
+ let bold = element["bold"] as? Bool ?? false
47
+ let size = element["size"] as? String ?? "normal"
48
+ let font = getFont(size: size, bold: bold)
49
+ let h = measureTextHeight(text: text, width: CGFloat(paperWidthPx), font: font)
50
+ totalHeight += h + 10 // padding bottom
51
+ } else if type == "line" {
52
+ totalHeight += 10 + 10 // height + padding
53
+ } else if type == "feed" {
54
+ let lines = (element["lines"] as? Int) ?? 0
55
+ totalHeight += CGFloat(lines) * 24
56
+ } else if type == "qr" {
57
+ totalHeight += 210 // 200 size + 10 padding
58
+ } else if type == "table" {
59
+ let headersRaw = element["headers"] as? [[String: Any]] ?? []
60
+ let rowsRaw = element["rows"] as? [[[String: Any]]] ?? []
61
+ let widthPercent = (element["widthPercent"] as? CGFloat) ?? 1.0
62
+ let tableWidth = CGFloat(paperWidthPx) * widthPercent
63
+ let colCount = CGFloat(max(1, headersRaw.count))
64
+ let colWidth = tableWidth / colCount
65
+
66
+ // Header height
67
+ if !headersRaw.isEmpty {
68
+ var maxHeaderH: CGFloat = 0
69
+ for header in headersRaw {
70
+ let text = header["text"] as? String ?? ""
71
+ let bold = header["bold"] as? Bool ?? false
72
+ let font = getFont(size: "normal", bold: bold)
73
+ let h = measureTextHeight(text: text, width: colWidth, font: font)
74
+ if h > maxHeaderH { maxHeaderH = h }
75
+ }
76
+ totalHeight += maxHeaderH + 10
77
+ }
78
+
79
+ // Rows height
80
+ for row in rowsRaw {
81
+ var maxRowH: CGFloat = 0
82
+ for (idx, col) in row.enumerated() {
83
+ if idx >= Int(colCount) { break }
84
+ let text = col["text"] as? String ?? ""
85
+ let bold = col["bold"] as? Bool ?? false
86
+ let font = getFont(size: "normal", bold: bold)
87
+ let h = measureTextHeight(text: text, width: colWidth, font: font)
88
+ if h > maxRowH { maxRowH = h }
89
+ }
90
+ totalHeight += maxRowH + 10
91
+ }
92
+ }
93
+ }
94
+ return totalHeight + 50 // Buffer cuối cùng
95
+ }
96
+
97
+ func render(elements: [[String: Any]]) -> UIImage? {
98
+ // Bước 1: Tính chiều cao dynamic để không bị cắt giấy
99
+ let totalHeight = calculateTotalHeight(elements: elements)
100
+
101
+ UIGraphicsBeginImageContextWithOptions(CGSize(width: CGFloat(paperWidthPx), height: totalHeight), false, 1.0)
102
+ guard let context = UIGraphicsGetCurrentContext() else { return nil }
103
+
104
+ // Fill nền trắng
105
+ context.setFillColor(UIColor.white.cgColor)
106
+ context.fill(CGRect(x: 0, y: 0, width: CGFloat(paperWidthPx), height: totalHeight))
107
+
108
+ var yOffset: CGFloat = 0
109
+
110
+ for element in elements {
111
+ guard let type = element["type"] as? String else { continue }
112
+
113
+ if type == "text" {
114
+ let text = element["value"] as? String ?? ""
115
+ let align = element["align"] as? String ?? "left"
116
+ let bold = element["bold"] as? Bool ?? false
117
+ let size = element["size"] as? String ?? "normal"
118
+
119
+ let font = getFont(size: size, bold: bold)
120
+ let attrs = getAttributes(font: font, align: align)
121
+ let height = measureTextHeight(text: text, width: CGFloat(paperWidthPx), font: font)
122
+
123
+ let rect = CGRect(x: 0, y: yOffset, width: CGFloat(paperWidthPx), height: height)
124
+ (text as NSString).draw(in: rect, withAttributes: attrs)
125
+
126
+ yOffset += height + 10
127
+
128
+ } else if type == "line" {
129
+ context.setStrokeColor(UIColor.black.cgColor)
130
+ context.setLineWidth(2)
131
+ context.move(to: CGPoint(x: 0, y: yOffset + 5))
132
+ context.addLine(to: CGPoint(x: CGFloat(paperWidthPx), y: yOffset + 5))
133
+ context.strokePath()
134
+ yOffset += 20 // 10 line + 10 space
135
+
136
+ } else if type == "feed" {
137
+ let lines = (element["lines"] as? Int) ?? 0
138
+ yOffset += CGFloat(lines) * 24
139
+
140
+ } else if type == "qr" {
141
+ if let data = element["data"] as? String {
142
+ let filter = CIFilter(name: "CIQRCodeGenerator")
143
+ filter?.setValue(data.data(using: .utf8), forKey: "inputMessage")
144
+ filter?.setValue("M", forKey: "inputCorrectionLevel")
145
+
146
+ if let ciImage = filter?.outputImage {
147
+ let qrSize: CGFloat = 200
148
+ let scaleX = qrSize / ciImage.extent.size.width
149
+ let scaleY = qrSize / ciImage.extent.size.height
150
+ let transformed = ciImage.transformed(by: CGAffineTransform(scaleX: scaleX, y: scaleY))
151
+ let qrImage = UIImage(ciImage: transformed)
152
+
153
+ let xPos = (CGFloat(paperWidthPx) - qrSize) / 2
154
+ qrImage.draw(in: CGRect(x: xPos, y: yOffset, width: qrSize, height: qrSize))
155
+ yOffset += qrSize + 10
156
+ }
157
+ }
158
+
159
+ } else if type == "table" {
160
+ let headersRaw = element["headers"] as? [[String: Any]] ?? []
161
+ let rowsRaw = element["rows"] as? [[[String: Any]]] ?? []
162
+ let widthPercent = (element["widthPercent"] as? CGFloat) ?? 1.0
163
+ let tableWidth = CGFloat(paperWidthPx) * widthPercent
164
+ let alignTable = element["align"] as? String ?? "left"
165
+ let xStart: CGFloat = (alignTable == "center") ? (CGFloat(paperWidthPx) - tableWidth) / 2 : (alignTable == "right") ? (CGFloat(paperWidthPx) - tableWidth) : 0
166
+
167
+ let colCount = CGFloat(max(1, headersRaw.count))
168
+ let colWidth = tableWidth / colCount
169
+
170
+ // Draw Headers
171
+ if !headersRaw.isEmpty {
172
+ var maxH: CGFloat = 0
173
+ // Tính max height của row header trước
174
+ for header in headersRaw {
175
+ let text = header["text"] as? String ?? ""
176
+ let bold = header["bold"] as? Bool ?? false
177
+ let font = getFont(size: "normal", bold: bold)
178
+ let h = measureTextHeight(text: text, width: colWidth, font: font)
179
+ if h > maxH { maxH = h }
180
+ }
181
+
182
+ // Vẽ
183
+ for (i, header) in headersRaw.enumerated() {
184
+ let text = header["text"] as? String ?? ""
185
+ let align = header["align"] as? String ?? "left"
186
+ let bold = header["bold"] as? Bool ?? false
187
+ let font = getFont(size: "normal", bold: bold)
188
+ let attrs = getAttributes(font: font, align: align)
189
+
190
+ let cellX = xStart + CGFloat(i) * colWidth
191
+ let rect = CGRect(x: cellX, y: yOffset, width: colWidth, height: maxH)
192
+ (text as NSString).draw(in: rect, withAttributes: attrs)
193
+ }
194
+ yOffset += maxH + 10
195
+ }
196
+
197
+ // Draw Rows
198
+ for row in rowsRaw {
199
+ var maxRowH: CGFloat = 0
200
+ // Pass 1: Tính chiều cao row
201
+ for (i, col) in row.enumerated() {
202
+ if i >= Int(colCount) { break }
203
+ let text = col["text"] as? String ?? ""
204
+ let bold = col["bold"] as? Bool ?? false
205
+ let font = getFont(size: "normal", bold: bold)
206
+ let h = measureTextHeight(text: text, width: colWidth, font: font)
207
+ if h > maxRowH { maxRowH = h }
208
+ }
209
+ if maxRowH < 24 { maxRowH = 24 } // Min height
210
+
211
+ // Pass 2: Vẽ
212
+ for (i, col) in row.enumerated() {
213
+ if i >= Int(colCount) { break }
214
+ let text = col["text"] as? String ?? ""
215
+ let align = col["align"] as? String ?? "left"
216
+ let bold = col["bold"] as? Bool ?? false
217
+ let font = getFont(size: "normal", bold: bold)
218
+ let attrs = getAttributes(font: font, align: align)
219
+
220
+ let cellX = xStart + CGFloat(i) * colWidth
221
+ let rect = CGRect(x: cellX, y: yOffset, width: colWidth, height: maxRowH)
222
+ (text as NSString).draw(in: rect, withAttributes: attrs)
223
+ }
224
+ yOffset += maxRowH + 10
225
+ }
226
+ }
227
+ }
228
+
229
+ let image = UIGraphicsGetImageFromCurrentImageContext()
230
+ UIGraphicsEndImageContext()
231
+ return image
232
+ }
233
+ }
@@ -0,0 +1,42 @@
1
+ import UIKit
2
+
3
+ class BitmapToESCPOS {
4
+ // Chuyển UIImage sang ESC/POS raster bytes
5
+ static func convert(_ image: UIImage) -> Data? {
6
+ guard let cgImage = image.cgImage else { return nil }
7
+ let width = cgImage.width
8
+ let height = cgImage.height
9
+ let bytesPerRow = (width + 7) / 8
10
+ var escpos = Data()
11
+ // ESC * m nL nH raster bit image
12
+ escpos.append(0x1D)
13
+ escpos.append(0x76)
14
+ escpos.append(0x30)
15
+ escpos.append(0x00) // Normal mode
16
+ escpos.append(UInt8(bytesPerRow & 0xFF))
17
+ escpos.append(UInt8((bytesPerRow >> 8) & 0xFF))
18
+ escpos.append(UInt8(height & 0xFF))
19
+ escpos.append(UInt8((height >> 8) & 0xFF))
20
+ // Lấy pixel data
21
+ guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue) else { return nil }
22
+ context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
23
+ guard let pixelData = context.data else { return nil }
24
+ let buffer = pixelData.bindMemory(to: UInt8.self, capacity: width * height)
25
+ for y in 0..<height {
26
+ for xByte in 0..<bytesPerRow {
27
+ var byte: UInt8 = 0
28
+ for bit in 0..<8 {
29
+ let x = xByte * 8 + bit
30
+ if x < width {
31
+ let pixel = buffer[y * width + x]
32
+ if pixel < 128 { // ngưỡng đen trắng
33
+ byte |= (0x80 >> bit)
34
+ }
35
+ }
36
+ }
37
+ escpos.append(byte)
38
+ }
39
+ }
40
+ return escpos
41
+ }
42
+ }
@@ -0,0 +1,29 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'ExpoHiuPrint'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.description = package['description']
10
+ s.license = package['license']
11
+ s.author = package['author']
12
+ s.homepage = package['homepage']
13
+ s.platforms = {
14
+ :ios => '15.1',
15
+ :tvos => '15.1'
16
+ }
17
+ s.swift_version = '5.9'
18
+ s.source = { git: 'https://github.com/hieufix1710/expo-hiu-print' }
19
+ s.static_framework = true
20
+
21
+ s.dependency 'ExpoModulesCore'
22
+
23
+ # Swift/Objective-C compatibility
24
+ s.pod_target_xcconfig = {
25
+ 'DEFINES_MODULE' => 'YES',
26
+ }
27
+
28
+ s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
29
+ end
@@ -0,0 +1,73 @@
1
+ import ExpoModulesCore
2
+
3
+ public class ExpoHiuPrintModule: Module {
4
+ // Each module class must implement the definition function. The definition consists of components
5
+ // that describes the module's functionality and behavior.
6
+ // See https://docs.expo.dev/modules/module-api for more details about available components.
7
+ public func definition() -> ModuleDefinition {
8
+ Name("ExpoHiuPrint")
9
+
10
+ AsyncFunction("print") { (job: [String: Any]) in
11
+ guard let printers = job["printers"] as? [[String: Any]], let elements = job["elements"] as? [[String: Any]] else {
12
+ throw NSError(domain: "ExpoHiuPrint", code: 400, userInfo: [NSLocalizedDescriptionKey: "Invalid arguments"])
13
+ }
14
+
15
+ for printer in printers {
16
+ let host = printer["host"] as? String ?? ""
17
+ let port = printer["port"] as? Int ?? 9100
18
+ let printerWidth = printer["paperWidth"] as? Int ?? 576
19
+
20
+ // Logic convert giống Android: nếu input là 58(mm) thì dùng 384px, mặc định 576px
21
+ let paperWidthPx = (printerWidth == 58) ? 384 : 576
22
+
23
+ let renderer = BitmapRenderer(paperWidthPx: paperWidthPx)
24
+
25
+ guard let image = renderer.render(elements: elements) else {
26
+ throw NSError(domain: "ExpoHiuPrint", code: 500, userInfo: [NSLocalizedDescriptionKey: "Failed to render image"])
27
+ }
28
+
29
+ guard let escposData = BitmapToESCPOS.convert(image) else {
30
+ throw NSError(domain: "ExpoHiuPrint", code: 500, userInfo: [NSLocalizedDescriptionKey: "Failed to convert to ESC/POS"])
31
+ }
32
+
33
+ let hasCut = elements.contains { ($0["type"] as? String) == "cut" }
34
+ let networkPrinter = NetworkPrinter(host: host, port: port)
35
+
36
+ do {
37
+ try networkPrinter.send(data: escposData, cut: hasCut)
38
+ } catch {
39
+ throw error
40
+ }
41
+ }
42
+ }
43
+
44
+ AsyncFunction("openCashBox") { (job: [String: Any]) in
45
+ guard let printers = job["printers"] as? [[String: Any]] else { return }
46
+ for printer in printers {
47
+ let host = printer["host"] as? String ?? ""
48
+ let port = printer["port"] as? Int ?? 9100
49
+ let networkPrinter = NetworkPrinter(host: host, port: port)
50
+ do {
51
+ try networkPrinter.openCashBox()
52
+ } catch {
53
+ throw error
54
+ }
55
+ }
56
+ }
57
+
58
+ AsyncFunction("checkPrinterStatus") { (printer: [String: Any]) -> String in
59
+ let host = printer["host"] as? String ?? ""
60
+ let port = printer["port"] as? Int ?? 9100
61
+ var inputStream: InputStream?
62
+ var outputStream: OutputStream?
63
+ Stream.getStreamsToHost(withName: host, port: port, inputStream: &inputStream, outputStream: &outputStream)
64
+
65
+ guard let out = outputStream else { return "disconnected" }
66
+ out.open()
67
+ // Basic connection check via stream status
68
+ let isConnected = out.streamStatus == .open || out.streamStatus == .writing
69
+ out.close()
70
+ return isConnected ? "connected" : "disconnected"
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,40 @@
1
+ import Foundation
2
+
3
+ class NetworkPrinter {
4
+ let host: String
5
+ let port: Int
6
+
7
+ init(host: String, port: Int) {
8
+ self.host = host
9
+ self.port = port
10
+ }
11
+
12
+ func send(data: Data, cut: Bool = false) throws {
13
+ var inputStream: InputStream?
14
+ var outputStream: OutputStream?
15
+ Stream.getStreamsToHost(withName: host, port: port, inputStream: &inputStream, outputStream: &outputStream)
16
+ guard let out = outputStream else { throw NSError(domain: "NetworkPrinter", code: 1, userInfo: [NSLocalizedDescriptionKey: "Cannot open output stream"]) }
17
+ out.open()
18
+ let bytes = [UInt8](data)
19
+ out.write(bytes, maxLength: bytes.count)
20
+ if cut {
21
+ // Gửi thêm 3 dòng feed (ESC d 3) để đẩy giấy ra trước khi cắt
22
+ out.write([0x1B, 0x64, 0x03], maxLength: 3)
23
+ // ESC/POS cut command: GS V 0
24
+ out.write([0x1D, 0x56, 0x00], maxLength: 3)
25
+ }
26
+ out.close()
27
+ }
28
+
29
+ func openCashBox() throws {
30
+ var inputStream: InputStream?
31
+ var outputStream: OutputStream?
32
+ Stream.getStreamsToHost(withName: host, port: port, inputStream: &inputStream, outputStream: &outputStream)
33
+ guard let out = outputStream else { throw NSError(domain: "NetworkPrinter", code: 1, userInfo: [NSLocalizedDescriptionKey: "Cannot open output stream"]) }
34
+ out.open()
35
+ // ESC/POS open cash drawer command: DLE DC4 n m t
36
+ let cmd: [UInt8] = [0x10, 0x14, 0x01, 0x00, 0x00]
37
+ out.write(cmd, maxLength: cmd.count)
38
+ out.close()
39
+ }
40
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "expo-hiu-print",
3
+ "version": "0.1.0",
4
+ "description": "Connect printer ",
5
+ "main": "build/index.js",
6
+ "types": "build/index.d.ts",
7
+ "scripts": {
8
+ "build": "expo-module build",
9
+ "clean": "expo-module clean",
10
+ "lint": "expo-module lint",
11
+ "test": "expo-module test",
12
+ "prepare": "expo-module prepare",
13
+ "prepublishOnly": "expo-module prepublishOnly",
14
+ "expo-module": "expo-module",
15
+ "open:ios": "xed example/ios",
16
+ "open:android": "open -a \"Android Studio\" example/android"
17
+ },
18
+ "keywords": [
19
+ "react-native",
20
+ "expo",
21
+ "expo-hiu-print",
22
+ "ExpoHiuPrint"
23
+ ],
24
+ "repository": "https://github.com/hieufix1710/expo-hiu-print",
25
+ "bugs": {
26
+ "url": "https://github.com/hieufix1710/expo-hiu-print/issues"
27
+ },
28
+ "author": "hiun <hieufix1710@gmail.com> (https://github.com/hieufix1710)",
29
+ "license": "MIT",
30
+ "homepage": "https://github.com/hieufix1710/expo-hiu-print#readme",
31
+ "dependencies": {},
32
+ "devDependencies": {
33
+ "@types/react": "~19.1.0",
34
+ "expo-module-scripts": "^5.0.8",
35
+ "expo": "^54.0.27",
36
+ "react-native": "0.81.5"
37
+ },
38
+ "peerDependencies": {
39
+ "expo": "*",
40
+ "react": "*",
41
+ "react-native": "*"
42
+ }
43
+ }
@@ -0,0 +1,4 @@
1
+ import type { ExpoHiuPrintModuleType } from "./types";
2
+ declare const ExpoHiuPrintModule: ExpoHiuPrintModuleType;
3
+ export default ExpoHiuPrintModule;
4
+ //# sourceMappingURL=ExpoHiuPrintModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoHiuPrintModule.d.ts","sourceRoot":"","sources":["ExpoHiuPrintModule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAEtD,QAAA,MAAM,kBAAkB,wBACqC,CAAC;AAE9D,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { requireNativeModule } from "expo";
2
+ const ExpoHiuPrintModule = requireNativeModule("ExpoHiuPrint");
3
+ export default ExpoHiuPrintModule;
4
+ //# sourceMappingURL=ExpoHiuPrintModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoHiuPrintModule.js","sourceRoot":"","sources":["ExpoHiuPrintModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAG3C,MAAM,kBAAkB,GACtB,mBAAmB,CAAyB,cAAc,CAAC,CAAC;AAE9D,eAAe,kBAAkB,CAAC","sourcesContent":["import { requireNativeModule } from \"expo\";\nimport type { ExpoHiuPrintModuleType } from \"./types\";\n\nconst ExpoHiuPrintModule =\n requireNativeModule<ExpoHiuPrintModuleType>(\"ExpoHiuPrint\");\n\nexport default ExpoHiuPrintModule;\n"]}
@@ -0,0 +1,7 @@
1
+ import { requireNativeModule } from "expo";
2
+ import type { ExpoHiuPrintModuleType } from "./types";
3
+
4
+ const ExpoHiuPrintModule =
5
+ requireNativeModule<ExpoHiuPrintModuleType>("ExpoHiuPrint");
6
+
7
+ export default ExpoHiuPrintModule;
package/src/api.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { PrintJob, Printer } from "./types";
2
+ export declare function print(job: PrintJob): Promise<void>;
3
+ export declare function openCashBox(job: {
4
+ printers: Printer[];
5
+ }): Promise<void>;
6
+ export declare function checkPrinterStatus(printer: Printer): Promise<"connected" | "disconnected">;
7
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEjD,wBAAgB,KAAK,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAElD;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvE;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,WAAW,GAAG,cAAc,CAAC,CAEvC"}
package/src/api.js ADDED
@@ -0,0 +1,12 @@
1
+ // API wrapper for expo-hiu-print
2
+ import ExpoHiuPrintModule from "./ExpoHiuPrintModule";
3
+ export function print(job) {
4
+ return ExpoHiuPrintModule.print(job);
5
+ }
6
+ export function openCashBox(job) {
7
+ return ExpoHiuPrintModule.openCashBox(job);
8
+ }
9
+ export function checkPrinterStatus(printer) {
10
+ return ExpoHiuPrintModule.checkPrinterStatus(printer);
11
+ }
12
+ //# sourceMappingURL=api.js.map
package/src/api.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AAGtD,MAAM,UAAU,KAAK,CAAC,GAAa;IACjC,OAAO,kBAAkB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAA4B;IACtD,OAAO,kBAAkB,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,OAAgB;IAEhB,OAAO,kBAAkB,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;AACxD,CAAC","sourcesContent":["// API wrapper for expo-hiu-print\nimport ExpoHiuPrintModule from \"./ExpoHiuPrintModule\";\nimport type { PrintJob, Printer } from \"./types\";\n\nexport function print(job: PrintJob): Promise<void> {\n return ExpoHiuPrintModule.print(job);\n}\n\nexport function openCashBox(job: { printers: Printer[] }): Promise<void> {\n return ExpoHiuPrintModule.openCashBox(job);\n}\n\nexport function checkPrinterStatus(\n printer: Printer,\n): Promise<\"connected\" | \"disconnected\"> {\n return ExpoHiuPrintModule.checkPrinterStatus(printer);\n}\n"]}
package/src/api.ts ADDED
@@ -0,0 +1,17 @@
1
+ // API wrapper for expo-hiu-print
2
+ import ExpoHiuPrintModule from "./ExpoHiuPrintModule";
3
+ import type { PrintJob, Printer } from "./types";
4
+
5
+ export function print(job: PrintJob): Promise<void> {
6
+ return ExpoHiuPrintModule.print(job);
7
+ }
8
+
9
+ export function openCashBox(job: { printers: Printer[] }): Promise<void> {
10
+ return ExpoHiuPrintModule.openCashBox(job);
11
+ }
12
+
13
+ export function checkPrinterStatus(
14
+ printer: Printer,
15
+ ): Promise<"connected" | "disconnected"> {
16
+ return ExpoHiuPrintModule.checkPrinterStatus(printer);
17
+ }
package/src/types.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { NativeModule } from "react-native";
2
+ export interface Printer {
3
+ name: string;
4
+ host: string;
5
+ port: number;
6
+ paperWidth: number;
7
+ }
8
+ export interface PrintElement {
9
+ type: "text" | "line" | "table" | "qr" | "feed" | "cut";
10
+ value?: string;
11
+ align?: "left" | "center" | "right";
12
+ bold?: boolean;
13
+ size?: "small" | "normal" | "large";
14
+ headers?: {
15
+ text: string;
16
+ align?: "left" | "center" | "right";
17
+ bold?: boolean;
18
+ }[];
19
+ rows?: {
20
+ text: string;
21
+ align?: "left" | "center" | "right";
22
+ bold?: boolean;
23
+ }[][];
24
+ data?: string;
25
+ lines?: number;
26
+ widthPercent?: number;
27
+ }
28
+ export interface PrintJob {
29
+ printers: Printer[];
30
+ elements: PrintElement[];
31
+ }
32
+ export interface ExpoHiuPrintModuleType extends NativeModule {
33
+ print(job: PrintJob): Promise<void>;
34
+ openCashBox(job: {
35
+ printers: Printer[];
36
+ }): Promise<void>;
37
+ checkPrinterStatus(printer: Printer): Promise<"connected" | "disconnected">;
38
+ }
39
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,KAAK,CAAC;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,OAAO,CAAC,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;QACpC,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,EAAE,CAAC;IACJ,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;QACpC,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,EAAE,EAAE,CAAC;IACN,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAuB,SAAQ,YAAY;IAC1D,KAAK,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,WAAW,CAAC,GAAG,EAAE;QAAE,QAAQ,EAAE,OAAO,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,GAAG,cAAc,CAAC,CAAC;CAC7E"}
package/src/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"","sourcesContent":["// Types for expo-hiu-print\nimport { NativeModule } from \"react-native\";\n\nexport interface Printer {\n name: string;\n host: string;\n port: number;\n paperWidth: number;\n}\n\nexport interface PrintElement {\n type: \"text\" | \"line\" | \"table\" | \"qr\" | \"feed\" | \"cut\";\n value?: string;\n align?: \"left\" | \"center\" | \"right\";\n bold?: boolean;\n size?: \"small\" | \"normal\" | \"large\";\n headers?: {\n text: string;\n align?: \"left\" | \"center\" | \"right\";\n bold?: boolean;\n }[];\n rows?: {\n text: string;\n align?: \"left\" | \"center\" | \"right\";\n bold?: boolean;\n }[][];\n data?: string;\n lines?: number;\n widthPercent?: number;\n}\n\nexport interface PrintJob {\n printers: Printer[];\n elements: PrintElement[];\n}\n\nexport interface ExpoHiuPrintModuleType extends NativeModule {\n print(job: PrintJob): Promise<void>;\n openCashBox(job: { printers: Printer[] }): Promise<void>;\n checkPrinterStatus(printer: Printer): Promise<\"connected\" | \"disconnected\">;\n}\n"]}
package/src/types.ts ADDED
@@ -0,0 +1,41 @@
1
+ // Types for expo-hiu-print
2
+ import { NativeModule } from "react-native";
3
+
4
+ export interface Printer {
5
+ name: string;
6
+ host: string;
7
+ port: number;
8
+ paperWidth: number;
9
+ }
10
+
11
+ export interface PrintElement {
12
+ type: "text" | "line" | "table" | "qr" | "feed" | "cut";
13
+ value?: string;
14
+ align?: "left" | "center" | "right";
15
+ bold?: boolean;
16
+ size?: "small" | "normal" | "large";
17
+ headers?: {
18
+ text: string;
19
+ align?: "left" | "center" | "right";
20
+ bold?: boolean;
21
+ }[];
22
+ rows?: {
23
+ text: string;
24
+ align?: "left" | "center" | "right";
25
+ bold?: boolean;
26
+ }[][];
27
+ data?: string;
28
+ lines?: number;
29
+ widthPercent?: number;
30
+ }
31
+
32
+ export interface PrintJob {
33
+ printers: Printer[];
34
+ elements: PrintElement[];
35
+ }
36
+
37
+ export interface ExpoHiuPrintModuleType extends NativeModule {
38
+ print(job: PrintJob): Promise<void>;
39
+ openCashBox(job: { printers: Printer[] }): Promise<void>;
40
+ checkPrinterStatus(printer: Printer): Promise<"connected" | "disconnected">;
41
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "expo-module-scripts/tsconfig.base",
3
+ "compilerOptions": {
4
+ "outDir": "./src",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["./src"],
8
+ "exclude": ["**/__mocks__/*", "**/__tests__/*"]
9
+ }