Skip to content

Commit d0c1f34

Browse files
authored
kn: Add support for experimental writer api (#764)
* kn: Allow reading barcode without instantiate a BarcodeReader object Example: ```kotlin val barcode = BarcodeReader.read(iv) ``` ```kotlin val barcode = BarcodeReader.read(iv, opts) ``` * kn: Correct the arrangement of parameter for assert functions * kn: Add support for experimental barcode writer * kn: Add documentation for experimental writer api * kn: Mark Barcode constructors as ExperimentalWriterApi
1 parent 03e9c12 commit d0c1f34

File tree

9 files changed

+235
-13
lines changed

9 files changed

+235
-13
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: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ to your `build.gradle.kts` file in the `dependencies` section of `nativeMain` so
1212

1313
## Use
1414

15+
### Reading
16+
1517
A trivial use case looks like this:
1618

1719
```kotlin
@@ -23,9 +25,9 @@ import zxingcpp.ImageView
2325
val data: ByteArray = ... // the image data
2426
val width: Int = ... // the image width
2527
val height: Int = ... // the image height
26-
val format = ImageFormat.Lum // ImageFormat.Lum assumes grey scale image data
28+
val format: ImageFormat = ImageFormat.Lum // ImageFormat.Lum assumes grey scale image data
2729

28-
val image = ImageView(data, width, height, format)
30+
val image: ImageView = ImageView(data, width, height, format)
2931
val barcodeReader = BarcodeReader().apply {
3032
formats = setOf(BarcodeFormat.EAN13, BarcodeFormat.QRCode)
3133
tryHarder = true
@@ -40,6 +42,40 @@ barcodeReader.read(image).joinToString("\n") { barcode: Barcode ->
4042

4143
Here you have to load your image into memory by yourself and pass the decoded data to the constructor of `ImageView`.
4244

45+
### Writing
46+
47+
A trivial use case looks like this:
48+
49+
```kotlin
50+
import zxingcpp.*
51+
52+
val text: String = "Hello, World!"
53+
val format = BarcodeFormat.QRCode
54+
55+
@OptIn(ExperimentalWriterApi::class)
56+
val cOpts = CreatorOptions(format) // more options, see documentation
57+
58+
@OptIn(ExperimentalWriterApi::class)
59+
val barcode = Barcode(text, cOpts)
60+
// or
61+
@OptIn(ExperimentalWriterApi::class)
62+
val barcode2 = Barcode(text.encodeToByteArray(), format)
63+
64+
@OptIn(ExperimentalWriterApi::class)
65+
val wOpts = WriterOptions().apply {
66+
sizeHint = 400
67+
// more options, see documentation
68+
}
69+
70+
@OptIn(ExperimentalWriterApi::class)
71+
val svg: String = barcode.toSVG(wOpts)
72+
@OptIn(ExperimentalWriterApi::class)
73+
val image: Image = barcode.toImage(wOpts)
74+
```
75+
76+
> Note: The Writer api is still experimental and may change in future versions.
77+
> You will have to opt-in `zxingcpp.ExperimentalWriterApi` to use it.
78+
4379
## Build locally
4480

4581
1. Install JDK, CMake and Android NDK(With `$ANDROID_NDK` correctly configured) and ensure their

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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,29 @@ fun ZXing_Position.toKObject(): Position = Position(
5151
bottomLeft.toKObject(),
5252
)
5353

54+
class BarcodeConstructionException(message: String?) : Exception("Failed to construct barcode: $message")
55+
5456
@OptIn(ExperimentalForeignApi::class)
5557
class Barcode(val cValue: CValuesRef<ZXing_Barcode>) {
58+
59+
@ExperimentalWriterApi
60+
constructor(text: String, opts: CreatorOptions) : this(
61+
ZXing_CreateBarcodeFromText(text, text.length, opts.cValue)
62+
?: throw BarcodeConstructionException(ZXing_LastErrorMsg()?.toKStringNullPtrHandledAndFree())
63+
)
64+
65+
@ExperimentalWriterApi
66+
constructor(text: String, format: BarcodeFormat) : this(text, CreatorOptions(format))
67+
68+
@ExperimentalWriterApi
69+
constructor(bytes: ByteArray, opts: CreatorOptions) : this(
70+
ZXing_CreateBarcodeFromBytes(bytes.refTo(0), bytes.size, opts.cValue)
71+
?: throw BarcodeConstructionException(ZXing_LastErrorMsg()?.toKStringNullPtrHandledAndFree())
72+
)
73+
74+
@ExperimentalWriterApi
75+
constructor(bytes: ByteArray, format: BarcodeFormat) : this(bytes, CreatorOptions(format))
76+
5677
val isValid: Boolean
5778
get() = ZXing_Barcode_isValid(cValue)
5879
val errorMsg: String? by lazy {
@@ -132,6 +153,20 @@ class Barcode(val cValue: CValuesRef<ZXing_Barcode>) {
132153
}
133154
}
134155

156+
@OptIn(ExperimentalForeignApi::class)
157+
@ExperimentalWriterApi
158+
fun Barcode.toSVG(opts: WriterOptions? = null): String = cValue.usePinned {
159+
ZXing_WriteBarcodeToSVG(it.get(), opts?.cValue)?.toKStringNullPtrHandledAndFree()
160+
?: throw BarcodeWritingException(ZXing_LastErrorMsg()?.toKStringNullPtrHandledAndFree())
161+
}
162+
163+
@OptIn(ExperimentalForeignApi::class)
164+
@ExperimentalWriterApi
165+
fun Barcode.toImage(opts: WriterOptions? = null): Image = cValue.usePinned {
166+
ZXing_WriteBarcodeToImage(it.get(), opts?.cValue)?.toKObject()
167+
?: throw BarcodeWritingException(ZXing_LastErrorMsg()?.toKStringNullPtrHandledAndFree())
168+
}
169+
135170
@OptIn(ExperimentalForeignApi::class)
136171
fun CValuesRef<ZXing_Barcode>.toKObject(): Barcode = Barcode(this)
137172

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,14 @@ internal fun CPointer<ByteVar>?.toKStringNullPtrHandledAndFree(): String? = (thi
2424
@OptIn(ExperimentalForeignApi::class)
2525
class BarcodeReader : ReaderOptions() {
2626
@Throws(BarcodeReadingException::class)
27-
fun read(imageView: ImageView): List<Barcode> =
28-
ZXing_ReadBarcodes(imageView.cValue, cValue)?.let { cValues -> cValues.toKObject().also { ZXing_Barcodes_delete(cValues) } }
29-
?: throw BarcodeReadingException(ZXing_LastErrorMsg()?.toKStringNullPtrHandledAndFree())
27+
fun read(imageView: ImageView): List<Barcode> = Companion.read(imageView, this)
28+
29+
companion object {
30+
@Throws(BarcodeReadingException::class)
31+
fun read(imageView: ImageView, opts: ReaderOptions? = null): List<Barcode> =
32+
ZXing_ReadBarcodes(imageView.cValue, opts?.cValue)?.let { cValues -> cValues.toKObject().also { ZXing_Barcodes_delete(cValues) } }
33+
?: throw BarcodeReadingException(ZXing_LastErrorMsg()?.toKStringNullPtrHandledAndFree())
34+
}
3035
}
3136

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

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
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.*
@@ -41,6 +42,7 @@ class ImageView(
4142
private val pinnedDataCleaner = createCleaner(pinnedData) { it.unpin() }
4243
}
4344

45+
4446
@OptIn(ExperimentalForeignApi::class)
4547
enum class ImageFormat(internal val cValue: ZXing_ImageFormat) {
4648
None(ZXing_ImageFormat_None),
@@ -57,3 +59,29 @@ enum class ImageFormat(internal val cValue: ZXing_ImageFormat) {
5759
@OptIn(ExperimentalForeignApi::class)
5860
fun ZXing_ImageFormat.parseIntoImageFormat(): ImageFormat? =
5961
ImageFormat.entries.firstOrNull { it.cValue == this }
62+
63+
@ExperimentalWriterApi
64+
@OptIn(ExperimentalForeignApi::class)
65+
class Image(val cValue: CValuesRef<ZXing_Image>) {
66+
val data: ByteArray
67+
get() = ZXing_Image_data(cValue)?.run {
68+
readBytes(width * height).also { ZXing_free(this) }
69+
}?.takeUnless { it.isEmpty() } ?: throw OutOfMemoryError()
70+
val width: Int get() = ZXing_Image_width(cValue)
71+
val height: Int get() = ZXing_Image_height(cValue)
72+
val format: ImageFormat
73+
get() = ZXing_Image_format(cValue).parseIntoImageFormat() ?: error(
74+
"Unknown format ${ZXing_Image_format(cValue)} for image, " +
75+
"this is an internal error, please report it to the library maintainers."
76+
)
77+
78+
@Suppress("unused")
79+
@OptIn(ExperimentalNativeApi::class)
80+
val cValueCleaner = createCleaner(cValue) { ZXing_Image_delete(it) }
81+
82+
fun toImageView(): ImageView = ImageView(data, width, height, format)
83+
}
84+
85+
@ExperimentalWriterApi
86+
@OptIn(ExperimentalForeignApi::class)
87+
fun CValuesRef<ZXing_Image>.toKObject(): Image = Image(this)

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

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,55 @@ class BarcodeReaderTest {
2929

3030
assertNotNull(res)
3131
assert(res.isValid)
32-
assertEquals(res.format, BarcodeFormat.EAN8)
33-
assertEquals(res.text, expected)
34-
assertContentEquals(res.bytes, expected.encodeToByteArray())
32+
assertEquals(BarcodeFormat.EAN8, res.format)
33+
assertEquals(expected, res.text)
34+
assertContentEquals(expected.encodeToByteArray(), res.bytes)
3535
assert(!res.hasECI)
36-
assertEquals(res.contentType, ContentType.Text)
37-
assertEquals(res.orientation, 0)
38-
assertEquals(res.position.topLeft, PointI(4, 0))
39-
assertEquals(res.lineCount, 1)
36+
assertEquals(ContentType.Text, res.contentType)
37+
assertEquals(0, res.orientation)
38+
assertEquals(PointI(4, 0), res.position.topLeft)
39+
assertEquals(1, res.lineCount)
40+
}
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(text, BarcodeFormat.DataMatrix)
47+
val image = barcode.toImage()
48+
49+
val res = BarcodeReader.read(image.toImageView()).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(0, 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(text.encodeToByteArray(), BarcodeFormat.DataMatrix)
68+
val image = barcode.toImage()
69+
70+
val res = BarcodeReader.read(image.toImageView()).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)
4082
}
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