Skip to content

Commit 5f1be8f

Browse files
committed
kn: Add support for experimental barcode writer
1 parent 9b8a93c commit 5f1be8f

File tree

8 files changed

+219
-24
lines changed

8 files changed

+219
-24
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ jobs:
158158
steps:
159159
- name: Checkout repository
160160
uses: actions/checkout@v4
161+
with:
162+
submodules: true
161163

162164
- name: Checkout toolchain initializer repository
163165
uses: actions/checkout@v4

.github/workflows/publish-kn.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ jobs:
2020
steps:
2121
- name: Checkout repository
2222
uses: actions/checkout@v4
23+
with:
24+
submodules: true
2325

2426
- name: Checkout toolchain initializer repository
2527
uses: actions/checkout@v4

wrappers/kn/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ import zxingcpp.BarcodeFormat
1919
import zxingcpp.BarcodeReader
2020
import zxingcpp.ImageFormat
2121
import zxingcpp.ImageView
22+
import zxingcpp.ImageViewImplNoCopy
2223

2324
val data: ByteArray = ... // the image data
2425
val width: Int = ... // the image width
2526
val height: Int = ... // the image height
26-
val format = ImageFormat.Lum // ImageFormat.Lum assumes grey scale image data
27+
val format: ImageView = ImageFormat.Lum // ImageFormat.Lum assumes grey scale image data
2728

