@delicity/capacitor-thermal-printer 7.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/DelicityThermalPrinter.podspec +28 -0
- package/LICENSE +21 -0
- package/README.md +649 -0
- package/android/build.gradle +122 -0
- package/android/src/main/AndroidManifest.xml +38 -0
- package/android/src/main/java/com/delicity/thermalprinter/Logger.kt +50 -0
- package/android/src/main/java/com/delicity/thermalprinter/ThermalPrinterEngine.kt +528 -0
- package/android/src/main/java/com/delicity/thermalprinter/ThermalPrinterPlugin.kt +334 -0
- package/android/src/main/java/com/delicity/thermalprinter/adapters/BleAdapter.kt +125 -0
- package/android/src/main/java/com/delicity/thermalprinter/adapters/BrotherAdapter.kt +206 -0
- package/android/src/main/java/com/delicity/thermalprinter/adapters/EpsonAdapter.kt +384 -0
- package/android/src/main/java/com/delicity/thermalprinter/adapters/EscPosAdapter.kt +160 -0
- package/android/src/main/java/com/delicity/thermalprinter/adapters/EscPosCommands.kt +42 -0
- package/android/src/main/java/com/delicity/thermalprinter/adapters/EscPosTextEncoder.kt +138 -0
- package/android/src/main/java/com/delicity/thermalprinter/adapters/PrinterAdapter.kt +95 -0
- package/android/src/main/java/com/delicity/thermalprinter/adapters/RawTcpAdapter.kt +96 -0
- package/android/src/main/java/com/delicity/thermalprinter/adapters/SdkContract.kt +158 -0
- package/android/src/main/java/com/delicity/thermalprinter/adapters/SdkReflect.kt +104 -0
- package/android/src/main/java/com/delicity/thermalprinter/adapters/StarAdapter.kt +322 -0
- package/android/src/main/java/com/delicity/thermalprinter/adapters/UsbAdapter.kt +248 -0
- package/android/src/main/java/com/delicity/thermalprinter/adapters/ZebraAdapter.kt +207 -0
- package/android/src/main/java/com/delicity/thermalprinter/discovery/AdapterPriority.kt +39 -0
- package/android/src/main/java/com/delicity/thermalprinter/discovery/BleScanner.kt +70 -0
- package/android/src/main/java/com/delicity/thermalprinter/discovery/BluetoothClassicScanner.kt +112 -0
- package/android/src/main/java/com/delicity/thermalprinter/discovery/DiscoveryManager.kt +136 -0
- package/android/src/main/java/com/delicity/thermalprinter/discovery/TcpScanner.kt +96 -0
- package/android/src/main/java/com/delicity/thermalprinter/image/ImageCache.kt +88 -0
- package/android/src/main/java/com/delicity/thermalprinter/image/ImageProcessor.kt +220 -0
- package/android/src/main/java/com/delicity/thermalprinter/image/TextRasterizer.kt +99 -0
- package/android/src/main/java/com/delicity/thermalprinter/model/Models.kt +206 -0
- package/android/src/main/java/com/delicity/thermalprinter/model/PrintItem.kt +100 -0
- package/android/src/main/java/com/delicity/thermalprinter/store/PrinterStore.kt +71 -0
- package/android/src/main/java/com/delicity/thermalprinter/transport/BleGattClient.kt +201 -0
- package/android/src/main/java/com/delicity/thermalprinter/transport/BluetoothSppTransport.kt +110 -0
- package/android/src/main/java/com/delicity/thermalprinter/transport/ByteTransport.kt +18 -0
- package/android/src/main/java/com/delicity/thermalprinter/transport/TcpTransport.kt +83 -0
- package/dist/esm/adapters/dedup.d.ts +26 -0
- package/dist/esm/adapters/dedup.js +66 -0
- package/dist/esm/adapters/dedup.js.map +1 -0
- package/dist/esm/adapters/priority.d.ts +29 -0
- package/dist/esm/adapters/priority.js +55 -0
- package/dist/esm/adapters/priority.js.map +1 -0
- package/dist/esm/core/enums.d.ts +61 -0
- package/dist/esm/core/enums.js +25 -0
- package/dist/esm/core/enums.js.map +1 -0
- package/dist/esm/core/errors.d.ts +16 -0
- package/dist/esm/core/errors.js +53 -0
- package/dist/esm/core/errors.js.map +1 -0
- package/dist/esm/core/escpos-text.d.ts +33 -0
- package/dist/esm/core/escpos-text.js +239 -0
- package/dist/esm/core/escpos-text.js.map +1 -0
- package/dist/esm/core/imaging.d.ts +91 -0
- package/dist/esm/core/imaging.js +184 -0
- package/dist/esm/core/imaging.js.map +1 -0
- package/dist/esm/core/models.d.ts +131 -0
- package/dist/esm/core/models.js +2 -0
- package/dist/esm/core/models.js.map +1 -0
- package/dist/esm/core/options.d.ts +154 -0
- package/dist/esm/core/options.js +2 -0
- package/dist/esm/core/options.js.map +1 -0
- package/dist/esm/core/text.d.ts +138 -0
- package/dist/esm/core/text.js +14 -0
- package/dist/esm/core/text.js.map +1 -0
- package/dist/esm/definitions.d.ts +155 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +15 -0
- package/dist/esm/index.js +18 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +63 -0
- package/dist/esm/web.js +112 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +224 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +227 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/Adapters/BrotherAdapter.swift +139 -0
- package/ios/Plugin/Adapters/EpsonAdapter.swift +131 -0
- package/ios/Plugin/Adapters/EscPosAdapter.swift +106 -0
- package/ios/Plugin/Adapters/EscPosCommands.swift +32 -0
- package/ios/Plugin/Adapters/EscPosTextEncoder.swift +115 -0
- package/ios/Plugin/Adapters/PrinterAdapter.swift +44 -0
- package/ios/Plugin/Adapters/RawTcpAdapter.swift +70 -0
- package/ios/Plugin/Adapters/StarAdapter.swift +305 -0
- package/ios/Plugin/Adapters/ZebraAdapter.swift +119 -0
- package/ios/Plugin/Discovery/AdapterPriority.swift +21 -0
- package/ios/Plugin/Discovery/BonjourScanner.swift +51 -0
- package/ios/Plugin/Discovery/DiscoveryManager.swift +86 -0
- package/ios/Plugin/Image/ImageCache.swift +73 -0
- package/ios/Plugin/Image/ImageProcessor.swift +168 -0
- package/ios/Plugin/Image/TextRasterizer.swift +81 -0
- package/ios/Plugin/Logger.swift +33 -0
- package/ios/Plugin/Model/Models.swift +174 -0
- package/ios/Plugin/Model/PrintItem.swift +111 -0
- package/ios/Plugin/Store/PrinterStore.swift +51 -0
- package/ios/Plugin/ThermalPrinterEngine.swift +395 -0
- package/ios/Plugin/ThermalPrinterPlugin.m +22 -0
- package/ios/Plugin/ThermalPrinterPlugin.swift +258 -0
- package/ios/Plugin/Transport/TcpTransport.swift +89 -0
- package/package.json +96 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
package com.delicity.thermalprinter.adapters
|
|
2
|
+
|
|
3
|
+
import com.delicity.thermalprinter.model.PrintItem
|
|
4
|
+
import com.delicity.thermalprinter.model.TextStyle
|
|
5
|
+
import java.io.ByteArrayOutputStream
|
|
6
|
+
import android.util.Base64
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Encodeur ESC/POS texte (miroir Kotlin de src/core/escpos-text.ts).
|
|
10
|
+
* Transforme une liste de PrintItem en flux d'octets ESC/POS.
|
|
11
|
+
*
|
|
12
|
+
* Les items `image` sont signalés via [imageIndexes] et NON encodés ici :
|
|
13
|
+
* ils sont rendus par le pipeline image (ImageProcessor) et insérés par le moteur.
|
|
14
|
+
*/
|
|
15
|
+
object EscPosTextEncoder {
|
|
16
|
+
|
|
17
|
+
private const val ESC = 0x1B
|
|
18
|
+
private const val GS = 0x1D
|
|
19
|
+
private const val LF = 0x0A
|
|
20
|
+
|
|
21
|
+
private val CODE_PAGE_TO_ESC_T = mapOf(
|
|
22
|
+
"CP437" to 0, "CP850" to 2, "CP858" to 19, "WPC1252" to 16, "CP852" to 18, "CP866" to 17,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
private val BARCODE_M = mapOf(
|
|
26
|
+
"UPC_A" to 65, "UPC_E" to 66, "EAN13" to 67, "EAN8" to 68,
|
|
27
|
+
"CODE39" to 69, "ITF" to 70, "CODABAR" to 71, "CODE93" to 72, "CODE128" to 73,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
data class Encoded(val bytes: ByteArray, val imageIndexes: List<Int>)
|
|
31
|
+
|
|
32
|
+
fun encodeString(value: String): ByteArray {
|
|
33
|
+
val out = ByteArrayOutputStream()
|
|
34
|
+
value.codePoints().forEach { cp -> out.write(if (cp <= 0xFF) cp else 0x3F) }
|
|
35
|
+
return out.toByteArray()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fun sizeByte(w: Int, h: Int): Int {
|
|
39
|
+
val ww = (w.coerceIn(1, 8)) - 1
|
|
40
|
+
val hh = (h.coerceIn(1, 8)) - 1
|
|
41
|
+
return (ww shl 4) or hh
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private fun openStyle(out: ByteArrayOutputStream, s: TextStyle, defaultCodePage: String) {
|
|
45
|
+
val cp = s.codePageId ?: (CODE_PAGE_TO_ESC_T[s.codePage ?: defaultCodePage] ?: 16)
|
|
46
|
+
out.write(byteArrayOf(ESC.toByte(), 0x74, (cp and 0xFF).toByte()))
|
|
47
|
+
val align = when (s.align) { "center" -> 1; "right" -> 2; else -> 0 }
|
|
48
|
+
out.write(byteArrayOf(ESC.toByte(), 0x61, align.toByte()))
|
|
49
|
+
out.write(byteArrayOf(ESC.toByte(), 0x4D, if (s.font == "B") 1 else 0))
|
|
50
|
+
out.write(byteArrayOf(ESC.toByte(), 0x45, if (s.bold) 1 else 0))
|
|
51
|
+
out.write(byteArrayOf(ESC.toByte(), 0x47, if (s.doubleStrike) 1 else 0))
|
|
52
|
+
val ul = when (s.underline) { "single" -> 1; "double" -> 2; else -> 0 }
|
|
53
|
+
out.write(byteArrayOf(ESC.toByte(), 0x2D, ul.toByte()))
|
|
54
|
+
out.write(byteArrayOf(GS.toByte(), 0x42, if (s.invert) 1 else 0))
|
|
55
|
+
out.write(byteArrayOf(ESC.toByte(), 0x7B, if (s.upsideDown) 1 else 0))
|
|
56
|
+
out.write(byteArrayOf(ESC.toByte(), 0x56, if (s.rotate90) 1 else 0))
|
|
57
|
+
out.write(byteArrayOf(GS.toByte(), 0x21, sizeByte(s.widthMultiplier, s.heightMultiplier).toByte()))
|
|
58
|
+
s.letterSpacing?.let { out.write(byteArrayOf(ESC.toByte(), 0x20, (it and 0xFF).toByte())) }
|
|
59
|
+
if (s.lineSpacing != null) out.write(byteArrayOf(ESC.toByte(), 0x33, (s.lineSpacing and 0xFF).toByte()))
|
|
60
|
+
else out.write(byteArrayOf(ESC.toByte(), 0x32))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private fun reset(out: ByteArrayOutputStream) = out.write(byteArrayOf(ESC.toByte(), 0x40))
|
|
64
|
+
|
|
65
|
+
private fun qrCode(out: ByteArrayOutputStream, item: PrintItem.QrCode) {
|
|
66
|
+
val align = when (item.align) { "left" -> 0; "right" -> 2; else -> 1 }
|
|
67
|
+
out.write(byteArrayOf(ESC.toByte(), 0x61, align.toByte()))
|
|
68
|
+
out.write(byteArrayOf(GS.toByte(), 0x28, 0x6B, 0x04, 0x00, 0x31, 0x41, 0x32, 0x00))
|
|
69
|
+
val size = item.size.coerceIn(1, 16)
|
|
70
|
+
out.write(byteArrayOf(GS.toByte(), 0x28, 0x6B, 0x03, 0x00, 0x31, 0x43, size.toByte()))
|
|
71
|
+
val ec = when (item.ec) { "L" -> 48; "Q" -> 50; "H" -> 51; else -> 49 }
|
|
72
|
+
out.write(byteArrayOf(GS.toByte(), 0x28, 0x6B, 0x03, 0x00, 0x31, 0x45, ec.toByte()))
|
|
73
|
+
val data = encodeString(item.value)
|
|
74
|
+
val len = data.size + 3
|
|
75
|
+
out.write(byteArrayOf(GS.toByte(), 0x28, 0x6B, (len and 0xFF).toByte(), ((len shr 8) and 0xFF).toByte(), 0x31, 0x50, 0x30))
|
|
76
|
+
out.write(data)
|
|
77
|
+
out.write(byteArrayOf(GS.toByte(), 0x28, 0x6B, 0x03, 0x00, 0x31, 0x51, 0x30))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private fun barcode(out: ByteArrayOutputStream, item: PrintItem.Barcode) {
|
|
81
|
+
val align = when (item.align) { "left" -> 0; "right" -> 2; else -> 1 }
|
|
82
|
+
out.write(byteArrayOf(ESC.toByte(), 0x61, align.toByte()))
|
|
83
|
+
val hri = when (item.hri) { "above" -> 1; "both" -> 3; "none" -> 0; else -> 2 }
|
|
84
|
+
out.write(byteArrayOf(GS.toByte(), 0x48, hri.toByte()))
|
|
85
|
+
out.write(byteArrayOf(GS.toByte(), 0x68, item.height.coerceIn(1, 255).toByte()))
|
|
86
|
+
out.write(byteArrayOf(GS.toByte(), 0x77, item.width.coerceIn(2, 6).toByte()))
|
|
87
|
+
val m = BARCODE_M[item.symbology] ?: 73
|
|
88
|
+
var data = encodeString(item.value)
|
|
89
|
+
if (item.symbology == "CODE128" && !(data.isNotEmpty() && data[0].toInt() == 0x7B)) {
|
|
90
|
+
data = byteArrayOf(0x7B, 0x42) + data
|
|
91
|
+
}
|
|
92
|
+
out.write(byteArrayOf(GS.toByte(), 0x6B, m.toByte(), data.size.toByte()))
|
|
93
|
+
out.write(data)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fun encode(items: List<PrintItem>, defaultCodePage: String = "WPC1252", columns: Int = 48): Encoded {
|
|
97
|
+
val out = ByteArrayOutputStream()
|
|
98
|
+
val imageIndexes = mutableListOf<Int>()
|
|
99
|
+
reset(out)
|
|
100
|
+
items.forEachIndexed { index, item ->
|
|
101
|
+
when (item) {
|
|
102
|
+
is PrintItem.Text -> {
|
|
103
|
+
openStyle(out, item.style, defaultCodePage)
|
|
104
|
+
out.write(encodeString(item.value))
|
|
105
|
+
if (item.style.newline) out.write(LF)
|
|
106
|
+
reset(out)
|
|
107
|
+
}
|
|
108
|
+
is PrintItem.Feed -> out.write(byteArrayOf(ESC.toByte(), 0x64, item.lines.coerceIn(0, 255).toByte()))
|
|
109
|
+
is PrintItem.Divider -> {
|
|
110
|
+
val ch = (item.char.firstOrNull() ?: '-').code
|
|
111
|
+
val n = item.columns ?: columns
|
|
112
|
+
val align = when (item.align) { "center" -> 1; "right" -> 2; else -> 0 }
|
|
113
|
+
out.write(byteArrayOf(ESC.toByte(), 0x61, align.toByte()))
|
|
114
|
+
if (item.bold) out.write(byteArrayOf(ESC.toByte(), 0x45, 1))
|
|
115
|
+
repeat(n) { out.write(ch) }
|
|
116
|
+
out.write(LF)
|
|
117
|
+
reset(out)
|
|
118
|
+
}
|
|
119
|
+
is PrintItem.QrCode -> qrCode(out, item)
|
|
120
|
+
is PrintItem.Barcode -> barcode(out, item)
|
|
121
|
+
is PrintItem.CashDrawer -> out.write(
|
|
122
|
+
if (item.pin == 5) byteArrayOf(ESC.toByte(), 0x70, 0x01, 0x19, 0xFA.toByte())
|
|
123
|
+
else byteArrayOf(ESC.toByte(), 0x70, 0x00, 0x19, 0xFA.toByte()),
|
|
124
|
+
)
|
|
125
|
+
is PrintItem.Cut -> {
|
|
126
|
+
if (item.feedBefore > 0) out.write(byteArrayOf(ESC.toByte(), 0x64, (item.feedBefore and 0xFF).toByte()))
|
|
127
|
+
out.write(if (item.mode == "full") byteArrayOf(GS.toByte(), 0x56, 0x00) else byteArrayOf(GS.toByte(), 0x56, 0x01))
|
|
128
|
+
}
|
|
129
|
+
is PrintItem.Raw -> {
|
|
130
|
+
val clean = if (item.bytesBase64.contains("base64,")) item.bytesBase64.substringAfter("base64,") else item.bytesBase64
|
|
131
|
+
out.write(Base64.decode(clean, Base64.DEFAULT))
|
|
132
|
+
}
|
|
133
|
+
is PrintItem.Image -> imageIndexes.add(index)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return Encoded(out.toByteArray(), imageIndexes)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
package com.delicity.thermalprinter.adapters
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import com.delicity.thermalprinter.model.AdapterId
|
|
5
|
+
import com.delicity.thermalprinter.model.DiscoveredPrinter
|
|
6
|
+
import com.delicity.thermalprinter.model.PrinterProfile
|
|
7
|
+
import com.delicity.thermalprinter.model.PrinterStatus
|
|
8
|
+
import com.delicity.thermalprinter.model.RenderOptions
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Contrat commun à tous les adapters natifs Android.
|
|
12
|
+
*
|
|
13
|
+
* Un adapter encapsule TOUT ce qui est spécifique à une famille d'imprimantes :
|
|
14
|
+
* - sa découverte (via SDK ou scan générique),
|
|
15
|
+
* - sa connexion / reconnexion,
|
|
16
|
+
* - la conversion d'un Bitmap déjà binarisé en commandes natives,
|
|
17
|
+
* - l'envoi,
|
|
18
|
+
* - la lecture de statut.
|
|
19
|
+
*
|
|
20
|
+
* Le moteur (ThermalPrinterEngine) orchestre les adapters et applique la priorité.
|
|
21
|
+
* Toutes les méthodes longues sont `suspend` (exécutées sur Dispatchers.IO).
|
|
22
|
+
*/
|
|
23
|
+
interface PrinterAdapter {
|
|
24
|
+
|
|
25
|
+
val id: AdapterId
|
|
26
|
+
|
|
27
|
+
/** True si le SDK requis est présent dans l'app (sinon l'adapter est ignoré). */
|
|
28
|
+
fun isAvailable(): Boolean
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Lance une découverte propre à cet adapter. Les résultats partiels peuvent
|
|
32
|
+
* être émis via [onFound] au fil de l'eau. La méthode se termine quand le
|
|
33
|
+
* scan est fini ou que [timeoutMs] est atteint.
|
|
34
|
+
*/
|
|
35
|
+
suspend fun discover(
|
|
36
|
+
timeoutMs: Long,
|
|
37
|
+
onFound: (DiscoveredPrinter) -> Unit,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
/** Indique si cet adapter sait gérer ce profil (transport + identité). */
|
|
41
|
+
fun canHandle(profile: PrinterProfile): Boolean
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* True si l'adapter sait imprimer des items texte NATIVEMENT (encodeur ESC/POS
|
|
45
|
+
* ou builder texte du SDK). Si false, le moteur effectue un repli automatique :
|
|
46
|
+
* il rend les items en image (TextRasterizer) puis appelle [printBitmap].
|
|
47
|
+
* Voir docs/SDK_INTEGRATION.md (printText sur marques SDK).
|
|
48
|
+
*/
|
|
49
|
+
fun supportsTextItems(): Boolean = false
|
|
50
|
+
|
|
51
|
+
/** Ouvre une connexion. Idempotent : ne rien faire si déjà connecté. */
|
|
52
|
+
suspend fun connect(profile: PrinterProfile, timeoutMs: Long)
|
|
53
|
+
|
|
54
|
+
/** True si une connexion active existe pour cette imprimante. */
|
|
55
|
+
fun isConnected(printerId: String): Boolean
|
|
56
|
+
|
|
57
|
+
/** Ferme la connexion. */
|
|
58
|
+
suspend fun disconnect(printerId: String)
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Imprime un bitmap.
|
|
62
|
+
*
|
|
63
|
+
* IMPORTANT : `bitmap` est DÉJÀ redimensionné à la largeur cible et
|
|
64
|
+
* binarisé/dithered par le moteur (ImageProcessor). Pour les adapters ESC/POS,
|
|
65
|
+
* il suffit de l'encoder en raster GS v 0. Pour les SDK fabricants, on passe
|
|
66
|
+
* le bitmap à l'API d'impression image du SDK.
|
|
67
|
+
*
|
|
68
|
+
* @return nombre d'octets envoyés (best effort).
|
|
69
|
+
*/
|
|
70
|
+
suspend fun printBitmap(profile: PrinterProfile, bitmap: Bitmap, options: RenderOptions): Int
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Imprime une liste d'items texte stylés (+ QR/code-barres/feed/cut...).
|
|
74
|
+
* Pour ESC/POS : encodage via EscPosTextEncoder. Pour les SDK fabricants :
|
|
75
|
+
* mapping vers le builder du SDK (voir docs/SDK_INTEGRATION.md).
|
|
76
|
+
*
|
|
77
|
+
* Implémentation par défaut : non supportée (les adapters SDK la surchargent
|
|
78
|
+
* avec leur builder texte ; ESC/POS et rawTcp l'implémentent via l'encodeur).
|
|
79
|
+
*
|
|
80
|
+
* @return nombre d'octets envoyés (best effort).
|
|
81
|
+
*/
|
|
82
|
+
suspend fun printItems(
|
|
83
|
+
profile: PrinterProfile,
|
|
84
|
+
items: List<com.delicity.thermalprinter.model.PrintItem>,
|
|
85
|
+
defaultCodePage: String,
|
|
86
|
+
cut: Boolean,
|
|
87
|
+
feedLines: Int,
|
|
88
|
+
): Int = throw com.delicity.thermalprinter.model.PrinterException(
|
|
89
|
+
com.delicity.thermalprinter.model.ErrorCode.SDK_NOT_AVAILABLE,
|
|
90
|
+
"printText non implémenté pour cet adapter (${id.value}) — voir docs/SDK_INTEGRATION.md",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
/** Lit le statut. Renvoie supportsStatus=false via online/paper=unknown si non supporté. */
|
|
94
|
+
suspend fun getStatus(profile: PrinterProfile): PrinterStatus
|
|
95
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
package com.delicity.thermalprinter.adapters
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import com.delicity.thermalprinter.image.ImageProcessor
|
|
5
|
+
import com.delicity.thermalprinter.model.AdapterId
|
|
6
|
+
import com.delicity.thermalprinter.model.DiscoveredPrinter
|
|
7
|
+
import com.delicity.thermalprinter.model.ErrorCode
|
|
8
|
+
import com.delicity.thermalprinter.model.PrinterException
|
|
9
|
+
import com.delicity.thermalprinter.model.PrinterProfile
|
|
10
|
+
import com.delicity.thermalprinter.model.PrinterStatus
|
|
11
|
+
import com.delicity.thermalprinter.model.RenderOptions
|
|
12
|
+
import com.delicity.thermalprinter.model.Transport
|
|
13
|
+
import com.delicity.thermalprinter.transport.TcpTransport
|
|
14
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Adapter "filet de sécurité" réseau : envoie un raster ESC/POS sur un socket TCP
|
|
18
|
+
* brut sans rien présumer du dialecte de statut. Utilisé quand une imprimante
|
|
19
|
+
* réseau n'est identifiée par aucun SDK et n'a pas confirmé l'ESC/POS.
|
|
20
|
+
*
|
|
21
|
+
* Priorité la plus basse (voir priority.ts). Ne lit jamais de statut.
|
|
22
|
+
*/
|
|
23
|
+
class RawTcpAdapter : PrinterAdapter {
|
|
24
|
+
|
|
25
|
+
override val id = AdapterId.RAW_TCP
|
|
26
|
+
private val connections = ConcurrentHashMap<String, TcpTransport>()
|
|
27
|
+
|
|
28
|
+
override fun isAvailable(): Boolean = true
|
|
29
|
+
|
|
30
|
+
override fun supportsTextItems(): Boolean = true
|
|
31
|
+
|
|
32
|
+
override suspend fun discover(timeoutMs: Long, onFound: (DiscoveredPrinter) -> Unit) {
|
|
33
|
+
// Délégué au TcpScanner.
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override fun canHandle(profile: PrinterProfile): Boolean =
|
|
37
|
+
profile.adapter == AdapterId.RAW_TCP &&
|
|
38
|
+
profile.transport in setOf(Transport.WIFI, Transport.ETHERNET)
|
|
39
|
+
|
|
40
|
+
override suspend fun connect(profile: PrinterProfile, timeoutMs: Long) {
|
|
41
|
+
if (isConnected(profile.id)) return
|
|
42
|
+
val idx = profile.address.lastIndexOf(':')
|
|
43
|
+
val host = if (idx > 0) profile.address.substring(0, idx) else profile.address
|
|
44
|
+
val port = if (idx > 0) profile.address.substring(idx + 1).toIntOrNull() ?: 9100 else 9100
|
|
45
|
+
val t = TcpTransport(host, port)
|
|
46
|
+
t.open(timeoutMs)
|
|
47
|
+
connections[profile.id] = t
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
override fun isConnected(printerId: String): Boolean = connections[printerId]?.isOpen == true
|
|
51
|
+
|
|
52
|
+
override suspend fun disconnect(printerId: String) {
|
|
53
|
+
connections.remove(printerId)?.close()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override suspend fun printBitmap(profile: PrinterProfile, bitmap: Bitmap, options: RenderOptions): Int {
|
|
57
|
+
val t = connections[profile.id]
|
|
58
|
+
?: throw PrinterException(ErrorCode.CONNECTION_FAILED, "rawTcp non connecté")
|
|
59
|
+
val mono = ImageProcessor.toMono(bitmap, options)
|
|
60
|
+
val raster = ImageProcessor.encodeEscPosRaster(mono)
|
|
61
|
+
val job = EscPosCommands.buildJob(raster, options.align, options.feedLines, options.cut, options.openCashDrawer)
|
|
62
|
+
t.write(job)
|
|
63
|
+
return job.size
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override suspend fun printItems(
|
|
67
|
+
profile: PrinterProfile,
|
|
68
|
+
items: List<com.delicity.thermalprinter.model.PrintItem>,
|
|
69
|
+
defaultCodePage: String,
|
|
70
|
+
cut: Boolean,
|
|
71
|
+
feedLines: Int,
|
|
72
|
+
): Int {
|
|
73
|
+
val t = connections[profile.id]
|
|
74
|
+
?: throw PrinterException(ErrorCode.CONNECTION_FAILED, "rawTcp non connecté")
|
|
75
|
+
val columns = if (profile.capabilities.printableDots <= 420) 32 else 48
|
|
76
|
+
val encoded = EscPosTextEncoder.encode(items, defaultCodePage, columns)
|
|
77
|
+
val out = java.io.ByteArrayOutputStream()
|
|
78
|
+
out.write(encoded.bytes)
|
|
79
|
+
if (feedLines > 0) out.write(EscPosCommands.feed(feedLines))
|
|
80
|
+
if (cut) out.write(EscPosCommands.CUT_PARTIAL)
|
|
81
|
+
val job = out.toByteArray()
|
|
82
|
+
t.write(job)
|
|
83
|
+
return job.size
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
override suspend fun getStatus(profile: PrinterProfile): PrinterStatus {
|
|
87
|
+
val connected = isConnected(profile.id)
|
|
88
|
+
return PrinterStatus(
|
|
89
|
+
id = profile.id,
|
|
90
|
+
connection = if (connected) "connected" else "disconnected",
|
|
91
|
+
online = connected,
|
|
92
|
+
paper = "unknown",
|
|
93
|
+
rawStatus = "rawTcp: statut non supporté",
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
package com.delicity.thermalprinter.adapters
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Contrat de réflexion : énumère la **surface exacte** (classes, constructeurs,
|
|
5
|
+
* méthodes, champs) que chaque adapter SDK appelle par réflexion. Sert à deux choses :
|
|
6
|
+
*
|
|
7
|
+
* 1. En CI (faux SDK présent) : un test vérifie que le faux satisfait le contrat
|
|
8
|
+
* → garde les faux fidèles à ce que l'adapter attend.
|
|
9
|
+
* 2. Avec le VRAI SDK (binaire déposé dans l'app) : appeler [verify] confirme que
|
|
10
|
+
* l'API réelle correspond à notre réflexion — **sans imprimante**. Tout symbole
|
|
11
|
+
* manquant est remonté (au lieu d'échouer silencieusement à l'exécution).
|
|
12
|
+
*
|
|
13
|
+
* Usage (dans l'app, après dépôt du binaire) :
|
|
14
|
+
* ```
|
|
15
|
+
* val missing = SdkContract.verify(SdkContract.EPSON)
|
|
16
|
+
* if (missing.isNotEmpty()) Log.e("SDK", "API Epson divergente: $missing")
|
|
17
|
+
* ```
|
|
18
|
+
* Voir docs/TESTING_SDK.md.
|
|
19
|
+
*/
|
|
20
|
+
object SdkContract {
|
|
21
|
+
|
|
22
|
+
data class Method(val name: String, val params: List<String> = emptyList())
|
|
23
|
+
data class Requirement(
|
|
24
|
+
val className: String,
|
|
25
|
+
val constructors: List<List<String>> = emptyList(),
|
|
26
|
+
val methods: List<Method> = emptyList(),
|
|
27
|
+
val fields: List<String> = emptyList(),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
/** Vérifie une liste de requirements ; renvoie les symboles manquants (vide = OK). */
|
|
31
|
+
fun verify(requirements: List<Requirement>): List<String> {
|
|
32
|
+
val missing = mutableListOf<String>()
|
|
33
|
+
for (r in requirements) {
|
|
34
|
+
val cls = try {
|
|
35
|
+
Class.forName(r.className)
|
|
36
|
+
} catch (e: Throwable) {
|
|
37
|
+
missing.add("class ${r.className}")
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
for (c in r.constructors) {
|
|
41
|
+
try {
|
|
42
|
+
cls.getConstructor(*c.map(::typeOf).toTypedArray())
|
|
43
|
+
} catch (e: Throwable) {
|
|
44
|
+
missing.add("${r.className}.<init>(${c.joinToString()})")
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (m in r.methods) {
|
|
48
|
+
try {
|
|
49
|
+
cls.getMethod(m.name, *m.params.map(::typeOf).toTypedArray())
|
|
50
|
+
} catch (e: Throwable) {
|
|
51
|
+
missing.add("${r.className}#${m.name}(${m.params.joinToString()})")
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (f in r.fields) {
|
|
55
|
+
try {
|
|
56
|
+
cls.getField(f)
|
|
57
|
+
} catch (e: Throwable) {
|
|
58
|
+
missing.add("${r.className}.$f")
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return missing
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Vérifie toutes les marches dont le SDK est présent (les absentes sont ignorées). */
|
|
66
|
+
fun verifyAll(): Map<String, List<String>> = buildMap {
|
|
67
|
+
if (SdkReflect.exists("com.epson.epos2.printer.Printer")) put("epson", verify(EPSON))
|
|
68
|
+
if (SdkReflect.exists("com.zebra.sdk.comm.Connection")) put("zebra", verify(ZEBRA))
|
|
69
|
+
if (SdkReflect.exists("com.brother.sdk.lmprinter.PrinterDriverGenerator")) put("brother", verify(BROTHER))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private fun typeOf(name: String): Class<*> = when (name) {
|
|
73
|
+
"int" -> Int::class.javaPrimitiveType!!
|
|
74
|
+
"long" -> Long::class.javaPrimitiveType!!
|
|
75
|
+
"double" -> Double::class.javaPrimitiveType!!
|
|
76
|
+
"float" -> Float::class.javaPrimitiveType!!
|
|
77
|
+
"boolean" -> Boolean::class.javaPrimitiveType!!
|
|
78
|
+
else -> Class.forName(name)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private const val BITMAP = "android.graphics.Bitmap"
|
|
82
|
+
private const val CONTEXT = "android.content.Context"
|
|
83
|
+
private const val BT_ADAPTER = "android.bluetooth.BluetoothAdapter"
|
|
84
|
+
|
|
85
|
+
// ------------------------------------------------------------------ Epson
|
|
86
|
+
val EPSON = listOf(
|
|
87
|
+
Requirement(
|
|
88
|
+
"com.epson.epos2.printer.Printer",
|
|
89
|
+
constructors = listOf(listOf("int", "int", CONTEXT)),
|
|
90
|
+
methods = listOf(
|
|
91
|
+
Method("connect", listOf("java.lang.String", "int")),
|
|
92
|
+
Method("disconnect"),
|
|
93
|
+
Method("clearCommandBuffer"),
|
|
94
|
+
Method("beginTransaction"),
|
|
95
|
+
Method("endTransaction"),
|
|
96
|
+
Method("addImage", listOf(BITMAP, "int", "int", "int", "int", "int", "int", "int", "double", "int")),
|
|
97
|
+
Method("addCut", listOf("int")),
|
|
98
|
+
Method("addPulse", listOf("int", "int")),
|
|
99
|
+
Method("sendData", listOf("int")),
|
|
100
|
+
Method("getStatus"),
|
|
101
|
+
Method("addText", listOf("java.lang.String")),
|
|
102
|
+
Method("addTextAlign", listOf("int")),
|
|
103
|
+
Method("addTextStyle", listOf("int", "int", "int", "int")),
|
|
104
|
+
Method("addTextSize", listOf("int", "int")),
|
|
105
|
+
Method("addFeedLine", listOf("int")),
|
|
106
|
+
Method("addSymbol", listOf("java.lang.String", "int", "int", "int", "int", "int")),
|
|
107
|
+
Method("addBarcode", listOf("java.lang.String", "int", "int", "int", "int", "int")),
|
|
108
|
+
),
|
|
109
|
+
fields = listOf(
|
|
110
|
+
"TRUE", "FALSE", "MODEL_ANK", "PARAM_DEFAULT", "COLOR_1", "MODE_MONO", "COMPRESS_AUTO",
|
|
111
|
+
"HALFTONE_DITHER", "HALFTONE_ERROR_DIFFUSION", "HALFTONE_THRESHOLD",
|
|
112
|
+
"CUT_FEED", "DRAWER_2PIN", "PULSE_100", "PAPER_EMPTY", "PAPER_NEAR_END",
|
|
113
|
+
"ALIGN_LEFT", "ALIGN_CENTER", "ALIGN_RIGHT", "LEVEL_M", "FONT_A",
|
|
114
|
+
),
|
|
115
|
+
),
|
|
116
|
+
Requirement("com.epson.epos2.printer.PrinterStatusInfo", fields = listOf("connection", "online", "paper", "coverOpen")),
|
|
117
|
+
Requirement(
|
|
118
|
+
"com.epson.epos2.discovery.Discovery",
|
|
119
|
+
methods = listOf(
|
|
120
|
+
Method("start", listOf(CONTEXT, "com.epson.epos2.discovery.FilterOption", "com.epson.epos2.discovery.DiscoveryListener")),
|
|
121
|
+
Method("stop"),
|
|
122
|
+
),
|
|
123
|
+
fields = listOf("TYPE_PRINTER"),
|
|
124
|
+
),
|
|
125
|
+
Requirement("com.epson.epos2.discovery.FilterOption", constructors = listOf(emptyList()), methods = listOf(Method("setDeviceType", listOf("int")))),
|
|
126
|
+
Requirement("com.epson.epos2.discovery.DeviceInfo", methods = listOf(Method("getTarget"), Method("getDeviceName"))),
|
|
127
|
+
Requirement("com.epson.epos2.discovery.DiscoveryListener"),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
// ------------------------------------------------------------------ Zebra
|
|
131
|
+
val ZEBRA = listOf(
|
|
132
|
+
Requirement("com.zebra.sdk.comm.Connection", methods = listOf(Method("open"), Method("close"), Method("isConnected"))),
|
|
133
|
+
Requirement("com.zebra.sdk.comm.TcpConnection", constructors = listOf(listOf("java.lang.String", "int"))),
|
|
134
|
+
Requirement("com.zebra.sdk.comm.BluetoothConnection", constructors = listOf(listOf("java.lang.String"))),
|
|
135
|
+
Requirement("com.zebra.sdk.printer.ZebraPrinterFactory", methods = listOf(Method("getInstance", listOf("com.zebra.sdk.comm.Connection")))),
|
|
136
|
+
Requirement("com.zebra.sdk.graphics.ZebraImageFactory", methods = listOf(Method("getImage", listOf(BITMAP)))),
|
|
137
|
+
Requirement("com.zebra.sdk.graphics.ZebraImageI"),
|
|
138
|
+
Requirement("com.zebra.sdk.printer.discovery.NetworkDiscoverer", methods = listOf(Method("findPrinters", listOf("com.zebra.sdk.printer.discovery.DiscoveryHandler")))),
|
|
139
|
+
Requirement("com.zebra.sdk.printer.discovery.BluetoothDiscoverer", methods = listOf(Method("findPrinters", listOf(CONTEXT, "com.zebra.sdk.printer.discovery.DiscoveryHandler")))),
|
|
140
|
+
Requirement("com.zebra.sdk.printer.discovery.DiscoveryHandler"),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------- Brother
|
|
144
|
+
val BROTHER = listOf(
|
|
145
|
+
Requirement("com.brother.sdk.lmprinter.PrinterDriverGenerator", methods = listOf(Method("openChannel", listOf("com.brother.sdk.lmprinter.Channel")))),
|
|
146
|
+
Requirement(
|
|
147
|
+
"com.brother.sdk.lmprinter.Channel",
|
|
148
|
+
methods = listOf(
|
|
149
|
+
Method("newWifiChannel", listOf("java.lang.String")),
|
|
150
|
+
Method("newBluetoothChannel", listOf("java.lang.String", BT_ADAPTER)),
|
|
151
|
+
Method("newBluetoothLowEnergyChannel", listOf("java.lang.String", CONTEXT, BT_ADAPTER)),
|
|
152
|
+
),
|
|
153
|
+
),
|
|
154
|
+
Requirement("com.brother.sdk.lmprinter.PrinterDriver", methods = listOf(Method("printImage", listOf(BITMAP, "com.brother.sdk.lmprinter.setting.PrintImageSettings")), Method("closeChannel"))),
|
|
155
|
+
Requirement("com.brother.sdk.lmprinter.setting.PrinterModel"),
|
|
156
|
+
Requirement("com.brother.sdk.lmprinter.setting.PrintImageSettings"),
|
|
157
|
+
)
|
|
158
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
package com.delicity.thermalprinter.adapters
|
|
2
|
+
|
|
3
|
+
import java.lang.reflect.InvocationHandler
|
|
4
|
+
import java.lang.reflect.Method
|
|
5
|
+
import java.lang.reflect.Proxy
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Boîte à outils de réflexion pour piloter les SDK fabricants NON redistribuables
|
|
9
|
+
* (Epson ePOS2, Brother, Zebra Link-OS) SANS dépendance de compilation.
|
|
10
|
+
*
|
|
11
|
+
* Pourquoi la réflexion ? Les licences de ces SDK interdisent leur redistribution
|
|
12
|
+
* (Maven Central / CocoaPods), donc on ne peut pas compiler le plugin contre leurs
|
|
13
|
+
* types. La réflexion permet :
|
|
14
|
+
* - de compiler/publier le plugin SANS les binaires,
|
|
15
|
+
* - d'activer automatiquement l'adapter quand l'app fournit le binaire
|
|
16
|
+
* (voir docs/SDK_INTEGRATION.md).
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ Le code réflexif n'est pas vérifié par le compilateur : toute évolution
|
|
19
|
+
* d'API du SDK doit être testée sur device avec le binaire réel.
|
|
20
|
+
*/
|
|
21
|
+
object SdkReflect {
|
|
22
|
+
|
|
23
|
+
fun classOrNull(name: String): Class<*>? = try {
|
|
24
|
+
Class.forName(name)
|
|
25
|
+
} catch (e: Throwable) {
|
|
26
|
+
null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fun exists(name: String): Boolean = classOrNull(name) != null
|
|
30
|
+
|
|
31
|
+
/** Instancie une classe via un constructeur dont on donne les types de paramètres. */
|
|
32
|
+
fun newInstance(className: String, paramTypes: Array<Class<*>>, args: Array<Any?>): Any {
|
|
33
|
+
val c = classOrNull(className) ?: error("Classe absente: $className")
|
|
34
|
+
return c.getConstructor(*paramTypes).newInstance(*args)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Appelle une méthode d'instance. */
|
|
38
|
+
fun call(target: Any, method: String, paramTypes: Array<Class<*>> = emptyArray(), args: Array<Any?> = emptyArray()): Any? =
|
|
39
|
+
target.javaClass.getMethod(method, *paramTypes).invoke(target, *args)
|
|
40
|
+
|
|
41
|
+
/** Appelle une méthode statique. */
|
|
42
|
+
fun callStatic(className: String, method: String, paramTypes: Array<Class<*>> = emptyArray(), args: Array<Any?> = emptyArray()): Any? {
|
|
43
|
+
val c = classOrNull(className) ?: error("Classe absente: $className")
|
|
44
|
+
return c.getMethod(method, *paramTypes).invoke(null, *args)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Lit une constante (champ static) — typiquement les `int` du SDK. */
|
|
48
|
+
fun staticInt(className: String, field: String, fallback: Int = 0): Int = try {
|
|
49
|
+
classOrNull(className)?.getField(field)?.getInt(null) ?: fallback
|
|
50
|
+
} catch (e: Throwable) {
|
|
51
|
+
fallback
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fun staticField(className: String, field: String): Any? = try {
|
|
55
|
+
classOrNull(className)?.getField(field)?.get(null)
|
|
56
|
+
} catch (e: Throwable) {
|
|
57
|
+
null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Lit un champ d'instance (public). */
|
|
61
|
+
fun field(target: Any, name: String): Any? = try {
|
|
62
|
+
target.javaClass.getField(name).get(target)
|
|
63
|
+
} catch (e: Throwable) {
|
|
64
|
+
null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fun intField(target: Any, name: String, fallback: Int = 0): Int =
|
|
68
|
+
(field(target, name) as? Int) ?: fallback
|
|
69
|
+
|
|
70
|
+
/** Valeur d'enum par nom. */
|
|
71
|
+
@Suppress("UNCHECKED_CAST")
|
|
72
|
+
fun enumValue(enumClass: String, name: String): Any? {
|
|
73
|
+
val c = classOrNull(enumClass) ?: return null
|
|
74
|
+
return try {
|
|
75
|
+
java.lang.Enum.valueOf(c as Class<out Enum<*>>, name)
|
|
76
|
+
} catch (e: Throwable) {
|
|
77
|
+
null
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Crée un proxy dynamique implémentant une interface listener du SDK.
|
|
83
|
+
* [handlers] mappe un nom de méthode -> lambda recevant les arguments.
|
|
84
|
+
*/
|
|
85
|
+
fun proxy(interfaceName: String, handlers: Map<String, (Array<Any?>) -> Any?>): Any {
|
|
86
|
+
val iface = classOrNull(interfaceName) ?: error("Interface absente: $interfaceName")
|
|
87
|
+
return Proxy.newProxyInstance(iface.classLoader, arrayOf(iface), object : InvocationHandler {
|
|
88
|
+
override fun invoke(proxy: Any?, method: Method, args: Array<out Any?>?): Any? {
|
|
89
|
+
val a = (args ?: emptyArray()).map { it }.toTypedArray()
|
|
90
|
+
return handlers[method.name]?.invoke(a) ?: defaultFor(method.returnType)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private fun defaultFor(t: Class<*>): Any? = when (t) {
|
|
96
|
+
Boolean::class.javaPrimitiveType -> false
|
|
97
|
+
Int::class.javaPrimitiveType -> 0
|
|
98
|
+
Long::class.javaPrimitiveType -> 0L
|
|
99
|
+
Double::class.javaPrimitiveType -> 0.0
|
|
100
|
+
Float::class.javaPrimitiveType -> 0f
|
|
101
|
+
Void.TYPE -> null
|
|
102
|
+
else -> null
|
|
103
|
+
}
|
|
104
|
+
}
|