28-
val image = ImageView(data, width, height, format)
29+
val image = ImageViewImplNoCopy(data, width, height, format)
2930
val barcodeReader = BarcodeReader().apply {
3031
formats = setOf(BarcodeFormat.EAN13, BarcodeFormat.QRCode)
3132
tryHarder = true

wrappers/kn/build.gradle.kts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ krossCompile {
9090
packageName = "zxingcpp.cinterop"
9191
includeDirs.from(buildDir)
9292
headers = listOf("$sourceDir/src/ZXingC.h")
93+
compilerOpts += "-DZXING_EXPERIMENTAL_API=ON"
9394
}
9495
cmake.apply {
9596
val buildDir = "$cmakeDir/{projectName}/{targetName}"
@@ -102,7 +103,9 @@ krossCompile {
102103
} + CustomCMakeCacheEntries(
103104
mapOf(
104105
"ZXING_READERS" to "ON",
105-
"ZXING_WRITERS" to "OFF",
106+
"ZXING_WRITERS" to "NEW",
107+
"ZXING_EXPERIMENTAL_API" to "ON",
108+
"ZXING_USE_BUNDLED_ZINT" to "ON",
106109
"ZXING_C_API" to "ON",
107110
)
108111
)).asCMakeParams

wrappers/kn/src/nativeMain/kotlin/zxingcpp/Barcode.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,23 @@ fun ZXing_Position.toKObject(): Position = Position(
5353

5454
@OptIn(ExperimentalForeignApi::class)
5555
class Barcode(val cValue: CValuesRef<ZXing_Barcode>) {
56+
companion object {
57+
@ExperimentalWriterApi
58+
fun fromText(text: String, opts: CreatorOptions): Barcode =
59+
ZXing_CreateBarcodeFromText(text, text.length, opts.cValue)?.toKObject()
60+
?: throw BarcodeReadingException(ZXing_LastErrorMsg()?.toKStringNullPtrHandledAndFree())
61+
@ExperimentalWriterApi
62+
fun fromText(text: String, format: BarcodeFormat): Barcode =
63+
fromText(text, CreatorOptions(format))
64+
@ExperimentalWriterApi
65+
fun fromBytes(bytes: ByteArray, opts: CreatorOptions): Barcode =
66+
ZXing_CreateBarcodeFromBytes(bytes.refTo(0), bytes.size, opts.cValue)?.toKObject()
67+
?: throw BarcodeReadingException(ZXing_LastErrorMsg()?.toKStringNullPtrHandledAndFree())
68+
@ExperimentalWriterApi
69+
fun fromBytes(bytes: ByteArray, format: BarcodeFormat): Barcode =
70+
fromBytes(bytes, CreatorOptions(format))
71+
}
72+
5673
val isValid: Boolean
5774
get() = ZXing_Barcode_isValid(cValue)
5875
val errorMsg: String? by lazy {
@@ -132,6 +149,12 @@ class Barcode(val cValue: CValuesRef<ZXing_Barcode>) {
132149
}
133150
}
134151

152+
@ExperimentalWriterApi
153+
fun Barcode.toSVG(opts: WriterOptions? = null): String = BarcodeWriter.writeToSVG(this, opts)
154+
155+
@ExperimentalWriterApi
156+
fun Barcode.toImage(opts: WriterOptions? = null): Image = BarcodeWriter.writeToImage(this, opts)
157+
135158
@OptIn(ExperimentalForeignApi::class)
136159
fun CValuesRef<ZXing_Barcode>.toKObject(): Barcode = Barcode(this)
137160

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package zxingcpp
2+
3+
import cnames.structs.ZXing_CreatorOptions
4+
import cnames.structs.ZXing_WriterOptions
5+
import kotlinx.cinterop.*
6+
import zxingcpp.cinterop.*
7+
import kotlin.experimental.ExperimentalNativeApi
8+
import kotlin.native.ref.createCleaner
9+
10+
// TODO: Remove this annotation when the API is stable
11+
@RequiresOptIn(level = RequiresOptIn.Level.ERROR, message = "The Writer API is experimental and may change in the future.")
12+
@Retention(AnnotationRetention.BINARY)
13+
annotation class ExperimentalWriterApi
14+
15+
16+
@ExperimentalWriterApi
17+
@OptIn(ExperimentalForeignApi::class)
18+
class BarcodeWriter : WriterOptions() {
19+
fun writeToSVG(barcode: Barcode): String = Companion.writeToSVG(barcode, this)
20+
21+
fun writeToImage(barcode: Barcode): Image = Companion.writeToImage(barcode, this)
22+
23+
companion object {
24+
@OptIn(ExperimentalForeignApi::class)
25+
fun writeToSVG(barcode: Barcode, opts: WriterOptions? = null): String = barcode.cValue.usePinned {
26+
ZXing_WriteBarcodeToSVG(it.get(), opts?.cValue)?.toKStringNullPtrHandledAndFree()
27+
?: throw BarcodeWritingException(ZXing_LastErrorMsg()?.toKStringNullPtrHandledAndFree())
28+
}
29+
30+
@OptIn(ExperimentalForeignApi::class)
31+
fun writeToImage(barcode: Barcode, opts: WriterOptions? = null): Image = barcode.cValue.usePinned {
32+
ZXing_WriteBarcodeToImage(it.get(), opts?.cValue)?.toKObject()
33+
?: throw BarcodeWritingException(ZXing_LastErrorMsg()?.toKStringNullPtrHandledAndFree())
34+
}
35+
36+
}
37+
}
38+
39+
class BarcodeWritingException(message: String?) : Exception("Failed to write barcode: $message")
40+
41+
@ExperimentalWriterApi
42+
@OptIn(ExperimentalForeignApi::class)
43+
open class CreatorOptions(format: BarcodeFormat) {
44+
var format: BarcodeFormat
45+
get() = ZXing_CreatorOptions_getFormat(cValue).parseIntoBarcodeFormat().first()
46+
set(value) = ZXing_CreatorOptions_setFormat(cValue, value.rawValue)
47+
var readerInit: Boolean
48+
get() = ZXing_CreatorOptions_getReaderInit(cValue)
49+
set(value) = ZXing_CreatorOptions_setReaderInit(cValue, value)
50+
var forceSquareDataMatrix: Boolean
51+
get() = ZXing_CreatorOptions_getForceSquareDataMatrix(cValue)
52+
set(value) = ZXing_CreatorOptions_setForceSquareDataMatrix(cValue, value)
53+
var ecLevel: String
54+
get() = ZXing_CreatorOptions_getEcLevel(cValue)?.toKStringNullPtrHandledAndFree() ?: ""
55+
set(value) = ZXing_CreatorOptions_setEcLevel(cValue, value)
56+
57+
val cValue: CValuesRef<ZXing_CreatorOptions>? = ZXing_CreatorOptions_new(format.rawValue)
58+
59+
@Suppress("unused")
60+
@OptIn(ExperimentalNativeApi::class)
61+
private val cleaner = createCleaner(cValue) { ZXing_CreatorOptions_delete(it) }
62+
}
63+
64+
@ExperimentalWriterApi
65+
@OptIn(ExperimentalForeignApi::class)
66+
open class WriterOptions {
67+
var scale: Int
68+
get() = ZXing_WriterOptions_getScale(cValue)
69+
set(value) = ZXing_WriterOptions_setScale(cValue, value)
70+
var sizeHint: Int
71+
get() = ZXing_WriterOptions_getSizeHint(cValue)
72+
set(value) = ZXing_WriterOptions_setSizeHint(cValue, value)
73+
var rotate: Int
74+
get() = ZXing_WriterOptions_getRotate(cValue)
75+
set(value) = ZXing_WriterOptions_setRotate(cValue, value)
76+
var withHRT: Boolean
77+
get() = ZXing_WriterOptions_getWithHRT(cValue)
78+
set(value) = ZXing_WriterOptions_setWithHRT(cValue, value)
79+
var withQuietZones: Boolean
80+
get() = ZXing_WriterOptions_getWithQuietZones(cValue)
81+
set(value) = ZXing_WriterOptions_setWithQuietZones(cValue, value)
82+
83+
val cValue: CValuesRef<ZXing_WriterOptions>? = ZXing_WriterOptions_new()
84+
85+
@Suppress("unused")
86+
@OptIn(ExperimentalNativeApi::class)
87+
private val cleaner = createCleaner(cValue) { ZXing_WriterOptions_delete(it) }
88+
}

wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,54 @@
55

66
package zxingcpp
77

8+
import cnames.structs.ZXing_Image
89
import cnames.structs.ZXing_ImageView
910
import kotlinx.cinterop.*
1011
import zxingcpp.cinterop.*
1112
import kotlin.experimental.ExperimentalNativeApi
13+
import kotlin.native.ref.Cleaner
1214
import kotlin.native.ref.createCleaner
1315

1416
@OptIn(ExperimentalForeignApi::class)
15-
class ImageView(
16-
val data: ByteArray,
17-
val width: Int,
18-
val height: Int,
19-
val format: ImageFormat,
20-
val rowStride: Int = 0,
21-
val pixStride: Int = 0,
22-
) {
23-
private val pinnedData = data.pin()
24-
val cValue: CPointer<ZXing_ImageView>? =
25-
ZXing_ImageView_new_checked(
26-
pinnedData.addressOf(0).reinterpret(),
27-
data.size,
28-
width,
29-
height,
30-
format.cValue,
31-
rowStride,
32-
pixStride
33-
)
17+
abstract class ImageView {
18+
abstract val cValue: CValuesRef<ZXing_ImageView>
3419

3520
@Suppress("unused")
3621
@OptIn(ExperimentalNativeApi::class)
37-
private val cValueCleaner = createCleaner(cValue) { ZXing_ImageView_delete(it) }
22+
protected abstract val cValueCleaner: Cleaner
23+
}
24+
25+
@OptIn(ExperimentalForeignApi::class)
26+
open class ImageViewImplNoCopy(
27+
data: ByteArray,
28+
width: Int,
29+
height: Int,
30+
format: ImageFormat,
31+
rowStride: Int = 0,
32+
pixStride: Int = 0,
33+
) : ImageView() {
34+
private val pinnedData = data.pin()
35+
36+
final override val cValue: CPointer<ZXing_ImageView> = ZXing_ImageView_new_checked(
37+
pinnedData.addressOf(0).reinterpret(),
38+
data.size,
39+
width,
40+
height,
41+
format.cValue,
42+
rowStride,
43+
pixStride
44+
) ?: error("Failed to create ZXing_ImageView")
3845

3946
@Suppress("unused")
4047
@OptIn(ExperimentalNativeApi::class)
4148
private val pinnedDataCleaner = createCleaner(pinnedData) { it.unpin() }
49+
50+
@Suppress("unused")
51+
@OptIn(ExperimentalNativeApi::class)
52+
override val cValueCleaner = createCleaner(cValue) { ZXing_ImageView_delete(it) }
4253
}
4354

55+
4456
@OptIn(ExperimentalForeignApi::class)
4557
enum class ImageFormat(internal val cValue: ZXing_ImageFormat) {
4658
None(ZXing_ImageFormat_None),
@@ -57,3 +69,25 @@ enum class ImageFormat(internal val cValue: ZXing_ImageFormat) {
5769
@OptIn(ExperimentalForeignApi::class)
5870
fun ZXing_ImageFormat.parseIntoImageFormat(): ImageFormat? =
5971
ImageFormat.entries.firstOrNull { it.cValue == this }
72+
73+
@ExperimentalWriterApi
74+
@OptIn(ExperimentalForeignApi::class)
75+
class Image(val cValueImage: CValuesRef<ZXing_Image>) : ImageView() {
76+
@Suppress("unchecked_cast")
77+
override val cValue: CValuesRef<ZXing_ImageView> = cValueImage as CValuesRef<ZXing_ImageView>
78+
79+
val data: ByteArray get() = ZXing_Image_data(cValueImage)?.run {
80+
readBytes(width * height).also { ZXing_free(this) }
81+
}?.takeUnless { it.isEmpty() } ?: throw OutOfMemoryError()
82+
val width: Int get() = ZXing_Image_width(cValueImage)
83+
val height: Int get() = ZXing_Image_height(cValueImage)
84+
val format: ImageFormat get() = ZXing_Image_format(cValueImage).parseIntoImageFormat() ?: error("Unknown format ${ZXing_Image_format(cValueImage)} for image")
85+
86+
@Suppress("unused")
87+
@OptIn(ExperimentalNativeApi::class)
88+
override val cValueCleaner = createCleaner(cValueImage) { ZXing_Image_delete(it) }
89+
}
90+
91+
@ExperimentalWriterApi
92+
@OptIn(ExperimentalForeignApi::class)
93+
fun CValuesRef<ZXing_Image>.toKObject(): Image = Image(this)

wrappers/kn/src/nativeTest/kotlin/Test.kt

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class BarcodeReaderTest {
1919
if (it == '0') 255.toByte() else 0.toByte()
2020
}
2121

22-
val iv = ImageView(data.toByteArray(), data.size, 1, ImageFormat.Lum)
22+
val iv = ImageViewImplNoCopy(data.toByteArray(), data.size, 1, ImageFormat.Lum)
2323
val br = BarcodeReader().apply {
2424
binarizer = Binarizer.BoolCast
2525
}
@@ -38,4 +38,46 @@ class BarcodeReaderTest {
3838
assertEquals(PointI(4, 0), res.position.topLeft)
3939
assertEquals(1, res.lineCount)
4040
}
41+
42+
@Test
43+
@OptIn(ExperimentalNativeApi::class, ExperimentalWriterApi::class)
44+
fun `create write and read barcode with text`() {
45+
val text = "I have the best words."
46+
val barcode = Barcode.fromText(text, BarcodeFormat.DataMatrix)
47+
val image = barcode.toImage()
48+
49+
val res = BarcodeReader.read(image).firstOrNull()
50+
51+
assertNotNull(res)
52+
assert(res.isValid)
53+
assertEquals(BarcodeFormat.DataMatrix, res.format)
54+
assertEquals(text, res.text)
55+
assertContentEquals(text.encodeToByteArray(), res.bytes)
56+
assert(!res.hasECI)
57+
assertEquals(ContentType.Text, res.contentType)
58+
assertEquals(0, res.orientation)
59+
assertEquals(PointI(1, 1), res.position.topLeft)
60+
assertEquals(1, res.lineCount)
61+
}
62+
63+
@Test
64+
@OptIn(ExperimentalNativeApi::class, ExperimentalWriterApi::class)
65+
fun `create write and read barcode with bytes`() {
66+
val text = "I have the best words."
67+
val barcode = Barcode.fromBytes(text.encodeToByteArray(), BarcodeFormat.DataMatrix)
68+
val image = barcode.toImage()
69+
70+
val res = BarcodeReader.read(image).firstOrNull()
71+
72+
assertNotNull(res)
73+
assert(res.isValid)
74+
assertEquals(BarcodeFormat.DataMatrix, res.format)
75+
assertEquals(text, res.text)
76+
assertContentEquals(text.encodeToByteArray(), res.bytes)
77+
assert(res.hasECI)
78+
assertEquals(ContentType.Binary, res.contentType)
79+
assertEquals(0, res.orientation)
80+
assertEquals(PointI(1, 1), res.position.topLeft)
81+
assertEquals(0, res.lineCount)
82+
}
4183
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy