From beb6b81d32700e4121fe19b6115af420bd32bef9 Mon Sep 17 00:00:00 2001 From: Alexander Zhirkevich Date: Wed, 22 Nov 2023 00:25:51 +0300 Subject: [PATCH 01/13] Update README.md --- README.md | 50 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 58c291d..13d039d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,4 @@ -# QRose - -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.alexzhirkevich/qrose/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.alexzhirkevich/qrose) - +# QRose ![badge-Android](https://img.shields.io/badge/Platform-Android-brightgreen) ![badge-iOS](https://img.shields.io/badge/Platform-iOS-lightgray) @@ -11,7 +8,6 @@ QR code design library for Compose Multiplatform - Screenshot 2023-10-10 at 10 34 05 Why QRose? @@ -22,22 +18,28 @@ Why QRose? - **Multiplatform** - supports all the targets supported by Compose Multiplatform. # Installation + +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.alexzhirkevich/qrose/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.alexzhirkevich/qrose) + ```gradle dependencies { - implementation("io.github.alexzhirkevich:qrose:1.0.0-beta02") + implementation("io.github.alexzhirkevich:qrose:1.0.0-beta3") } ``` # Usage +## Generate + You can create code right in composition using `rememberQrCodePainter`. Or do it outside of Compose scope by instantiating a `QrCodePainter` class. There are some overloads of `rememberQrCodePainter` including DSL constructor: + ```kotlin -val logoPainter = painterResource("logo.png") +val logoPainter : Painter = painterResource("logo.png") -val qrcodePainter = rememberQrCodePainter("https://example.com") { +val qrcodePainter : Painter = rememberQrCodePainter("https://example.com") { logo { painter = logoPainter padding = QrLogoPadding.Natural(.1f) @@ -74,7 +76,7 @@ val qrcodePainter = rememberQrCodePainter( ) ``` -# Customization +## Customize You can create your own shapes for each QR code part, for example: @@ -86,6 +88,36 @@ class MyCircleBallShape : QrBallShape { } } ``` + > **Note** >A path here uses [`PathFillType.EvenOdd`](https://developer.android.com/reference/kotlin/androidx/compose/ui/graphics/PathFillType#EvenOdd()) that cannot be changed. +## Data types + +QR codes can hold various payload types: Text, Wi-Fi, E-mail, vCard, etc. + +`QrData` object can be used to perform such encodings, for example: + +```kotlin +val wifiData : String = QrData.wifi(ssid = "My Network", psk = "12345678") + +val wifiCode = rememberQrCodePainter(wifiData) +``` + +## Export + +QR codes can be exported to `PNG`, `JPEG` and `WEBP` formats using `toByteArray` function: + +```kotlin + +val painter : Painter = QrCodePainter( + data = "https://example.com", + options = QrOptions { + colors { + //... + } + } +) + +val bytes : ByteArray = painter.toByteArray(1024, 1024, ImageFormat.PNG) +``` From fcc757dde6f74431bba895e0a0378ec80a066ac6 Mon Sep 17 00:00:00 2001 From: Alexander Zhirkevich Date: Mon, 12 Feb 2024 11:55:59 +0300 Subject: [PATCH 02/13] Update README.md --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 13d039d..e2357f6 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,20 @@ dependencies { # Usage -## Generate +## Basic You can create code right in composition using `rememberQrCodePainter`. Or do it outside of Compose scope by instantiating a `QrCodePainter` class. +```kotlin +Image( + painter = rememberQrCodePainter("https://example.com") + contentDescription = "QR code" +) +``` + +## Design + There are some overloads of `rememberQrCodePainter` including DSL constructor: ```kotlin From 0b4e72cb04809a9dd8f9bb037ccbac15dd3e5e3f Mon Sep 17 00:00:00 2001 From: Alexander Zhirkevich Date: Mon, 12 Feb 2024 11:57:44 +0300 Subject: [PATCH 03/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e2357f6..cd26963 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Or do it outside of Compose scope by instantiating a `QrCodePainter` class. ```kotlin Image( - painter = rememberQrCodePainter("https://example.com") + painter = rememberQrCodePainter("https://example.com"), contentDescription = "QR code" ) ``` From 63cca29990d025a1ff706f849f2b190feb7d1cc4 Mon Sep 17 00:00:00 2001 From: Zhirkevich Alexander Y Date: Wed, 28 Feb 2024 17:19:35 +0300 Subject: [PATCH 04/13] compose 1.6.0, wasmJs, new barcode types --- build.gradle.kts | 95 +- example/desktopApp/build.gradle.kts | 7 +- .../drawable}/jc.png | Bin .../drawable}/jcbg.png | Bin .../io/github/alexzhirkevich/shared/App.kt | 109 +- gradle.properties | 5 +- gradle/libs.versions.toml | 6 +- kotlin-js-store/yarn.lock | 2804 ----------------- qrose/build.gradle.kts | 247 +- .../alexzhirkevich/qrose/CachedPainter.kt | 135 + .../github/alexzhirkevich/qrose/DrawCache.kt | 76 - .../alexzhirkevich/qrose/QrCodePainter.kt | 49 +- .../alexzhirkevich/qrose/oned/Code128.kt | 647 ++++ .../qrose/oned/Code128Painter.kt | 75 + .../alexzhirkevich/qrose/oned/Code39.kt | 121 + .../qrose/oned/Code39Painter.kt | 57 + .../alexzhirkevich/qrose/oned/Code93.kt | 100 + .../qrose/oned/Code93Painter.kt | 59 + .../alexzhirkevich/qrose/oned/CodeEAN13.kt | 56 + .../alexzhirkevich/qrose/oned/CodeEAN8.kt | 163 + .../alexzhirkevich/qrose/oned/EAN13Painter.kt | 55 + .../alexzhirkevich/qrose/oned/EAN8Painter.kt | 55 + .../qrose/oned/SingleDimensionBarcode.kt | 50 + .../alexzhirkevich/qrose/options/QrBrush.kt | 6 - .../alexzhirkevich/qrose/toByteArray.kt | 0 settings.gradle.kts | 1 + 26 files changed, 1866 insertions(+), 3112 deletions(-) rename example/shared/src/commonMain/{resources => composeResources/drawable}/jc.png (100%) rename example/shared/src/commonMain/{resources => composeResources/drawable}/jcbg.png (100%) delete mode 100644 kotlin-js-store/yarn.lock create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt rename qrose/src/{nonAndroidMain => skikoMain}/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt (100%) diff --git a/build.gradle.kts b/build.gradle.kts index 7f35f69..a670154 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,8 @@ +@file: Suppress("DSL_SCOPE_VIOLATION") plugins { - alias(libs.plugins.kotlin.multiplatform).apply(false) + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.android.application).apply(false) - alias(libs.plugins.android.library).apply(false) alias(libs.plugins.compose).apply(false) alias(libs.plugins.dokka).apply(false) } @@ -9,3 +10,93 @@ plugins { //tasks.register("clean", Delete::class) { // delete(rootProject.buildDir) //} + +val _jvmTarget = findProperty("jvmTarget") as String + +val kmpPlugin = libs.plugins.kotlin.multiplatform +val androidLibPlugin = libs.plugins.android.library +val composePlugin = libs.plugins.compose + +val ver = libs.versions.qrose.get() + +subprojects { + if (!name.startsWith("qrose")) + return@subprojects + + this.group = "io.github.alexzhirkevich" + this.version = ver + + plugins.apply(kmpPlugin.get().pluginId) + plugins.apply(androidLibPlugin.get().pluginId) + plugins.apply(composePlugin.get().pluginId) + + kotlin { + applyDefaultHierarchyTemplate() + + androidTarget { + publishLibraryVariants("release") + compilations.all { + kotlinOptions { + jvmTarget = _jvmTarget + } + } + } + iosArm64() + iosX64() + iosSimulatorArm64() + js(IR) { + browser() + } + wasmJs(){ + browser() + } + jvm("desktop") { + compilations.all { + kotlinOptions { + jvmTarget = _jvmTarget + } + } + } + + macosArm64() + macosX64() + + sourceSets { + + val desktopMain by getting + + val darwinMain by creating { + dependsOn(commonMain.get()) + macosMain.get().dependsOn(this) + iosMain.get().dependsOn(this) + } + + val wasmJsMain by getting + + val skikoMain by creating { + dependsOn(commonMain.get()) + jsMain.get().dependsOn(this) + wasmJsMain.dependsOn(this) + desktopMain.dependsOn(this) + darwinMain.dependsOn(this) + } + } + } + + android { + namespace = "io.github.alexzhirkevich.${name.replace('-','.')}" + compileSdk = 34 + defaultConfig { + minSdk = 21 + } + + compileOptions { + sourceCompatibility = JavaVersion.toVersion(_jvmTarget) + targetCompatibility = JavaVersion.toVersion(_jvmTarget) + } + } +} +android { + namespace = "io.github.alexzhirkevich.qrose" + compileSdk = findProperty("android.compileSdk").toString().toInt() +} \ No newline at end of file diff --git a/example/desktopApp/build.gradle.kts b/example/desktopApp/build.gradle.kts index 749c79b..1d3723d 100644 --- a/example/desktopApp/build.gradle.kts +++ b/example/desktopApp/build.gradle.kts @@ -7,10 +7,7 @@ plugins { kotlin { jvm { - withJava() -// compilations.all { -// kotlinOptions.jvmTarget = "11" -// } + } sourceSets { val jvmMain by getting { @@ -18,7 +15,7 @@ kotlin { dependencies { implementation(project(":example:shared")) - implementation(compose.desktop.macos_arm64) + implementation(compose.desktop.currentOs) api(compose.runtime) api(compose.foundation) api(compose.material) diff --git a/example/shared/src/commonMain/resources/jc.png b/example/shared/src/commonMain/composeResources/drawable/jc.png similarity index 100% rename from example/shared/src/commonMain/resources/jc.png rename to example/shared/src/commonMain/composeResources/drawable/jc.png diff --git a/example/shared/src/commonMain/resources/jcbg.png b/example/shared/src/commonMain/composeResources/drawable/jcbg.png similarity index 100% rename from example/shared/src/commonMain/resources/jcbg.png rename to example/shared/src/commonMain/composeResources/drawable/jcbg.png diff --git a/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt b/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt index 5abd2be..c3e540c 100644 --- a/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt +++ b/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt @@ -3,13 +3,13 @@ package io.github.alexzhirkevich.shared import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TextField import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -21,10 +21,13 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.dp -import io.github.alexzhirkevich.qrose.ImageFormat -import io.github.alexzhirkevich.qrose.QrCodePainter import io.github.alexzhirkevich.qrose.QrData import io.github.alexzhirkevich.qrose.email +import io.github.alexzhirkevich.qrose.oned.rememberCode128Painter +import io.github.alexzhirkevich.qrose.oned.rememberCode39Painter +import io.github.alexzhirkevich.qrose.oned.rememberCode93Painter +import io.github.alexzhirkevich.qrose.oned.rememberEAN13Painter +import io.github.alexzhirkevich.qrose.oned.rememberEAN8Painter import io.github.alexzhirkevich.qrose.options.DelicateQRoseApi import io.github.alexzhirkevich.qrose.options.QrBallShape import io.github.alexzhirkevich.qrose.options.QrBrush @@ -33,7 +36,6 @@ import io.github.alexzhirkevich.qrose.options.QrErrorCorrectionLevel import io.github.alexzhirkevich.qrose.options.QrFrameShape import io.github.alexzhirkevich.qrose.options.QrLogoPadding import io.github.alexzhirkevich.qrose.options.QrLogoShape -import io.github.alexzhirkevich.qrose.options.QrOptions import io.github.alexzhirkevich.qrose.options.QrPixelShape import io.github.alexzhirkevich.qrose.options.brush import io.github.alexzhirkevich.qrose.options.circle @@ -42,21 +44,106 @@ import io.github.alexzhirkevich.qrose.options.image import io.github.alexzhirkevich.qrose.options.roundCorners import io.github.alexzhirkevich.qrose.options.solid import io.github.alexzhirkevich.qrose.rememberQrCodePainter -import io.github.alexzhirkevich.qrose.toByteArray -import io.github.alexzhirkevich.qrose.wifi import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.painterResource +import qrose.example.shared.generated.resources.Res +import qrose.example.shared.generated.resources.jc +import qrose.example.shared.generated.resources.jcbg @Composable fun App() { + QrCode() +} + +@Composable +fun CodeEAN13() { + val painter = rememberEAN13Painter( + data = "9780201379624" + ) + + Image( + painter = painter, + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(.3f) + ) +} + + +@Composable +fun CodeEAN8(){ + val painter = rememberEAN8Painter( + data = "1234567" + ) + + Image( + painter = painter, + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(.3f) + ) +} + +@Composable +fun Code39(){ + val painter = rememberCode39Painter( + data = "WIKIPEDIA" + ) + Image( + painter = painter, + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(.3f) + ) +} + +@Composable +fun Code93(){ + val painter = rememberCode93Painter( + data = "TEST123" + ) + + Image( + painter = painter, + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(.3f) + .padding(30.dp) + ) +} + + +@Composable +fun Code128(){ + val painter = rememberCode128Painter( + data = "test", + brush = Brush.horizontalGradient(0f to Color.Red, 1f to Color.Blue) + ) + + Image( + painter = painter, + contentDescription = null, + modifier = Modifier + .size(350.dp) + .padding(10.dp) + ) +} + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun QrCode(){ var text by remember { mutableStateOf("https://github.com/alexzhirkevich/qrose") } - val bg = painterResource("jcbg.png") + val bg = painterResource(Res.drawable.jcbg) - val logo = painterResource("jc.png") + val logo = painterResource(Res.drawable.jc) val painter = rememberQrCodePainter(text) { logo { @@ -113,8 +200,8 @@ fun App() { @OptIn(DelicateQRoseApi::class) @Composable fun JetpackCompose(data : String) : Painter { - val bg = painterResource("jcbg.png") - val logo = painterResource("jc.png") + val bg = painterResource(Res.drawable.jcbg) + val logo = painterResource(Res.drawable.jc) return rememberQrCodePainter(data) { fourEyed = true diff --git a/gradle.properties b/gradle.properties index 56c1da7..123b64a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,7 @@ kotlin.mpp.androidSourceSetLayoutVersion=2 org.jetbrains.compose.experimental.macos.enabled=true org.jetbrains.compose.experimental.uikit.enabled=true -org.jetbrains.compose.experimental.jscanvas.enabled=true \ No newline at end of file +org.jetbrains.compose.experimental.jscanvas.enabled=true +org.jetbrains.compose.experimental.wasm.enabled=true + +android.compileSdk=34 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 74192a6..ff0fd1a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] -kotlin="1.9.10" +kotlin="1.9.22" dokka="1.9.0" -compose="1.5.10" +compose="1.6.0" androidGradlePlugin = "8.1.1" -qrose = "1.0.0-beta02" +qrose = "1.0.0" [plugins] compose = { id = "org.jetbrains.compose", version.ref = "compose" } diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock deleted file mode 100644 index ac83132..0000000 --- a/kotlin-js-store/yarn.lock +++ /dev/null @@ -1,2804 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@discoveryjs/json-ext@^0.5.0": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" - integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== - -"@jridgewell/gen-mapping@^0.3.0": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== - -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/source-map@^0.3.3": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" - integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.19" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" - integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@js-joda/core@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" - integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== - -"@leichtgewicht/ip-codec@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" - integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== - -"@socket.io/component-emitter@~3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" - integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== - -"@types/body-parser@*": - version "1.19.3" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.3.tgz#fb558014374f7d9e56c8f34bab2042a3a07d25cd" - integrity sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/bonjour@^3.5.9": - version "3.5.11" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.11.tgz#fbaa46a1529ea5c5e46cde36e4be6a880db55b84" - integrity sha512-isGhjmBtLIxdHBDl2xGwUzEM8AOyOvWsADWq7rqirdi/ZQoHnLWErHvsThcEzTX8juDRiZtzp2Qkv5bgNh6mAg== - dependencies: - "@types/node" "*" - -"@types/connect-history-api-fallback@^1.3.5": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.1.tgz#6e5e3602d93bda975cebc3449e1a318340af9e20" - integrity sha512-iaQslNbARe8fctL5Lk+DsmgWOM83lM+7FzP0eQUJs1jd3kBE8NWqBTIT2S8SqQOJjxvt2eyIjpOuYeRXq2AdMw== - dependencies: - "@types/express-serve-static-core" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.36" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.36.tgz#e511558c15a39cb29bd5357eebb57bd1459cd1ab" - integrity sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w== - dependencies: - "@types/node" "*" - -"@types/cookie@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" - integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== - -"@types/cors@^2.8.12": - version "2.8.14" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.14.tgz#94eeb1c95eda6a8ab54870a3bf88854512f43a92" - integrity sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ== - dependencies: - "@types/node" "*" - -"@types/eslint-scope@^3.7.3": - version "3.7.5" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.5.tgz#e28b09dbb1d9d35fdfa8a884225f00440dfc5a3e" - integrity sha512-JNvhIEyxVW6EoMIFIvj93ZOywYFatlpu9deeH6eSx6PE3WHYvHaQtmHmQeNw7aA81bYGBPPQqdtBm6b1SsQMmA== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.44.3" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.3.tgz#96614fae4875ea6328f56de38666f582d911d962" - integrity sha512-iM/WfkwAhwmPff3wZuPLYiHX18HI24jU8k1ZSH7P8FHwxTjZ2P6CoX2wnF43oprR+YXJM6UUxATkNvyv/JHd+g== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*", "@types/estree@^1.0.0": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.2.tgz#ff02bc3dc8317cd668dfec247b750ba1f1d62453" - integrity sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA== - -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": - version "4.17.37" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.37.tgz#7e4b7b59da9142138a2aaa7621f5abedce8c7320" - integrity sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@*", "@types/express@^4.17.13": - version "4.17.18" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.18.tgz#efabf5c4495c1880df1bdffee604b143b29c4a95" - integrity sha512-Sxv8BSLLgsBYmcnGdGjjEjqET2U+AKAdCRODmMiq02FgjwuV75Ut85DRpvFjyw/Mk0vgUOliGRU0UUmuuZHByQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/http-errors@*": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.2.tgz#a86e00bbde8950364f8e7846687259ffcd96e8c2" - integrity sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg== - -"@types/http-proxy@^1.17.8": - version "1.17.12" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.12.tgz#86e849e9eeae0362548803c37a0a1afc616bd96b" - integrity sha512-kQtujO08dVtQ2wXAuSFfk9ASy3sug4+ogFR8Kd8UgP8PEuc1/G/8yjYRmp//PcDNJEUKOza/MrQu15bouEUCiw== - dependencies: - "@types/node" "*" - -"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.13" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" - integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== - -"@types/mime@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.2.tgz#c1ae807f13d308ee7511a5b81c74f327028e66e8" - integrity sha512-Wj+fqpTLtTbG7c0tH47dkahefpLKEbB+xAZuLq7b4/IDHPl/n6VoXcyUQ2bypFlbSwvCr0y+bD4euTTqTJsPxQ== - -"@types/mime@^1": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.3.tgz#bbe64987e0eb05de150c305005055c7ad784a9ce" - integrity sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg== - -"@types/node@*", "@types/node@>=10.0.0": - version "20.8.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.4.tgz#0e9ebb2ff29d5c3302fc84477d066fa7c6b441aa" - integrity sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A== - dependencies: - undici-types "~5.25.1" - -"@types/qs@*": - version "6.9.8" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.8.tgz#f2a7de3c107b89b441e071d5472e6b726b4adf45" - integrity sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg== - -"@types/range-parser@*": - version "1.2.5" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.5.tgz#38bd1733ae299620771bd414837ade2e57757498" - integrity sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA== - -"@types/retry@0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" - integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== - -"@types/send@*": - version "0.17.2" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.2.tgz#af78a4495e3c2b79bfbdac3955fdd50e03cc98f2" - integrity sha512-aAG6yRf6r0wQ29bkS+x97BIs64ZLxeE/ARwyS6wrldMm3C1MdKwCcnnEwMC1slI8wuxJOpiUH9MioC0A0i+GJw== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-index@^1.9.1": - version "1.9.2" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.2.tgz#cb26e775678a8526b73a5d980a147518740aaecd" - integrity sha512-asaEIoc6J+DbBKXtO7p2shWUpKacZOoMBEGBgPG91P8xhO53ohzHWGCs4ScZo5pQMf5ukQzVT9fhX1WzpHihig== - dependencies: - "@types/express" "*" - -"@types/serve-static@*", "@types/serve-static@^1.13.10": - version "1.15.3" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.3.tgz#2cfacfd1fd4520bbc3e292cca432d5e8e2e3ee61" - integrity sha512-yVRvFsEMrv7s0lGhzrggJjNOSmZCdgCjw9xWrPr/kNNLp6FaDfMC1KaYl3TSJ0c58bECwNBMoQrZJ8hA8E1eFg== - dependencies: - "@types/http-errors" "*" - "@types/mime" "*" - "@types/node" "*" - -"@types/sockjs@^0.3.33": - version "0.3.34" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.34.tgz#43e10e549b36d2ba2589278f00f81b5d7ccda167" - integrity sha512-R+n7qBFnm/6jinlteC9DBL5dGiDGjWAvjo4viUanpnc/dG1y7uDoacXPIQ/PQEg1fI912SMHIa014ZjRpvDw4g== - dependencies: - "@types/node" "*" - -"@types/ws@^8.5.1": - version "8.5.6" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.6.tgz#e9ad51f0ab79b9110c50916c9fcbddc36d373065" - integrity sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg== - dependencies: - "@types/node" "*" - -"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" - integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - -"@webassemblyjs/floating-point-hex-parser@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" - integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== - -"@webassemblyjs/helper-api-error@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" - integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== - -"@webassemblyjs/helper-buffer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093" - integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== - -"@webassemblyjs/helper-numbers@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" - integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.6" - "@webassemblyjs/helper-api-error" "1.11.6" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" - integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== - -"@webassemblyjs/helper-wasm-section@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577" - integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - -"@webassemblyjs/ieee754@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" - integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" - integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" - integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== - -"@webassemblyjs/wasm-edit@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab" - integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/helper-wasm-section" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-opt" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" - "@webassemblyjs/wast-printer" "1.11.6" - -"@webassemblyjs/wasm-gen@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268" - integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/ieee754" "1.11.6" - "@webassemblyjs/leb128" "1.11.6" - "@webassemblyjs/utf8" "1.11.6" - -"@webassemblyjs/wasm-opt@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2" - integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" - -"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1" - integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-api-error" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/ieee754" "1.11.6" - "@webassemblyjs/leb128" "1.11.6" - "@webassemblyjs/utf8" "1.11.6" - -"@webassemblyjs/wast-printer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20" - integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@xtuc/long" "4.2.2" - -"@webpack-cli/configtest@^2.1.0": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.1.tgz#3b2f852e91dac6e3b85fb2a314fb8bef46d94646" - integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw== - -"@webpack-cli/info@^2.0.1": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.2.tgz#cc3fbf22efeb88ff62310cf885c5b09f44ae0fdd" - integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A== - -"@webpack-cli/serve@^2.0.3": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e" - integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ== - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -abab@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" - integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== - -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-import-assertions@^1.7.6: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== - -acorn@^8.7.1, acorn@^8.8.2: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== - -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv-keywords@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" - integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== - dependencies: - fast-deep-equal "^3.1.3" - -ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.0.0, ajv@^8.9.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - -ansi-html-community@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -array-flatten@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64id@2.0.0, base64id@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" - integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== - -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - -body-parser@^1.19.0: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - -bonjour-service@^1.0.11: - version "1.1.1" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.1.1.tgz#960948fa0e0153f5d26743ab15baf8e33752c135" - integrity sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg== - dependencies: - array-flatten "^2.1.2" - dns-equal "^1.0.0" - fast-deep-equal "^3.1.3" - multicast-dns "^7.2.5" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - -browserslist@^4.14.5: - version "4.22.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" - integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ== - dependencies: - caniuse-lite "^1.0.30001541" - electron-to-chromium "^1.4.535" - node-releases "^2.0.13" - update-browserslist-db "^1.0.13" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -camelcase@^6.0.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001541: - version "1.0.30001547" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz#d4f92efc488aab3c7f92c738d3977c2a3180472b" - integrity sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA== - -chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chokidar@3.5.3, chokidar@^3.5.1, chokidar@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colorette@^2.0.10, colorette@^2.0.14: - version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -commander@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -connect-history-api-fallback@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" - integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== - -connect@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" - integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== - dependencies: - debug "2.6.9" - finalhandler "1.1.2" - parseurl "~1.3.3" - utils-merge "1.0.1" - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4, content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - -cookie@~0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cors@~2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -custom-event@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" - integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg== - -date-format@^4.0.14: - version "4.0.14" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400" - integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg== - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4.3.4, debug@^4.1.0, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -decamelize@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" - integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== - -default-gateway@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" - integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== - dependencies: - execa "^5.0.0" - -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - -di@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" - integrity sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA== - -diff@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== - -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== - -dns-packet@^5.2.2: - version "5.6.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" - integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== - dependencies: - "@leichtgewicht/ip-codec" "^2.0.1" - -dom-serialize@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" - integrity sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ== - dependencies: - custom-event "~1.0.0" - ent "~2.2.0" - extend "^3.0.0" - void-elements "^2.0.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.4.535: - version "1.4.548" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.548.tgz#e695d769e0e801fa6d438b63f6bc9b80372000d6" - integrity sha512-R77KD6mXv37DOyKLN/eW1rGS61N6yHOfapNSX9w+y9DdPG83l9Gkuv7qkCFZ4Ta4JPhrjgQfYbv4Y3TnM1Hi2Q== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -engine.io-parser@~5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.1.tgz#9f213c77512ff1a6cc0c7a86108a7ffceb16fcfb" - integrity sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ== - -engine.io@~6.5.2: - version "6.5.3" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.3.tgz#80b0692912cef3a417e1b7433301d6397bf0374b" - integrity sha512-IML/R4eG/pUS5w7OfcDE0jKrljWS9nwnEfsxWCIJF5eO6AHo6+Hlv+lQbdlAYsiJPHzUthLm1RUjnBzWOs45cw== - dependencies: - "@types/cookie" "^0.4.1" - "@types/cors" "^2.8.12" - "@types/node" ">=10.0.0" - accepts "~1.3.4" - base64id "2.0.0" - cookie "~0.4.1" - cors "~2.8.5" - debug "~4.3.1" - engine.io-parser "~5.2.1" - ws "~8.11.0" - -enhanced-resolve@^5.13.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -ent@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - integrity sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA== - -envinfo@^7.7.3: - version "7.10.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.10.0.tgz#55146e3909cc5fe63c22da63fb15b05aeac35b13" - integrity sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw== - -es-module-lexer@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.1.tgz#c1b0dd5ada807a3b3155315911f364dc4e909db1" - integrity sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q== - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -express@^4.17.3: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.1" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fastest-levenshtein@^1.0.12: - version "1.0.16" - resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" - integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== - -faye-websocket@^0.11.3: - version "0.11.4" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" - integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== - dependencies: - websocket-driver ">=0.5.1" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -find-up@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -flatted@^3.2.7: - version "3.2.9" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" - integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== - -follow-redirects@^1.0.0: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== - -format-util@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" - integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-monkey@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.5.tgz#fe450175f0db0d7ea758102e1d84096acb925788" - integrity sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" - integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-proto "^1.0.1" - has-symbols "^1.0.3" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -glob@7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.1.3, glob@^7.1.7: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== - -has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6" - integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== - -he@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -html-entities@^2.3.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.4.0.tgz#edd0cee70402584c8c76cc2c0556db09d1f45061" - integrity sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ== - -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== - -http-proxy-middleware@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" - integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== - dependencies: - "@types/http-proxy" "^1.17.8" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -iconv-lite@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - -interpret@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" - integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -ipaddr.js@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f" - integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-core-module@^2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" - integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== - dependencies: - has "^1.0.3" - -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-plain-obj@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isbinaryfile@^4.0.8: - version "4.0.10" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" - integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -js-yaml@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -json-parse-even-better-errors@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== - optionalDependencies: - graceful-fs "^4.1.6" - -karma-chrome-launcher@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz#eb9c95024f2d6dfbb3748d3415ac9b381906b9a9" - integrity sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q== - dependencies: - which "^1.2.1" - -karma-mocha@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/karma-mocha/-/karma-mocha-2.0.1.tgz#4b0254a18dfee71bdbe6188d9a6861bf86b0cd7d" - integrity sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ== - dependencies: - minimist "^1.2.3" - -karma-sourcemap-loader@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.4.0.tgz#b01d73f8f688f533bcc8f5d273d43458e13b5488" - integrity sha512-xCRL3/pmhAYF3I6qOrcn0uhbQevitc2DERMPH82FMnG+4WReoGcGFZb1pURf2a5apyrOHRdvD+O6K7NljqKHyA== - dependencies: - graceful-fs "^4.2.10" - -karma-webpack@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-5.0.0.tgz#2a2c7b80163fe7ffd1010f83f5507f95ef39f840" - integrity sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA== - dependencies: - glob "^7.1.3" - minimatch "^3.0.4" - webpack-merge "^4.1.5" - -karma@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.2.tgz#a983f874cee6f35990c4b2dcc3d274653714de8e" - integrity sha512-C6SU/53LB31BEgRg+omznBEMY4SjHU3ricV6zBcAe1EeILKkeScr+fZXtaI5WyDbkVowJxxAI6h73NcFPmXolQ== - dependencies: - "@colors/colors" "1.5.0" - body-parser "^1.19.0" - braces "^3.0.2" - chokidar "^3.5.1" - connect "^3.7.0" - di "^0.0.1" - dom-serialize "^2.2.1" - glob "^7.1.7" - graceful-fs "^4.2.6" - http-proxy "^1.18.1" - isbinaryfile "^4.0.8" - lodash "^4.17.21" - log4js "^6.4.1" - mime "^2.5.2" - minimatch "^3.0.4" - mkdirp "^0.5.5" - qjobs "^1.2.0" - range-parser "^1.2.1" - rimraf "^3.0.2" - socket.io "^4.4.1" - source-map "^0.6.1" - tmp "^0.2.1" - ua-parser-js "^0.7.30" - yargs "^16.1.1" - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -launch-editor@^2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.1.tgz#f259c9ef95cbc9425620bbbd14b468fcdb4ffe3c" - integrity sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw== - dependencies: - picocolors "^1.0.0" - shell-quote "^1.8.1" - -loader-runner@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" - integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash@^4.17.15, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -log4js@^6.4.1: - version "6.9.1" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.9.1.tgz#aba5a3ff4e7872ae34f8b4c533706753709e38b6" - integrity sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g== - dependencies: - date-format "^4.0.14" - debug "^4.3.4" - flatted "^3.2.7" - rfdc "^1.3.0" - streamroller "^3.1.5" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memfs@^3.4.3: - version "3.6.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" - integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== - dependencies: - fs-monkey "^1.0.4" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^4.0.2: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@^2.5.2: - version "2.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimalistic-assert@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimatch@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" - integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^3.0.4, minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.3, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -mkdirp@^0.5.5: - version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== - dependencies: - minimist "^1.2.6" - -mocha@10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8" - integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg== - dependencies: - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.3" - debug "4.3.4" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "7.2.0" - he "1.2.0" - js-yaml "4.1.0" - log-symbols "4.1.0" - minimatch "5.0.1" - ms "2.1.3" - nanoid "3.3.3" - serialize-javascript "6.0.0" - strip-json-comments "3.1.1" - supports-color "8.1.1" - workerpool "6.2.1" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multicast-dns@^7.2.5: - version "7.2.5" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" - integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== - dependencies: - dns-packet "^5.2.2" - thunky "^1.0.2" - -nanoid@3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" - integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -node-forge@^1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== - -node-releases@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -object-assign@^4: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.9.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" - integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== - -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -open@^8.0.9: - version "8.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" - integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-retry@^4.5.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" - integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== - dependencies: - "@types/retry" "0.12.0" - retry "^0.13.1" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -punycode@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== - -qjobs@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" - integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== - -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -readable-stream@^2.0.1: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.6: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -rechoir@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" - integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== - dependencies: - resolve "^1.20.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve@^1.20.0: - version "1.22.6" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" - integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - -rfdc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== - -rimraf@^3.0.0, rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -schema-utils@^3.1.1, schema-utils@^3.1.2: - version "3.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" - integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.9.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.1.0" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== - -selfsigned@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61" - integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ== - dependencies: - node-forge "^1" - -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serialize-javascript@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== - dependencies: - randombytes "^2.1.0" - -serialize-javascript@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" - integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== - dependencies: - randombytes "^2.1.0" - -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== - dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shell-quote@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" - integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -signal-exit@^3.0.3: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -socket.io-adapter@~2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12" - integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA== - dependencies: - ws "~8.11.0" - -socket.io-parser@~4.2.4: - version "4.2.4" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" - integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== - dependencies: - "@socket.io/component-emitter" "~3.1.0" - debug "~4.3.1" - -socket.io@^4.4.1: - version "4.7.2" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.2.tgz#22557d76c3f3ca48f82e73d68b7add36a22df002" - integrity sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw== - dependencies: - accepts "~1.3.4" - base64id "~2.0.0" - cors "~2.8.5" - debug "~4.3.2" - engine.io "~6.5.2" - socket.io-adapter "~2.5.2" - socket.io-parser "~4.2.4" - -sockjs@^0.3.24: - version "0.3.24" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" - integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== - dependencies: - faye-websocket "^0.11.3" - uuid "^8.3.2" - websocket-driver "^0.7.4" - -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== - -source-map-loader@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-4.0.1.tgz#72f00d05f5d1f90f80974eda781cbd7107c125f2" - integrity sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA== - dependencies: - abab "^2.0.6" - iconv-lite "^0.6.3" - source-map-js "^1.0.2" - -source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -"statuses@>= 1.4.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - -streamroller@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.5.tgz#1263182329a45def1ffaef58d31b15d13d2ee7ff" - integrity sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw== - dependencies: - date-format "^4.0.14" - debug "^4.3.4" - fs-extra "^8.1.0" - -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -supports-color@8.1.1, supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -tapable@^2.1.1, tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -terser-webpack-plugin@^5.3.7: - version "5.3.9" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" - integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.17" - jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.1" - terser "^5.16.8" - -terser@^5.16.8: - version "5.21.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.21.0.tgz#d2b27e92b5e56650bc83b6defa00a110f0b124b2" - integrity sha512-WtnFKrxu9kaoXuiZFSGrcAvvBqAdmKx0SFNmVNYdJamMu9yyN3I/QF0FbH4QcqJQ+y1CJnzxGIKH0cSj+FGYRw== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.8.2" - commander "^2.20.0" - source-map-support "~0.5.20" - -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - -tmp@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typescript@5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" - integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== - -ua-parser-js@^0.7.30: - version "0.7.36" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.36.tgz#382c5d6fc09141b6541be2cae446ecfcec284db2" - integrity sha512-CPPLoCts2p7D8VbybttE3P2ylv0OBZEAy7a12DsulIEcAiMtWJy+PBgMXgWDI80D5UwqE8oQPHYnk13tm38M2Q== - -undici-types@~5.25.1: - version "5.25.3" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3" - integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA== - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -void-elements@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" - integrity sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung== - -watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - -webpack-cli@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.0.tgz#abc4b1f44b50250f2632d8b8b536cfe2f6257891" - integrity sha512-a7KRJnCxejFoDpYTOwzm5o21ZXMaNqtRlvS183XzGDUPRdVEzJNImcQokqYZ8BNTnk9DkKiuWxw75+DCCoZ26w== - dependencies: - "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^2.1.0" - "@webpack-cli/info" "^2.0.1" - "@webpack-cli/serve" "^2.0.3" - colorette "^2.0.14" - commander "^10.0.1" - cross-spawn "^7.0.3" - envinfo "^7.7.3" - fastest-levenshtein "^1.0.12" - import-local "^3.0.2" - interpret "^3.1.1" - rechoir "^0.8.0" - webpack-merge "^5.7.3" - -webpack-dev-middleware@^5.3.1: - version "5.3.3" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" - integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== - dependencies: - colorette "^2.0.10" - memfs "^3.4.3" - mime-types "^2.1.31" - range-parser "^1.2.1" - schema-utils "^4.0.0" - -webpack-dev-server@4.15.0: - version "4.15.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.0.tgz#87ba9006eca53c551607ea0d663f4ae88be7af21" - integrity sha512-HmNB5QeSl1KpulTBQ8UT4FPrByYyaLxpJoQ0+s7EvUrMc16m0ZS1sgb1XGqzmgCPk0c9y+aaXxn11tbLzuM7NQ== - dependencies: - "@types/bonjour" "^3.5.9" - "@types/connect-history-api-fallback" "^1.3.5" - "@types/express" "^4.17.13" - "@types/serve-index" "^1.9.1" - "@types/serve-static" "^1.13.10" - "@types/sockjs" "^0.3.33" - "@types/ws" "^8.5.1" - ansi-html-community "^0.0.8" - bonjour-service "^1.0.11" - chokidar "^3.5.3" - colorette "^2.0.10" - compression "^1.7.4" - connect-history-api-fallback "^2.0.0" - default-gateway "^6.0.3" - express "^4.17.3" - graceful-fs "^4.2.6" - html-entities "^2.3.2" - http-proxy-middleware "^2.0.3" - ipaddr.js "^2.0.1" - launch-editor "^2.6.0" - open "^8.0.9" - p-retry "^4.5.0" - rimraf "^3.0.2" - schema-utils "^4.0.0" - selfsigned "^2.1.1" - serve-index "^1.9.1" - sockjs "^0.3.24" - spdy "^4.0.2" - webpack-dev-middleware "^5.3.1" - ws "^8.13.0" - -webpack-merge@^4.1.5: - version "4.2.2" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" - integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== - dependencies: - lodash "^4.17.15" - -webpack-merge@^5.7.3: - version "5.9.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.9.0.tgz#dc160a1c4cf512ceca515cc231669e9ddb133826" - integrity sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg== - dependencies: - clone-deep "^4.0.1" - wildcard "^2.0.0" - -webpack-sources@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== - -webpack@5.82.0: - version "5.82.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.82.0.tgz#3c0d074dec79401db026b4ba0fb23d6333f88e7d" - integrity sha512-iGNA2fHhnDcV1bONdUu554eZx+XeldsaeQ8T67H6KKHl2nUSwX8Zm7cmzOA46ox/X1ARxf7Bjv8wQ/HsB5fxBg== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^1.0.0" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" - acorn "^8.7.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.13.0" - es-module-lexer "^1.2.1" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.1.2" - tapable "^2.1.1" - terser-webpack-plugin "^5.3.7" - watchpack "^2.4.0" - webpack-sources "^3.2.3" - -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -which@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wildcard@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" - integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== - -workerpool@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" - integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -ws@^8.13.0: - version "8.14.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" - integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== - -ws@~8.11.0: - version "8.11.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" - integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-unparser@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" - integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== - dependencies: - camelcase "^6.0.0" - decamelize "^4.0.0" - flat "^5.0.2" - is-plain-obj "^2.1.0" - -yargs@16.2.0, yargs@^16.1.1: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/qrose/build.gradle.kts b/qrose/build.gradle.kts index 6017a26..49168ba 100644 --- a/qrose/build.gradle.kts +++ b/qrose/build.gradle.kts @@ -1,90 +1,15 @@ @file:Suppress("DSL_SCOPE_VIOLATION") -import java.util.Properties - plugins { - alias(libs.plugins.kotlin.multiplatform) - alias(libs.plugins.android.library) - alias(libs.plugins.compose) - alias(libs.plugins.dokka) id("maven-publish") id("signing") } -group = "io.github.alexzhirkevich" -version = libs.versions.qrose.get() - -val _jvmTarget = findProperty("jvmTarget") as String kotlin { - androidTarget{ - publishLibraryVariants("release") - compilations.all { - kotlinOptions { - jvmTarget = _jvmTarget - } - } - } - ios() - iosSimulatorArm64() - js(IR){ - browser() - } - jvm("desktop"){ - compilations.all { - kotlinOptions { - jvmTarget = _jvmTarget - } - } - } - - macosArm64() - macosX64() - - - sourceSets { - val commonMain by getting { - dependencies { - implementation(compose.ui) - } - } - - val jsMain by getting - val iosMain by getting - val iosSimulatorArm64Main by getting - val desktopMain by getting - val macosX64Main by getting - val macosArm64Main by getting - - val darwinMain by creating { - dependsOn(commonMain) - macosX64Main.dependsOn(this) - macosArm64Main.dependsOn(this) - iosMain.dependsOn(this) - iosSimulatorArm64Main.dependsOn(this) - } - - val nonAndroidMain by creating { - dependsOn(commonMain) - jsMain.dependsOn(this) - desktopMain.dependsOn(this) - darwinMain.dependsOn(this) - } - } -} - -android { - namespace = "io.github.alexzhirkevich.qrose" - compileSdk = 34 - defaultConfig { - minSdk = 21 - } - - compileOptions { - sourceCompatibility = JavaVersion.toVersion(_jvmTarget) - targetCompatibility = JavaVersion.toVersion(_jvmTarget) + sourceSets.commonMain.dependencies { + implementation(compose.ui) } - } //ext["signing.keyId"] = null @@ -93,87 +18,87 @@ android { //ext["ossrhUsername"] = null //ext["ossrhPassword"] = null -extra.apply { - val publishPropFile = rootProject.file("local.properties") - if (publishPropFile.exists()) { - Properties().apply { - load(publishPropFile.inputStream()) - }.forEach { name, value -> - if (name == "signing.secretKeyRingFile") { - set(name.toString(), rootProject.file(value.toString()).absolutePath) - } else { - set(name.toString(), value) - } - } - } else { - ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID") - ext["signing.password"] = System.getenv("SIGNING_PASSWORD") - ext["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE") - ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME") - ext["ossrhPassword"]= System.getenv("OSSRH_PASSWORD") - } -} - -val javadocJar by tasks.registering(Jar::class) { - archiveClassifier.set("javadoc") -} -// https://github.com/gradle/gradle/issues/26091 -val signingTasks = tasks.withType() -tasks.withType().configureEach { - dependsOn(signingTasks) -} - -publishing { - if (rootProject.file("local.properties").exists()) { - - repositories { - maven { - val releasesRepoUrl = - "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" - val snapshotsRepoUrl = - "https://s01.oss.sonatype.org/content/repositories/snapshots/" - url = if (version.toString().endsWith("SNAPSHOT")) { - uri(snapshotsRepoUrl) - } else { - uri(releasesRepoUrl) - } - credentials { - username = project.ext.get("ossrhUsername").toString() - password = project.ext.get("ossrhPassword").toString() - } - } - } - } - - publications.withType { - artifact(javadocJar) - pom { - name.set("QRose") - description.set("Styled QR code generation library for Compose Multiplatform") - url.set("https://github.com/alexzhirkevich/qrose") - - licenses { - license { - name.set("MIT") - url.set("https://opensource.org/licenses/MIT") - } - } - developers { - developer { - id.set("alexzhirkevich") - name.set("Alexander Zhirkevich") - email.set("sasha.zhirkevich@gmail.com") - } - } - scm { - url.set("https://github.com/alexzhirkevich/qrose") - connection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") - developerConnection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") - } - } - } -} - -signing { - sign(publishing.publications) -} \ No newline at end of file +//extra.apply { +// val publishPropFile = rootProject.file("local.properties") +// if (publishPropFile.exists()) { +// Properties().apply { +// load(publishPropFile.inputStream()) +// }.forEach { name, value -> +// if (name == "signing.secretKeyRingFile") { +// set(name.toString(), rootProject.file(value.toString()).absolutePath) +// } else { +// set(name.toString(), value) +// } +// } +// } else { +// ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID") +// ext["signing.password"] = System.getenv("SIGNING_PASSWORD") +// ext["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE") +// ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME") +// ext["ossrhPassword"]= System.getenv("OSSRH_PASSWORD") +// } +//} +// +//val javadocJar by tasks.registering(Jar::class) { +// archiveClassifier.set("javadoc") +//} +//// https://github.com/gradle/gradle/issues/26091 +//val signingTasks = tasks.withType() +//tasks.withType().configureEach { +// dependsOn(signingTasks) +//} + +//publishing { +// if (rootProject.file("local.properties").exists()) { +// +// repositories { +// maven { +// val releasesRepoUrl = +// "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" +// val snapshotsRepoUrl = +// "https://s01.oss.sonatype.org/content/repositories/snapshots/" +// url = if (version.toString().endsWith("SNAPSHOT")) { +// uri(snapshotsRepoUrl) +// } else { +// uri(releasesRepoUrl) +// } +// credentials { +// username = project.ext.get("ossrhUsername").toString() +// password = project.ext.get("ossrhPassword").toString() +// } +// } +// } +// } +// +// publications.withType { +// artifact(javadocJar) +// pom { +// name.set("QRose") +// description.set("Styled QR code generation library for Compose Multiplatform") +// url.set("https://github.com/alexzhirkevich/qrose") +// +// licenses { +// license { +// name.set("MIT") +// url.set("https://opensource.org/licenses/MIT") +// } +// } +// developers { +// developer { +// id.set("alexzhirkevich") +// name.set("Alexander Zhirkevich") +// email.set("sasha.zhirkevich@gmail.com") +// } +// } +// scm { +// url.set("https://github.com/alexzhirkevich/qrose") +// connection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") +// developerConnection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") +// } +// } +// } +//} + +//signing { +// sign(publishing.publications) +//} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt new file mode 100644 index 0000000..cad66a0 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt @@ -0,0 +1,135 @@ +package io.github.alexzhirkevich.qrose + +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Canvas +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.drawscope.CanvasDrawScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.toSize +import kotlin.math.ceil + +abstract class CachedPainter : Painter() { + + private var alpha = 1f + private var colorFilter : ColorFilter?= null + + private var cachedSize: Size? = null + + private var cacheDrawScope = DrawCache() + + override fun applyAlpha(alpha: Float): Boolean { + this.alpha = alpha + return true + } + + override fun applyColorFilter(colorFilter: ColorFilter?): Boolean { + this.colorFilter = colorFilter + return true + } + + abstract fun DrawScope.onCache() + + private val block : DrawScope.() -> Unit = { onCache() } + + override fun DrawScope.onDraw() { + if (cachedSize != size) { + + cacheDrawScope.drawCachedImage( + size = IntSize(ceil(size.width).toInt(), ceil(size.height).toInt()), + density = this, + layoutDirection = layoutDirection, + block = block + ) + cachedSize = size + } + cacheDrawScope.drawInto( + target = this, + alpha = alpha, + colorFilter = colorFilter + ) + } +} + +/** + * Creates a drawing environment that directs its drawing commands to an [ImageBitmap] + * which can be drawn directly in another [DrawScope] instance. This is useful to cache + * complicated drawing commands across frames especially if the content has not changed. + * Additionally some drawing operations such as rendering paths are done purely in + * software so it is beneficial to cache the result and render the contents + * directly through a texture as done by [DrawScope.drawImage] + */ +private class DrawCache { + + @PublishedApi internal var mCachedImage: ImageBitmap? = null + private var cachedCanvas: Canvas? = null + private var scopeDensity: Density? = null + private var layoutDirection: LayoutDirection = LayoutDirection.Ltr + private var size: IntSize = IntSize.Zero + + private val cacheScope = CanvasDrawScope() + + /** + * Draw the contents of the lambda with receiver scope into an [ImageBitmap] with the provided + * size. If the same size is provided across calls, the same [ImageBitmap] instance is + * re-used and the contents are cleared out before drawing content in it again + */ + fun drawCachedImage( + size: IntSize, + density: Density, + layoutDirection: LayoutDirection, + block: DrawScope.() -> Unit + ) { + this.scopeDensity = density + this.layoutDirection = layoutDirection + var targetImage = mCachedImage + var targetCanvas = cachedCanvas + if (targetImage == null || + targetCanvas == null || + size.width > targetImage.width || + size.height > targetImage.height + ) { + targetImage = ImageBitmap(size.width, size.height) + targetCanvas = Canvas(targetImage) + + mCachedImage = targetImage + cachedCanvas = targetCanvas + } + this.size = size + cacheScope.draw(density, layoutDirection, targetCanvas, size.toSize()) { + clear() + block() + } + targetImage.prepareToDraw() + } + + /** + * Draw the cached content into the provided [DrawScope] instance + */ + fun drawInto( + target: DrawScope, + alpha: Float = 1.0f, + colorFilter: ColorFilter? = null + ) { + val targetImage = mCachedImage + check(targetImage != null) { + "drawCachedImage must be invoked first before attempting to draw the result " + + "into another destination" + } + target.drawImage(targetImage, srcSize = size, alpha = alpha, colorFilter = colorFilter) + } + + /** + * Helper method to clear contents of the draw environment from the given bounds of the + * DrawScope + */ + private fun DrawScope.clear() { + drawRect(color = Color.Black, blendMode = BlendMode.Clear) + } +} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DrawCache.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DrawCache.kt index 8e29cd1..7a76bd5 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DrawCache.kt +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DrawCache.kt @@ -28,79 +28,3 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.toSize -/** - * Creates a drawing environment that directs its drawing commands to an [ImageBitmap] - * which can be drawn directly in another [DrawScope] instance. This is useful to cache - * complicated drawing commands across frames especially if the content has not changed. - * Additionally some drawing operations such as rendering paths are done purely in - * software so it is beneficial to cache the result and render the contents - * directly through a texture as done by [DrawScope.drawImage] - */ -internal class DrawCache { - - @PublishedApi internal var mCachedImage: ImageBitmap? = null - private var cachedCanvas: Canvas? = null - private var scopeDensity: Density? = null - private var layoutDirection: LayoutDirection = LayoutDirection.Ltr - private var size: IntSize = IntSize.Zero - - private val cacheScope = CanvasDrawScope() - - /** - * Draw the contents of the lambda with receiver scope into an [ImageBitmap] with the provided - * size. If the same size is provided across calls, the same [ImageBitmap] instance is - * re-used and the contents are cleared out before drawing content in it again - */ - fun drawCachedImage( - size: IntSize, - density: Density, - layoutDirection: LayoutDirection, - block: DrawScope.() -> Unit - ) { - this.scopeDensity = density - this.layoutDirection = layoutDirection - var targetImage = mCachedImage - var targetCanvas = cachedCanvas - if (targetImage == null || - targetCanvas == null || - size.width > targetImage.width || - size.height > targetImage.height - ) { - targetImage = ImageBitmap(size.width, size.height) - targetCanvas = Canvas(targetImage) - - mCachedImage = targetImage - cachedCanvas = targetCanvas - } - this.size = size - cacheScope.draw(density, layoutDirection, targetCanvas, size.toSize()) { - clear() - block() - } - targetImage.prepareToDraw() - } - - /** - * Draw the cached content into the provided [DrawScope] instance - */ - fun drawInto( - target: DrawScope, - alpha: Float = 1.0f, - colorFilter: ColorFilter? = null - ) { - val targetImage = mCachedImage - check(targetImage != null) { - "drawCachedImage must be invoked first before attempting to draw the result " + - "into another destination" - } - target.drawImage(targetImage, srcSize = size, alpha = alpha, colorFilter = colorFilter) - } - - /** - * Helper method to clear contents of the draw environment from the given bounds of the - * DrawScope - */ - private fun DrawScope.clear() { - drawRect(color = Color.Black, blendMode = BlendMode.Clear) - } -} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/QrCodePainter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/QrCodePainter.kt index 52d320e..4a39900 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/QrCodePainter.kt +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/QrCodePainter.kt @@ -96,8 +96,7 @@ fun rememberQrCodePainter( class QrCodePainter( val data : String, val options: QrOptions = QrOptions(), -) : Painter() { - +) : CachedPainter() { private val initialMatrixSize : Int private val actualCodeMatrix = options.shapes.code.run { @@ -122,6 +121,7 @@ class QrCodePainter( codeMatrix.size.toFloat() * 10f ) + private val shapeIncrease = (codeMatrix.size - initialMatrixSize)/2 private val balls = mutableListOf( @@ -155,12 +155,6 @@ class QrCodePainter( private val shouldSeparateBalls get() = options.colors.ball.isSpecified || shouldSeparateDarkPixels - private var colorFilter: ColorFilter? = null - private var alpha: Float = 1f - - private val cacheDrawScope = DrawCache() - private var cachedSize: Size? = null - override fun toString(): String { return "QrCodePainter(data = $data)" } @@ -169,16 +163,6 @@ class QrCodePainter( return data.hashCode() * 31 + options.hashCode() } - override fun applyAlpha(alpha: Float): Boolean { - this.alpha = alpha - return true - } - - override fun applyColorFilter(colorFilter: ColorFilter?): Boolean { - this.colorFilter = colorFilter - return true - } - private val DrawScope.logoSize get() = size * options.logo.size @@ -188,8 +172,10 @@ class QrCodePainter( private val DrawScope.pixelSize : Float get() = minOf(size.width, size.height) / codeMatrix.size - private val drawBlock: DrawScope.() -> Unit = { draw() } + override fun DrawScope.onCache() { + draw() + } private fun DrawScope.draw() { val pixelSize = pixelSize @@ -229,29 +215,6 @@ class QrCodePainter( drawLogo() } - - - override fun DrawScope.onDraw() { - - if (cachedSize != size) { - - codeMatrix = actualCodeMatrix.copy() - - cacheDrawScope.drawCachedImage( - size = IntSize(ceil(size.width).toInt(), ceil(size.height).toInt()), - density = this, - layoutDirection = layoutDirection, - block = drawBlock - ) - } - cacheDrawScope.drawInto( - target = this, - alpha = alpha, - colorFilter = colorFilter - ) - } - - private fun DrawScope.drawSeparatePixels( pixelSize: Float, ){ @@ -369,7 +332,7 @@ class QrCodePainter( left = center.x - logoSize.width / 2, top = center.y - logoSize.height / 2 ) { - draw(logoSize, alpha, colorFilter) + draw(logoSize) } } } diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt new file mode 100644 index 0000000..388ffc0 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt @@ -0,0 +1,647 @@ +package io.github.alexzhirkevich.qrose.oned + +internal class Code128 { + // Results of minimal lookahead for code C + private enum class CType { + UNCODABLE, + ONE_DIGIT, + TWO_DIGITS, + FNC_1 + } + + + fun encode(contents: String, compact : Boolean = true , codeSet : Code128Painter.CodeSet? = null): BooleanArray { + val forcedCodeSet = check(contents, codeSet) + + return if (compact) { + MinimalEncoder().encode(contents) + } else { + encodeFast( + contents, + forcedCodeSet + ) + } + } + + /** + * Encodes minimally using Divide-And-Conquer with Memoization + */ + private class MinimalEncoder { + private enum class Charset { + A, + B, + C, + NONE + } + + private enum class Latch { + A, + B, + C, + SHIFT, + NONE + } + + private var memoizedCost: Array? = null + private var minPath: Array>? = null + + fun encode(contents: String): BooleanArray { + memoizedCost = Array(4) { IntArray(contents.length) } + minPath = Array(4) { + arrayOfNulls( + contents.length + ) + } + encode(contents, Charset.NONE, 0) + val patterns: MutableCollection = ArrayList() + val checkSum = intArrayOf(0) + val checkWeight = intArrayOf(1) + val length = contents.length + var charset = Charset.NONE + var i = 0 + while (i < length) { + val latch = minPath!![charset.ordinal][i] + when (latch) { + Latch.A -> { + charset = Charset.A + addPattern( + patterns, + if (i == 0) CODE_START_A else CODE_CODE_A, + checkSum, + checkWeight, + i + ) + } + + Latch.B -> { + charset = Charset.B + addPattern( + patterns, + if (i == 0) CODE_START_B else CODE_CODE_B, + checkSum, + checkWeight, + i + ) + } + + Latch.C -> { + charset = Charset.C + addPattern( + patterns, + if (i == 0) CODE_START_C else CODE_CODE_C, + checkSum, + checkWeight, + i + ) + } + + Latch.SHIFT -> addPattern(patterns, CODE_SHIFT, checkSum, checkWeight, i) + else -> {} + } + if (charset == Charset.C) { + if (contents[i] == ESCAPE_FNC_1) { + addPattern(patterns, CODE_FNC_1, checkSum, checkWeight, i) + } else { + addPattern( + patterns, + contents.substring(i, i + 2).toInt(), + checkSum, + checkWeight, + i + ) + require(i + 1 < length) //the algorithm never leads to a single trailing digit in character set C + + if (i + 1 < length) { + i++ + } + } + } else { // charset A or B + var patternIndex: Int + patternIndex = + when (contents[i]) { + ESCAPE_FNC_1 -> CODE_FNC_1 + ESCAPE_FNC_2 -> CODE_FNC_2 + ESCAPE_FNC_3 -> CODE_FNC_3 + ESCAPE_FNC_4 -> if (charset == Charset.A && latch != Latch.SHIFT || + charset == Charset.B && latch == Latch.SHIFT + ) { + CODE_FNC_4_A + } else { + CODE_FNC_4_B + } + + else -> contents[i].code - ' '.code + } + if ((charset == Charset.A && latch != Latch.SHIFT || + charset == Charset.B && latch == Latch.SHIFT) && + patternIndex < 0 + ) { + patternIndex += '`'.code + } + addPattern(patterns, patternIndex, checkSum, checkWeight, i) + } + i++ + } + memoizedCost = null + minPath = null + return produceResult(patterns, checkSum[0]) + } + + private fun canEncode(contents: CharSequence, charset: Charset, position: Int): Boolean { + val c = contents[position] + return when (charset) { + Charset.A -> c == ESCAPE_FNC_1 || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4 || A.indexOf( + c + ) >= 0 + + Charset.B -> c == ESCAPE_FNC_1 || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4 || B.indexOf( + c + ) >= 0 + + Charset.C -> c == ESCAPE_FNC_1 || position + 1 < contents.length && + isDigit(c) && isDigit(contents[position + 1]) + + else -> false + } + } + + /** + * Encode the string starting at position position starting with the character set charset + */ + private fun encode(contents: CharSequence, charset: Charset, position: Int): Int { + require(position < contents.length) + val mCost = memoizedCost!![charset.ordinal][position] + if (mCost > 0) { + return mCost + } + var minCost = Int.MAX_VALUE + var minLatch: Latch? = Latch.NONE + val atEnd = position + 1 >= contents.length + val sets = arrayOf(Charset.A, Charset.B) + for (i in 0..1) { + if (canEncode(contents, sets[i], position)) { + var cost = 1 + var latch = Latch.NONE + if (charset != sets[i]) { + cost++ + latch = Latch.valueOf( + sets[i].toString() + ) + } + if (!atEnd) { + cost += encode(contents, sets[i], position + 1) + } + if (cost < minCost) { + minCost = cost + minLatch = latch + } + cost = 1 + if (charset == sets[(i + 1) % 2]) { + cost++ + latch = Latch.SHIFT + if (!atEnd) { + cost += encode(contents, charset, position + 1) + } + if (cost < minCost) { + minCost = cost + minLatch = latch + } + } + } + } + if (canEncode(contents, Charset.C, position)) { + var cost = 1 + var latch = Latch.NONE + if (charset != Charset.C) { + cost++ + latch = Latch.C + } + val advance = if (contents[position] == ESCAPE_FNC_1) 1 else 2 + if (position + advance < contents.length) { + cost += encode(contents, Charset.C, position + advance) + } + if (cost < minCost) { + minCost = cost + minLatch = latch + } + } + if (minCost == Int.MAX_VALUE) { + throw IllegalArgumentException("Bad character in input: ASCII value=" + contents[position].code) + } + memoizedCost!![charset.ordinal][position] = minCost + minPath!![charset.ordinal][position] = minLatch + return minCost + } + + companion object { + const val A = + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u0000\u0001\u0002" + + "\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000B\u000C\r\u000E\u000F\u0010\u0011" + + "\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F" + + "\u00FF" + const val B = + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr" + + "stuvwxyz{|}~\u007F\u00FF" + private const val CODE_SHIFT = 98 + + private fun addPattern( + patterns: MutableCollection, + patternIndex: Int, + checkSum: IntArray, + checkWeight: IntArray, + position: Int, + ) { + patterns.add(CODE_PATTERNS.get(patternIndex)) + if (position != 0) { + checkWeight[0]++ + } + checkSum[0] += patternIndex * checkWeight[0] + } + + private fun isDigit(c: Char): Boolean { + return c in '0'..'9' + } + } + } + + + companion object { + + private const val CODE_START_A = 103 + private const val CODE_START_B = 104 + private const val CODE_START_C = 105 + internal const val CODE_CODE_A = 101 + internal const val CODE_CODE_B = 100 + internal const val CODE_CODE_C = 99 + private const val CODE_STOP = 106 + + // Dummy characters used to specify control characters in input + private const val ESCAPE_FNC_1 = '\u00f1' + private const val ESCAPE_FNC_2 = '\u00f2' + private const val ESCAPE_FNC_3 = '\u00f3' + private const val ESCAPE_FNC_4 = '\u00f4' + private const val CODE_FNC_1 = 102 // Code A, Code B, Code C + private const val CODE_FNC_2 = 97 // Code A, Code B + private const val CODE_FNC_3 = 96 // Code A, Code B + private const val CODE_FNC_4_A = 101 // Code A + private const val CODE_FNC_4_B = 100 // Code B + + private fun check(contents: String, codeSet : Code128Painter.CodeSet?): Int { + + // Check content + val length = contents.length + for (i in 0 until length) { + val c = contents[i] + when (c) { + ESCAPE_FNC_1, ESCAPE_FNC_2, ESCAPE_FNC_3, ESCAPE_FNC_4 -> {} + else -> if (c.code > 127) { + // no full Latin-1 character set available at the moment + // shift and manual code change are not supported + throw IllegalArgumentException("Bad character in input: ASCII value=" + c.code) + } + } + when (codeSet) { + Code128Painter.CodeSet.A -> // allows no ascii above 95 (no lower caps, no special symbols) + if (c.code in 96..127) { + throw IllegalArgumentException("Bad character in input for forced code set A: ASCII value=" + c.code) + } + + Code128Painter.CodeSet.B -> // allows no ascii below 32 (terminal symbols) + if (c.code < 32) { + throw IllegalArgumentException("Bad character in input for forced code set B: ASCII value=" + c.code) + } + + Code128Painter.CodeSet.C -> // allows only numbers and no FNC 2/3/4 + if (c.code < 48 || c.code in 58..127 || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4) { + throw IllegalArgumentException("Bad character in input for forced code set C: ASCII value=" + c.code) + } + + else -> {} + } + } + return codeSet?.v ?: -1 + } + + private fun encodeFast(contents: String, forcedCodeSet: Int): BooleanArray { + val length = contents.length + val patterns: MutableCollection = ArrayList() // temporary storage for patterns + var checkSum = 0 + var checkWeight = 1 + var codeSet = 0 // selected code (CODE_CODE_B or CODE_CODE_C) + var position = 0 // position in contents + while (position < length) { + //Select code to use + var newCodeSet: Int + newCodeSet = if (forcedCodeSet == -1) { + chooseCode(contents, position, codeSet) + } else { + forcedCodeSet + } + + //Get the pattern index + var patternIndex: Int + if (newCodeSet == codeSet) { + // Encode the current character + // First handle escapes + when (contents[position]) { + ESCAPE_FNC_1 -> patternIndex = CODE_FNC_1 + ESCAPE_FNC_2 -> patternIndex = CODE_FNC_2 + ESCAPE_FNC_3 -> patternIndex = CODE_FNC_3 + ESCAPE_FNC_4 -> patternIndex = if (codeSet == CODE_CODE_A) { + CODE_FNC_4_A + } else { + CODE_FNC_4_B + } + + else -> when (codeSet) { + CODE_CODE_A -> { + patternIndex = contents[position].code - ' '.code + if (patternIndex < 0) { + // everything below a space character comes behind the underscore in the code patterns table + patternIndex += '`'.code + } + } + + CODE_CODE_B -> patternIndex = contents[position].code - ' '.code + else -> { + // CODE_CODE_C + if (position + 1 == length) { + // this is the last character, but the encoding is C, which always encodes two characers + throw IllegalArgumentException("Bad number of characters for digit only encoding.") + } + patternIndex = contents.substring(position, position + 2).toInt() + position++ // Also incremented below + } + } + } + position++ + } else { + // Should we change the current code? + // Do we have a code set? + patternIndex = if (codeSet == 0) { + // No, we don't have a code set + when (newCodeSet) { + CODE_CODE_A -> CODE_START_A + CODE_CODE_B -> CODE_START_B + else -> CODE_START_C + } + } else { + // Yes, we have a code set + newCodeSet + } + codeSet = newCodeSet + } + + // Get the pattern + patterns.add(CODE_PATTERNS[patternIndex]) + + // Compute checksum + checkSum += patternIndex * checkWeight + if (position != 0) { + checkWeight++ + } + } + return produceResult(patterns, checkSum) + } + + fun produceResult(patterns: MutableCollection, checkSum: Int): BooleanArray { + // Compute and append checksum + val checkSumMod = checkSum % 103 + if (checkSumMod < 0) { + throw IllegalArgumentException("Unable to compute a valid input checksum") + } + patterns.add(CODE_PATTERNS[checkSumMod]) + + // Append stop code + patterns.add(CODE_PATTERNS[CODE_STOP]) + + // Compute code width + var codeWidth = 0 + for (pattern in patterns) { + for (width in pattern) { + codeWidth += width + } + } + + // Compute result + val result = BooleanArray(codeWidth) + var pos = 0 + for (pattern in patterns) { + pos += appendPattern(result, pos, pattern, true) + } + return result + } + + private fun findCType(value: CharSequence, start: Int): CType { + val last = value.length + if (start >= last) { + return CType.UNCODABLE + } + var c = value[start] + if (c == ESCAPE_FNC_1) { + return CType.FNC_1 + } + if (c < '0' || c > '9') { + return CType.UNCODABLE + } + if (start + 1 >= last) { + return CType.ONE_DIGIT + } + c = value[start + 1] + return if (c < '0' || c > '9') { + CType.ONE_DIGIT + } else CType.TWO_DIGITS + } + + private fun chooseCode(value: CharSequence, start: Int, oldCode: Int): Int { + var lookahead = findCType(value, start) + if (lookahead == CType.ONE_DIGIT) { + return if (oldCode == CODE_CODE_A) { + CODE_CODE_A + } else CODE_CODE_B + } + if (lookahead == CType.UNCODABLE) { + if (start < value.length) { + val c = value[start] + if (c < ' ' || oldCode == CODE_CODE_A && (c < '`' || c >= ESCAPE_FNC_1 && c <= ESCAPE_FNC_4)) { + // can continue in code A, encodes ASCII 0 to 95 or FNC1 to FNC4 + return CODE_CODE_A + } + } + return CODE_CODE_B // no choice + } + if (oldCode == CODE_CODE_A && lookahead == CType.FNC_1) { + return CODE_CODE_A + } + if (oldCode == CODE_CODE_C) { // can continue in code C + return CODE_CODE_C + } + if (oldCode == CODE_CODE_B) { + if (lookahead == CType.FNC_1) { + return CODE_CODE_B // can continue in code B + } + // Seen two consecutive digits, see what follows + lookahead = findCType(value, start + 2) + if (lookahead == CType.UNCODABLE || lookahead == CType.ONE_DIGIT) { + return CODE_CODE_B // not worth switching now + } + if (lookahead == CType.FNC_1) { // two digits, then FNC_1... + lookahead = findCType(value, start + 3) + return if (lookahead == CType.TWO_DIGITS) { // then two more digits, switch + CODE_CODE_C + } else { + CODE_CODE_B // otherwise not worth switching + } + } + // At this point, there are at least 4 consecutive digits. + // Look ahead to choose whether to switch now or on the next round. + var index = start + 4 + while (findCType(value, index).also { lookahead = it } == CType.TWO_DIGITS) { + index += 2 + } + return if (lookahead == CType.ONE_DIGIT) { // odd number of digits, switch later + CODE_CODE_B + } else CODE_CODE_C + // even number of digits, switch now + } + // Here oldCode == 0, which means we are choosing the initial code + if (lookahead == CType.FNC_1) { // ignore FNC_1 + lookahead = findCType(value, start + 1) + } + return if (lookahead == CType.TWO_DIGITS) { // at least two digits, start in code C + CODE_CODE_C + } else CODE_CODE_B + } + } +} + +internal fun appendPattern( + target: BooleanArray, + pos: Int, + pattern: IntArray, + startColor: Boolean, +): Int { + var pos = pos + var color = startColor + var numAdded = 0 + for (len in pattern) { + for (j in 0 until len) { + target[pos++] = color + } + numAdded += len + color = !color // flip color after each segment + } + return numAdded +} + +private val CODE_PATTERNS by lazy { + arrayOf( + intArrayOf(2, 1, 2, 2, 2, 2), + intArrayOf(2, 2, 2, 1, 2, 2), + intArrayOf(2, 2, 2, 2, 2, 1), + intArrayOf(1, 2, 1, 2, 2, 3), + intArrayOf(1, 2, 1, 3, 2, 2), + intArrayOf(1, 3, 1, 2, 2, 2), + intArrayOf(1, 2, 2, 2, 1, 3), + intArrayOf(1, 2, 2, 3, 1, 2), + intArrayOf(1, 3, 2, 2, 1, 2), + intArrayOf(2, 2, 1, 2, 1, 3), + intArrayOf(2, 2, 1, 3, 1, 2), + intArrayOf(2, 3, 1, 2, 1, 2), + intArrayOf(1, 1, 2, 2, 3, 2), + intArrayOf(1, 2, 2, 1, 3, 2), + intArrayOf(1, 2, 2, 2, 3, 1), + intArrayOf(1, 1, 3, 2, 2, 2), + intArrayOf(1, 2, 3, 1, 2, 2), + intArrayOf(1, 2, 3, 2, 2, 1), + intArrayOf(2, 2, 3, 2, 1, 1), + intArrayOf(2, 2, 1, 1, 3, 2), + intArrayOf(2, 2, 1, 2, 3, 1), + intArrayOf(2, 1, 3, 2, 1, 2), + intArrayOf(2, 2, 3, 1, 1, 2), + intArrayOf(3, 1, 2, 1, 3, 1), + intArrayOf(3, 1, 1, 2, 2, 2), + intArrayOf(3, 2, 1, 1, 2, 2), + intArrayOf(3, 2, 1, 2, 2, 1), + intArrayOf(3, 1, 2, 2, 1, 2), + intArrayOf(3, 2, 2, 1, 1, 2), + intArrayOf(3, 2, 2, 2, 1, 1), + intArrayOf(2, 1, 2, 1, 2, 3), + intArrayOf(2, 1, 2, 3, 2, 1), + intArrayOf(2, 3, 2, 1, 2, 1), + intArrayOf(1, 1, 1, 3, 2, 3), + intArrayOf(1, 3, 1, 1, 2, 3), + intArrayOf(1, 3, 1, 3, 2, 1), + intArrayOf(1, 1, 2, 3, 1, 3), + intArrayOf(1, 3, 2, 1, 1, 3), + intArrayOf(1, 3, 2, 3, 1, 1), + intArrayOf(2, 1, 1, 3, 1, 3), + intArrayOf(2, 3, 1, 1, 1, 3), + intArrayOf(2, 3, 1, 3, 1, 1), + intArrayOf(1, 1, 2, 1, 3, 3), + intArrayOf(1, 1, 2, 3, 3, 1), + intArrayOf(1, 3, 2, 1, 3, 1), + intArrayOf(1, 1, 3, 1, 2, 3), + intArrayOf(1, 1, 3, 3, 2, 1), + intArrayOf(1, 3, 3, 1, 2, 1), + intArrayOf(3, 1, 3, 1, 2, 1), + intArrayOf(2, 1, 1, 3, 3, 1), + intArrayOf(2, 3, 1, 1, 3, 1), + intArrayOf(2, 1, 3, 1, 1, 3), + intArrayOf(2, 1, 3, 3, 1, 1), + intArrayOf(2, 1, 3, 1, 3, 1), + intArrayOf(3, 1, 1, 1, 2, 3), + intArrayOf(3, 1, 1, 3, 2, 1), + intArrayOf(3, 3, 1, 1, 2, 1), + intArrayOf(3, 1, 2, 1, 1, 3), + intArrayOf(3, 1, 2, 3, 1, 1), + intArrayOf(3, 3, 2, 1, 1, 1), + intArrayOf(3, 1, 4, 1, 1, 1), + intArrayOf(2, 2, 1, 4, 1, 1), + intArrayOf(4, 3, 1, 1, 1, 1), + intArrayOf(1, 1, 1, 2, 2, 4), + intArrayOf(1, 1, 1, 4, 2, 2), + intArrayOf(1, 2, 1, 1, 2, 4), + intArrayOf(1, 2, 1, 4, 2, 1), + intArrayOf(1, 4, 1, 1, 2, 2), + intArrayOf(1, 4, 1, 2, 2, 1), + intArrayOf(1, 1, 2, 2, 1, 4), + intArrayOf(1, 1, 2, 4, 1, 2), + intArrayOf(1, 2, 2, 1, 1, 4), + intArrayOf(1, 2, 2, 4, 1, 1), + intArrayOf(1, 4, 2, 1, 1, 2), + intArrayOf(1, 4, 2, 2, 1, 1), + intArrayOf(2, 4, 1, 2, 1, 1), + intArrayOf(2, 2, 1, 1, 1, 4), + intArrayOf(4, 1, 3, 1, 1, 1), + intArrayOf(2, 4, 1, 1, 1, 2), + intArrayOf(1, 3, 4, 1, 1, 1), + intArrayOf(1, 1, 1, 2, 4, 2), + intArrayOf(1, 2, 1, 1, 4, 2), + intArrayOf(1, 2, 1, 2, 4, 1), + intArrayOf(1, 1, 4, 2, 1, 2), + intArrayOf(1, 2, 4, 1, 1, 2), + intArrayOf(1, 2, 4, 2, 1, 1), + intArrayOf(4, 1, 1, 2, 1, 2), + intArrayOf(4, 2, 1, 1, 1, 2), + intArrayOf(4, 2, 1, 2, 1, 1), + intArrayOf(2, 1, 2, 1, 4, 1), + intArrayOf(2, 1, 4, 1, 2, 1), + intArrayOf(4, 1, 2, 1, 2, 1), + intArrayOf(1, 1, 1, 1, 4, 3), + intArrayOf(1, 1, 1, 3, 4, 1), + intArrayOf(1, 3, 1, 1, 4, 1), + intArrayOf(1, 1, 4, 1, 1, 3), + intArrayOf(1, 1, 4, 3, 1, 1), + intArrayOf(4, 1, 1, 1, 1, 3), + intArrayOf(4, 1, 1, 3, 1, 1), + intArrayOf(1, 1, 3, 1, 4, 1), + intArrayOf(1, 1, 4, 1, 3, 1), + intArrayOf(3, 1, 1, 1, 4, 1), + intArrayOf(4, 1, 1, 1, 3, 1), + intArrayOf(2, 1, 1, 4, 1, 2), + intArrayOf(2, 1, 1, 2, 1, 4), + intArrayOf(2, 1, 1, 2, 3, 2), + intArrayOf(2, 3, 3, 1, 1, 1, 2) + ) +} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt new file mode 100644 index 0000000..6342381 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt @@ -0,0 +1,75 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + + +/** + * Code 128 barcode painter + * + * @param brush code brush + * @param compact Specifies whether to use compact mode for Code-128 code. This can yield slightly smaller bar codes. This option and [forceCodeSet] are mutually exclusive. + * @param forceCodeSet Forces which encoding will be used. Currently only used for Code-128 code sets. This option and CODE128_COMPACT are mutually exclusive + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberCode128Painter( + data: String, + brush: Brush = SolidColor(Color.Black), + compact : Boolean = true, + forceCodeSet : Code128Painter.CodeSet? = null, + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush, forceCodeSet) { + runCatching { + Code128Painter( + data = data, + density = density.density, + brush = brush, + compact = compact, + codeSet = forceCodeSet, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + + +@Immutable +class Code128Painter( + val data : String, + val brush: Brush = SolidColor(Color.Black), + val compact : Boolean = true, + val codeSet : CodeSet? = null, + density: Float, + builder : BarcodePathBuilder= ::defaultOneDBarcodeBuilder +) : SingleDimensionBarcodePainter( + density = density, + code = Code128().encode(data, compact, codeSet).toList(), + brush = brush, + builder = builder +) { + + enum class CodeSet(internal val v: Int) { + A(Code128.CODE_CODE_A), + B(Code128.CODE_CODE_B), + C(Code128.CODE_CODE_C) + } +} diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt new file mode 100644 index 0000000..71b55d9 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt @@ -0,0 +1,121 @@ +package io.github.alexzhirkevich.qrose.oned + +internal class Code39 { + + fun encode(contents: String): BooleanArray { + var actualContent = contents + var length = actualContent.length + if (length > 80) { + throw IllegalArgumentException( + "Requested contents should be less than 80 digits long, but got $length" + ) + } + for (i in 0 until length) { + val indexInString: Int = ALPHABET_STRING.indexOf(actualContent[i]) + if (indexInString < 0) { + actualContent = tryToConvertToExtendedMode(actualContent) + length = actualContent.length + if (length > 80) { + throw IllegalArgumentException( + "Requested contents should be less than 80 digits long, but got " + + length + " (extended full ASCII mode)" + ) + } + break + } + } + val widths = IntArray(9) + val codeWidth = 24 + 1 + (13 * length) + val result = BooleanArray(codeWidth) + toIntArray(ASTERISK_ENCODING, widths) + var pos = appendPattern(result, 0, widths, true) + val narrowWhite = intArrayOf(1) + pos += appendPattern(result, pos, narrowWhite, false) + //append next character to byte matrix + for (i in 0 until length) { + val indexInString: Int = ALPHABET_STRING.indexOf(actualContent[i]) + toIntArray(CHARACTER_ENCODINGS[indexInString], widths) + pos += appendPattern(result, pos, widths, true) + pos += appendPattern(result, pos, narrowWhite, false) + } + toIntArray(ASTERISK_ENCODING, widths) + appendPattern(result, pos, widths, true) + return result + } + + companion object { + + + const val ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. \$/+%" + + val CHARACTER_ENCODINGS by lazy { + intArrayOf( + 0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, // 0-9 + // 0-9 + 0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, // A-J + // A-J + 0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, // K-T + // K-T + 0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x0A8, // U-$ + // U-$ + 0x0A2, 0x08A, 0x02A // /-% + // /-% + ) + } + + val ASTERISK_ENCODING = 0x094 + + private fun toIntArray(a: Int, toReturn: IntArray) { + for (i in 0..8) { + val temp = a and (1 shl (8 - i)) + toReturn[i] = if (temp == 0) 1 else 2 + } + } + + private fun tryToConvertToExtendedMode(contents: String): String { + val length = contents.length + val extendedContent = StringBuilder() + for (i in 0 until length) { + val character = contents[i] + when (character) { + '\u0000' -> extendedContent.append("%U") + ' ', '-', '.' -> extendedContent.append(character) + '@' -> extendedContent.append("%V") + '`' -> extendedContent.append("%W") + else -> if (character.code <= 26) { + extendedContent.append('$') + extendedContent.append(('A'.code + (character.code - 1)).toChar()) + } else if (character < ' ') { + extendedContent.append('%') + extendedContent.append(('A'.code + (character.code - 27)).toChar()) + } else if ((character <= ',') || (character == '/') || (character == ':')) { + extendedContent.append('/') + extendedContent.append(('A'.code + (character.code - 33)).toChar()) + } else if (character <= '9') { + extendedContent.append(('0'.code + (character.code - 48)).toChar()) + } else if (character <= '?') { + extendedContent.append('%') + extendedContent.append(('F'.code + (character.code - 59)).toChar()) + } else if (character <= 'Z') { + extendedContent.append(('A'.code + (character.code - 65)).toChar()) + } else if (character <= '_') { + extendedContent.append('%') + extendedContent.append(('K'.code + (character.code - 91)).toChar()) + } else if (character <= 'z') { + extendedContent.append('+') + extendedContent.append(('A'.code + (character.code - 97)).toChar()) + } else if (character.code <= 127) { + extendedContent.append('%') + extendedContent.append(('P'.code + (character.code - 123)).toChar()) + } else { + throw IllegalArgumentException( + "Requested content contains a non-encodable character: '" + contents[i] + "'" + ) + } + } + } + return extendedContent.toString() + } + } +} + diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt new file mode 100644 index 0000000..84f31e9 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt @@ -0,0 +1,57 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * Code 39 barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberCode39Painter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + Code39Painter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +@Immutable +class Code39Painter( + val data : String, + val brush : Brush = SolidColor(Color.Black), + density: Float = 1f, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : SingleDimensionBarcodePainter( + density = density, + code = Code39().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt new file mode 100644 index 0000000..df88aa9 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt @@ -0,0 +1,100 @@ +package io.github.alexzhirkevich.qrose.oned + + +internal class Code93 { + + fun encode(contents: String): BooleanArray { + var actualContents = contents + var length = actualContents.length + if (length > 80) { + throw IllegalArgumentException( + "Requested contents should be less than 80 digits long, but got $length" + ) + } + for (i in 0 until length) { + val indexInString: Int = Code39.ALPHABET_STRING.indexOf(actualContents[i]) + if (indexInString < 0) { + actualContents = tryToConvertToExtendedMode(actualContents) + length = actualContents.length + if (length > 80) { + throw IllegalArgumentException( + "Requested contents should be less than 80 digits long, but got " + + length + " (extended full ASCII mode)" + ) + } + break + } + } + val widths = IntArray(9) + val codeWidth = 24 + 1 + (13 * length) + val result = BooleanArray(codeWidth) + toIntArray(Code39.ASTERISK_ENCODING, widths) + var pos = appendPattern(result, 0, widths, true) + val narrowWhite = intArrayOf(1) + pos += appendPattern(result, pos, narrowWhite, false) + //append next character to byte matrix + for (i in 0 until length) { + val indexInString: Int = Code39.ALPHABET_STRING.indexOf(actualContents[i]) + toIntArray(Code39.CHARACTER_ENCODINGS.get(indexInString), widths) + pos += appendPattern(result, pos, widths, true) + pos += appendPattern(result, pos, narrowWhite, false) + } + toIntArray(Code39.ASTERISK_ENCODING, widths) + appendPattern(result, pos, widths, true) + return result + } + + companion object { + private fun toIntArray(a: Int, toReturn: IntArray) { + for (i in 0..8) { + val temp = a and (1 shl (8 - i)) + toReturn[i] = if (temp == 0) 1 else 2 + } + } + + private fun tryToConvertToExtendedMode(contents: String): String { + val length = contents.length + val extendedContent: StringBuilder = StringBuilder() + for (i in 0 until length) { + val character = contents[i] + when (character) { + '\u0000' -> extendedContent.append("%U") + ' ', '-', '.' -> extendedContent.append(character) + '@' -> extendedContent.append("%V") + '`' -> extendedContent.append("%W") + else -> if (character.code <= 26) { + extendedContent.append('$') + extendedContent.append(('A'.code + (character.code - 1)).toChar()) + } else if (character < ' ') { + extendedContent.append('%') + extendedContent.append(('A'.code + (character.code - 27)).toChar()) + } else if ((character <= ',') || (character == '/') || (character == ':')) { + extendedContent.append('/') + extendedContent.append(('A'.code + (character.code - 33)).toChar()) + } else if (character <= '9') { + extendedContent.append(('0'.code + (character.code - 48)).toChar()) + } else if (character <= '?') { + extendedContent.append('%') + extendedContent.append(('F'.code + (character.code - 59)).toChar()) + } else if (character <= 'Z') { + extendedContent.append(('A'.code + (character.code - 65)).toChar()) + } else if (character <= '_') { + extendedContent.append('%') + extendedContent.append(('K'.code + (character.code - 91)).toChar()) + } else if (character <= 'z') { + extendedContent.append('+') + extendedContent.append(('A'.code + (character.code - 97)).toChar()) + } else if (character.code <= 127) { + extendedContent.append('%') + extendedContent.append(('P'.code + (character.code - 123)).toChar()) + } else { + throw IllegalArgumentException( + "Requested content contains a non-encodable character: '" + contents[i] + "'" + ) + } + } + } + return extendedContent.toString() + } + } +} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt new file mode 100644 index 0000000..f4434ea --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt @@ -0,0 +1,59 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * Code 93 barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberCode93Painter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + Code93Painter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +@Immutable +class Code93Painter( + val data : String, + val brush : Brush = SolidColor(Color.Black), + density: Float = 1f, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : SingleDimensionBarcodePainter( + density = density, + code = Code93().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt new file mode 100644 index 0000000..c7c6421 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt @@ -0,0 +1,56 @@ +package io.github.alexzhirkevich.qrose.oned + + +internal class CodeEAN13 { + + private val CODE_WIDTH = 3 + 7 * 6 + // left bars + 5 + 7 * 6 + // right bars + 3 // end guard + + + fun encode(contents: String): BooleanArray { + var contents = contents + val length = contents.length + when (length) { + 12 -> { + contents += CodeEAN8.getStandardUPCEANChecksum(contents) + } + + 13 -> if (!CodeEAN8.checkStandardUPCEANChecksum(contents)) { + throw IllegalArgumentException("Contents do not pass checksum") + } + + else -> throw IllegalArgumentException( + "Requested contents should be 12 or 13 digits long, but got $length" + ) + } + contents.requireNumeric() + val firstDigit = contents[0].digitToIntOrNull() ?: -1 + val parities: Int = CodeEAN8.FIRST_DIGIT_ENCODINGS[firstDigit] + val result = BooleanArray(CODE_WIDTH) + var pos = 0 + pos += appendPattern(result, pos, CodeEAN8.START_END_PATTERN, true) + + // See EAN13Reader for a description of how the first digit & left bars are encoded + for (i in 1..6) { + var digit = contents[i].digitToIntOrNull() ?: -1 + if (parities shr 6 - i and 1 == 1) { + digit += 10 + } + pos += appendPattern( + result, + pos, + CodeEAN8.L_AND_G_PATTERNS.get(digit), + false + ) + } + pos += appendPattern(result, pos, CodeEAN8.MIDDLE_PATTERN, false) + for (i in 7..12) { + val digit = contents[i].digitToIntOrNull() ?: -1 + pos += appendPattern(result, pos, CodeEAN8.L_PATTERNS.get(digit), true) + } + appendPattern(result, pos, CodeEAN8.START_END_PATTERN, true) + return result + } + +} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt new file mode 100644 index 0000000..ef82cf6 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt @@ -0,0 +1,163 @@ +package io.github.alexzhirkevich.qrose.oned + +internal class CodeEAN8 { + private val CODE_WIDTH = 3 + 7 * 4 + // left bars + 5 + 7 * 4 + // right bars + 3 // end guard + + + /** + * @return a byte array of horizontal pixels (false = white, true = black) + */ + fun encode(contents: String): BooleanArray { + var actualContentns = contents + val length = actualContentns.length + when (length) { + 7 -> { + // No check digit present, calculate it and add it + val check: Int = getStandardUPCEANChecksum(actualContentns) + actualContentns += check + } + + 8 -> if (!checkStandardUPCEANChecksum(actualContentns)) { + throw IllegalArgumentException("Contents do not pass checksum") + } + + else -> throw IllegalArgumentException( + "Requested contents should be 7 or 8 digits long, but got $length" + ) + } + actualContentns.requireNumeric() + val result = BooleanArray(CODE_WIDTH) + var pos = 0 + pos += appendPattern(result, pos, START_END_PATTERN, true) + for (i in 0..3) { + val digit = actualContentns[i].digitToIntOrNull() ?: -1 + pos += appendPattern( + result, + pos, + L_PATTERNS.get(digit), + false + ) + } + pos += appendPattern( + result, + pos, + MIDDLE_PATTERN, + false + ) + for (i in 4..7) { + val digit = actualContentns[i].digitToIntOrNull() ?: -1 + pos += appendPattern(result, pos, L_PATTERNS.get(digit), true) + } + appendPattern(result, pos, START_END_PATTERN, true) + return result + } + + companion object { + fun checkStandardUPCEANChecksum(s: CharSequence): Boolean { + val length = s.length + if (length == 0) { + return false + } + val check = s[length - 1].digitToIntOrNull() ?: -1 + return getStandardUPCEANChecksum(s.subSequence(0, length - 1)) == check + } + + fun getStandardUPCEANChecksum(s: CharSequence): Int { + val length = s.length + var sum = 0 + run { + var i = length - 1 + while (i >= 0) { + val digit = s[i].code - '0'.code + if (digit < 0 || digit > 9) { + throw IllegalStateException("Illegal contents") + } + sum += digit + i -= 2 + } + } + sum *= 3 + var i = length - 2 + while (i >= 0) { + val digit = s[i].code - '0'.code + if (digit < 0 || digit > 9) { + throw IllegalStateException("Illegal contents") + } + sum += digit + i -= 2 + } + return (1000 - sum) % 10 + } + + // These two values are critical for determining how permissive the decoding will be. + // We've arrived at these values through a lot of trial and error. Setting them any higher + // lets false positives creep in quickly. + private const val MAX_AVG_VARIANCE = 0.48f + private const val MAX_INDIVIDUAL_VARIANCE = 0.7f + + /** + * Start/end guard pattern. + */ + val START_END_PATTERN by lazy { + intArrayOf(1, 1, 1) + } + + /** + * Pattern marking the middle of a UPC/EAN pattern, separating the two halves. + */ + val MIDDLE_PATTERN by lazy { + intArrayOf(1, 1, 1, 1, 1) + } + + /** + * end guard pattern. + */ + val END_PATTERN by lazy { + intArrayOf(1, 1, 1, 1, 1, 1) + } + /** + * "Odd", or "L" patterns used to encode UPC/EAN digits. + */ + val L_PATTERNS by lazy { + arrayOf( + intArrayOf(3, 2, 1, 1), + intArrayOf(2, 2, 2, 1), + intArrayOf(2, 1, 2, 2), + intArrayOf(1, 4, 1, 1), + intArrayOf(1, 1, 3, 2), + intArrayOf(1, 2, 3, 1), + intArrayOf(1, 1, 1, 4), + intArrayOf(1, 3, 1, 2), + intArrayOf(1, 2, 1, 3), + intArrayOf(3, 1, 1, 2) + ) + } + + val L_AND_G_PATTERNS by lazy { + buildList(20) { + addAll(L_PATTERNS) + + for (i in 10..19) { + val widths: IntArray = L_PATTERNS[i - 10] + val reversedWidths = IntArray(widths.size) + for (j in widths.indices) { + reversedWidths[j] = widths[widths.size - j - 1] + } + add(reversedWidths) + } + } + } + + val FIRST_DIGIT_ENCODINGS by lazy { + intArrayOf( + 0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A + ) + } + } +} + +internal fun String.requireNumeric() = require(all { it.isDigit() }){ + "Input should only contain digits 0-9" +} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt new file mode 100644 index 0000000..52c8343 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt @@ -0,0 +1,55 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * EAN13 barcode barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberEAN13Painter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + EAN13Painter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +class EAN13Painter( + val data : String, + val brush: Brush = SolidColor(Color.Black), + density : Float = 1f, + builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, +) : SingleDimensionBarcodePainter( + density = density, + code = CodeEAN13().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt new file mode 100644 index 0000000..9762860 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt @@ -0,0 +1,55 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * EAN8 barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberEAN8Painter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + EAN8Painter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +class EAN8Painter( + val data : String, + val brush: Brush = SolidColor(Color.Black), + density : Float = 1f, + builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, +) : SingleDimensionBarcodePainter( + density = density, + code = CodeEAN8().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt new file mode 100644 index 0000000..b5d6a09 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt @@ -0,0 +1,50 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.util.fastForEachIndexed +import io.github.alexzhirkevich.qrose.CachedPainter + +typealias BarcodePathBuilder = (size : Size, code : List) -> Path + +sealed class SingleDimensionBarcodePainter( + density: Float, + private val code : List, + private val brush: Brush, + private val builder : BarcodePathBuilder +) : CachedPainter(){ + + override fun DrawScope.onCache() { + drawPath( + path = builder(size, code), + brush = brush + ) + } + + override val intrinsicSize: Size = Size( + width = density * 3 * code.size, + height = 60 * density + ) +} + +@PublishedApi +internal fun defaultOneDBarcodeBuilder(size : Size, data : List): Path = Path().apply { + + val width = size.width / data.size + + data.fastForEachIndexed { i, b -> + if (b) { + addRect( + Rect( + left = i * width, + top = 0f, + right = (i + 1) * width, + bottom = size.height + ) + ) + } + } +} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/QrBrush.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/QrBrush.kt index 5e88850..c49416c 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/QrBrush.kt +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/QrBrush.kt @@ -1,22 +1,16 @@ package io.github.alexzhirkevich.qrose.options import androidx.compose.runtime.Immutable -import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageShader import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.TileMode -import androidx.compose.ui.graphics.drawscope.CanvasDrawScope import androidx.compose.ui.graphics.isUnspecified import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.LayoutDirection import io.github.alexzhirkevich.qrose.options.QrBrushMode.Separate import io.github.alexzhirkevich.qrose.toImageBitmap import kotlin.random.Random diff --git a/qrose/src/nonAndroidMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt b/qrose/src/skikoMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt similarity index 100% rename from qrose/src/nonAndroidMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt rename to qrose/src/skikoMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index d7d93e4..4157867 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,6 +19,7 @@ dependencyResolutionManagement { rootProject.name = "QRose" include(":qrose") + include(":example:desktopApp") include(":example:webJsApp") include(":example:androidapp") From 051dd576f4698215381223157ae5d6a8708d1c7d Mon Sep 17 00:00:00 2001 From: Zhirkevich Alexander Y Date: Wed, 28 Feb 2024 18:01:09 +0300 Subject: [PATCH 05/13] wasm example --- .gitignore | 4 +- example/shared/build.gradle.kts | 4 ++ example/webApp/build.gradle.kts | 46 +++++++++++++++++++ .../alexzhirkevich/qrose/example/web/main.kt | 2 +- .../src/jsMain/resources/index.html | 2 +- .../alexzhirkevich/qrose/example/web/main.kt | 12 +++++ .../src/wasmJsMain/resources/index.html | 12 +++++ example/webJsApp/build.gradle.kts | 32 ------------- .../qrose/qrcode/internals/QRData.kt | 9 ---- settings.gradle.kts | 2 +- 10 files changed, 80 insertions(+), 45 deletions(-) create mode 100644 example/webApp/build.gradle.kts rename example/{webJsApp => webApp}/src/jsMain/kotlin/io/github/alexzhirkevich/qrose/example/web/main.kt (86%) rename example/{webJsApp => webApp}/src/jsMain/resources/index.html (85%) create mode 100644 example/webApp/src/wasmJsMain/kotlin/io/github/alexzhirkevich/qrose/example/web/main.kt create mode 100644 example/webApp/src/wasmJsMain/resources/index.html delete mode 100644 example/webJsApp/build.gradle.kts diff --git a/.gitignore b/.gitignore index b1095f4..d115b0b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ local.properties xcuserdata *.gpg *.pgp -*.psd \ No newline at end of file +*.psd + +kotlin-js-store \ No newline at end of file diff --git a/example/shared/build.gradle.kts b/example/shared/build.gradle.kts index 8f1eca1..4a20671 100644 --- a/example/shared/build.gradle.kts +++ b/example/shared/build.gradle.kts @@ -27,6 +27,10 @@ kotlin { js(IR){ browser() } + + wasmJs(){ + browser() + } listOf( iosX64(), diff --git a/example/webApp/build.gradle.kts b/example/webApp/build.gradle.kts new file mode 100644 index 0000000..6f52f65 --- /dev/null +++ b/example/webApp/build.gradle.kts @@ -0,0 +1,46 @@ +plugins { + id("org.jetbrains.compose") + kotlin("multiplatform") +} + + +val copyJsResources = tasks.register("copyJsResourcesWorkaround") { + from(project(":example:shared").file("src/commonMain/composeResources")) + into("build/processedResources/js/main") +} + +val copyWasmJsResources = tasks.register("copyWasmJsResourcesWorkaround") { + from(project(":example:shared").file("src/commonMain/composeResources")) + into("build/processedResources/wasmJs/main") +} + +afterEvaluate { + tasks.named("jsProcessResources") { + finalizedBy(copyJsResources) + } + tasks.named("wasmJsProcessResources") { + finalizedBy(copyWasmJsResources) + } +} + +kotlin { + js(IR){ + browser() + binaries.executable() + } + + wasmJs { + browser() + binaries.executable() + } + + sourceSets { + commonMain.dependencies { + implementation(compose.ui) + implementation(compose.components.resources) + implementation(project(":example:shared")) + } + } +} + +compose.experimental.web.application{} diff --git a/example/webJsApp/src/jsMain/kotlin/io/github/alexzhirkevich/qrose/example/web/main.kt b/example/webApp/src/jsMain/kotlin/io/github/alexzhirkevich/qrose/example/web/main.kt similarity index 86% rename from example/webJsApp/src/jsMain/kotlin/io/github/alexzhirkevich/qrose/example/web/main.kt rename to example/webApp/src/jsMain/kotlin/io/github/alexzhirkevich/qrose/example/web/main.kt index 2ca5c36..c39b0b4 100644 --- a/example/webJsApp/src/jsMain/kotlin/io/github/alexzhirkevich/qrose/example/web/main.kt +++ b/example/webApp/src/jsMain/kotlin/io/github/alexzhirkevich/qrose/example/web/main.kt @@ -2,7 +2,7 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.window.CanvasBasedWindow -import com.github.alexzhirkevich.shared.App +import io.github.alexzhirkevich.shared.App import org.jetbrains.skiko.wasm.onWasmReady fun main(){ diff --git a/example/webJsApp/src/jsMain/resources/index.html b/example/webApp/src/jsMain/resources/index.html similarity index 85% rename from example/webJsApp/src/jsMain/resources/index.html rename to example/webApp/src/jsMain/resources/index.html index 8c82312..36cdb2f 100644 --- a/example/webJsApp/src/jsMain/resources/index.html +++ b/example/webApp/src/jsMain/resources/index.html @@ -7,6 +7,6 @@ - + \ No newline at end of file diff --git a/example/webApp/src/wasmJsMain/kotlin/io/github/alexzhirkevich/qrose/example/web/main.kt b/example/webApp/src/wasmJsMain/kotlin/io/github/alexzhirkevich/qrose/example/web/main.kt new file mode 100644 index 0000000..8490f03 --- /dev/null +++ b/example/webApp/src/wasmJsMain/kotlin/io/github/alexzhirkevich/qrose/example/web/main.kt @@ -0,0 +1,12 @@ +@file:OptIn(ExperimentalComposeUiApi::class) + +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.window.CanvasBasedWindow +import io.github.alexzhirkevich.shared.App + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + CanvasBasedWindow { + App() + } +} \ No newline at end of file diff --git a/example/webApp/src/wasmJsMain/resources/index.html b/example/webApp/src/wasmJsMain/resources/index.html new file mode 100644 index 0000000..36cdb2f --- /dev/null +++ b/example/webApp/src/wasmJsMain/resources/index.html @@ -0,0 +1,12 @@ + + + + QRose example + + + + + + + + \ No newline at end of file diff --git a/example/webJsApp/build.gradle.kts b/example/webJsApp/build.gradle.kts deleted file mode 100644 index 98b0018..0000000 --- a/example/webJsApp/build.gradle.kts +++ /dev/null @@ -1,32 +0,0 @@ -plugins { - id("org.jetbrains.compose") - kotlin("multiplatform") -} - - -val copyJsResources = tasks.create("copyJsResourcesWorkaround", Copy::class.java) { - from(project(":example:shared").file("src/commonMain/resources")) - into("build/processedResources/js/main") -} - -afterEvaluate { - project.tasks.getByName("jsProcessResources").finalizedBy(copyJsResources) -} - -kotlin { - js(IR){ - browser() - binaries.executable() - } - - sourceSets { - val jsMain by getting { - dependencies { - implementation(compose.ui) - implementation(project(":example:shared")) - } - } - } -} - -compose.experimental.web.application{} diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/qrcode/internals/QRData.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/qrcode/internals/QRData.kt index 1829b23..4593465 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/qrcode/internals/QRData.kt +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/qrcode/internals/QRData.kt @@ -53,9 +53,6 @@ internal abstract class QRData(val dataType: QRCodeDataType, val data: String) { * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ -@JsExport -@OptIn(ExperimentalJsExport::class) -@Suppress("NON_EXPORTABLE_TYPE") internal class QR8BitByte(data: String) : QRData(DEFAULT, data) { private val dataBytes = data.encodeToByteArray() @@ -75,9 +72,6 @@ internal class QR8BitByte(data: String) : QRData(DEFAULT, data) { * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ -@JsExport -@OptIn(ExperimentalJsExport::class) -@Suppress("NON_EXPORTABLE_TYPE") internal class QRAlphaNum(data: String) : QRData(UPPER_ALPHA_NUM, data) { override fun write(buffer: BitBuffer) { var i = 0 @@ -120,9 +114,6 @@ internal class QRAlphaNum(data: String) : QRData(UPPER_ALPHA_NUM, data) { * @author Rafael Lins - g0dkar * @author Kazuhiko Arase - kazuhikoarase */ -@JsExport -@OptIn(ExperimentalJsExport::class) -@Suppress("NON_EXPORTABLE_TYPE") internal class QRNumber(data: String) : QRData(NUMBERS, data) { override fun write(buffer: BitBuffer) { var i = 0 diff --git a/settings.gradle.kts b/settings.gradle.kts index 4157867..5c44080 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -21,6 +21,6 @@ rootProject.name = "QRose" include(":qrose") include(":example:desktopApp") -include(":example:webJsApp") +include(":example:webApp") include(":example:androidapp") include(":example:shared") From d37a650a8060452d63992d8f1a408f9fb2035c32 Mon Sep 17 00:00:00 2001 From: Zhirkevich Alexander Y Date: Thu, 29 Feb 2024 17:21:33 +0300 Subject: [PATCH 06/13] more barcode types, split to modules --- example/desktopApp/build.gradle.kts | 1 + .../qrose/example/desktop/main.kt | 12 +- example/shared/build.gradle.kts | 2 +- .../io/github/alexzhirkevich/shared/App.kt | 122 ++-- qrose-core/build.gradle.kts | 104 +++ .../alexzhirkevich/qrose/toByteArray.kt | 18 + .../alexzhirkevich/qrose/CachedPainter.kt | 135 ++++ .../github/alexzhirkevich/qrose/Converters.kt | 50 ++ .../github/alexzhirkevich/qrose/DrawCache.kt | 30 + .../alexzhirkevich/qrose/toByteArray.kt | 18 + qrose-oned/build.gradle.kts | 105 +++ .../alexzhirkevich/qrose/oned/Codabar.kt | 112 +++ .../qrose/oned/CodabarPainter.kt | 55 ++ .../alexzhirkevich/qrose/oned/Code128.kt | 647 ++++++++++++++++++ .../qrose/oned/Code128Painter.kt | 75 ++ .../alexzhirkevich/qrose/oned/Code39.kt | 121 ++++ .../qrose/oned/Code39Painter.kt | 57 ++ .../alexzhirkevich/qrose/oned/Code93.kt | 100 +++ .../qrose/oned/Code93Painter.kt | 59 ++ .../alexzhirkevich/qrose/oned/CodeEAN13.kt | 56 ++ .../alexzhirkevich/qrose/oned/CodeEAN8.kt | 58 ++ .../alexzhirkevich/qrose/oned/CodeITF.kt | 59 ++ .../alexzhirkevich/qrose/oned/CodeUPCA.kt | 5 + .../alexzhirkevich/qrose/oned/CodeUPCE.kt | 49 ++ .../alexzhirkevich/qrose/oned/EAN13Painter.kt | 55 ++ .../alexzhirkevich/qrose/oned/EAN8Painter.kt | 55 ++ .../alexzhirkevich/qrose/oned/ITFPainter.kt | 55 ++ .../qrose/oned/SingleDimensionBarcode.kt | 50 ++ .../alexzhirkevich/qrose/oned/UPCAPainter.kt | 55 ++ .../alexzhirkevich/qrose/oned/UPCEPainter.kt | 55 ++ .../alexzhirkevich/qrose/oned/UpcEanUtils.kt | 145 ++++ qrose/build.gradle.kts | 1 + .../alexzhirkevich/qrose/oned/Codabar.kt | 112 +++ .../qrose/oned/CodabarPainter.kt | 55 ++ .../alexzhirkevich/qrose/oned/CodeEAN13.kt | 28 +- .../alexzhirkevich/qrose/oned/CodeEAN8.kt | 115 +--- .../alexzhirkevich/qrose/oned/CodeITF.kt | 59 ++ .../alexzhirkevich/qrose/oned/CodeUPCA.kt | 5 + .../alexzhirkevich/qrose/oned/CodeUPCE.kt | 49 ++ .../alexzhirkevich/qrose/oned/EAN13Painter.kt | 2 +- .../alexzhirkevich/qrose/oned/ITFPainter.kt | 55 ++ .../alexzhirkevich/qrose/oned/UPCAPainter.kt | 55 ++ .../alexzhirkevich/qrose/oned/UPCEPainter.kt | 55 ++ .../alexzhirkevich/qrose/oned/UpcEanUtils.kt | 145 ++++ settings.gradle.kts | 2 + 45 files changed, 3057 insertions(+), 201 deletions(-) create mode 100644 qrose-core/build.gradle.kts create mode 100644 qrose-core/src/androidMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt create mode 100644 qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt create mode 100644 qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/Converters.kt create mode 100644 qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DrawCache.kt create mode 100644 qrose-core/src/skikoMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt create mode 100644 qrose-oned/build.gradle.kts create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt create mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt diff --git a/example/desktopApp/build.gradle.kts b/example/desktopApp/build.gradle.kts index 1d3723d..e3226be 100644 --- a/example/desktopApp/build.gradle.kts +++ b/example/desktopApp/build.gradle.kts @@ -21,6 +21,7 @@ kotlin { api(compose.material) api(compose.ui) api(compose.materialIconsExtended) + implementation("com.google.zxing:core:3.5.3") } } } diff --git a/example/desktopApp/src/jvmMain/kotlin/io/github/alexzhirkevich/qrose/example/desktop/main.kt b/example/desktopApp/src/jvmMain/kotlin/io/github/alexzhirkevich/qrose/example/desktop/main.kt index e6ee7d3..1b37469 100644 --- a/example/desktopApp/src/jvmMain/kotlin/io/github/alexzhirkevich/qrose/example/desktop/main.kt +++ b/example/desktopApp/src/jvmMain/kotlin/io/github/alexzhirkevich/qrose/example/desktop/main.kt @@ -1,23 +1,29 @@ package io.github.alexzhirkevich.qrose.example.desktop -//import androidx.compose.runtime.remember +import androidx.compose.material.Text import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState +import com.google.zxing.oned.UPCEWriter import io.github.alexzhirkevich.shared.App +import java.time.Instant +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle +import java.time.temporal.TemporalAccessor +import java.util.Date +import java.util.Locale fun main() { application { val windowState = rememberWindowState() - Window( onCloseRequest = ::exitApplication, title = "QRose example", state = windowState, ) { - App() } } diff --git a/example/shared/build.gradle.kts b/example/shared/build.gradle.kts index 4a20671..babe65a 100644 --- a/example/shared/build.gradle.kts +++ b/example/shared/build.gradle.kts @@ -46,7 +46,7 @@ kotlin { val commonMain by getting { dependencies { implementation(project(":qrose")) -// implementation("io.github.alexzhirkevich:qrose:1.0.0-beta02") + implementation(project(":qrose-oned")) implementation(compose.ui) implementation(compose.runtime) implementation(compose.material3) diff --git a/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt b/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt index c3e540c..69b37df 100644 --- a/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt +++ b/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt @@ -2,13 +2,24 @@ package io.github.alexzhirkevich.shared import androidx.compose.foundation.Image +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.DatePicker +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text import androidx.compose.material3.TextField +import androidx.compose.material3.rememberDatePickerState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -23,11 +34,15 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.dp import io.github.alexzhirkevich.qrose.QrData import io.github.alexzhirkevich.qrose.email +import io.github.alexzhirkevich.qrose.oned.rememberCodabarPainter import io.github.alexzhirkevich.qrose.oned.rememberCode128Painter import io.github.alexzhirkevich.qrose.oned.rememberCode39Painter import io.github.alexzhirkevich.qrose.oned.rememberCode93Painter import io.github.alexzhirkevich.qrose.oned.rememberEAN13Painter import io.github.alexzhirkevich.qrose.oned.rememberEAN8Painter +import io.github.alexzhirkevich.qrose.oned.rememberITFPainter +import io.github.alexzhirkevich.qrose.oned.rememberUPCAPainter +import io.github.alexzhirkevich.qrose.oned.rememberUPCEPainter import io.github.alexzhirkevich.qrose.options.DelicateQRoseApi import io.github.alexzhirkevich.qrose.options.QrBallShape import io.github.alexzhirkevich.qrose.options.QrBrush @@ -50,90 +65,53 @@ import qrose.example.shared.generated.resources.Res import qrose.example.shared.generated.resources.jc import qrose.example.shared.generated.resources.jcbg +@OptIn(ExperimentalMaterial3Api::class) @Composable fun App() { - QrCode() + AllBarcodes() } +@OptIn(ExperimentalLayoutApi::class) @Composable -fun CodeEAN13() { - val painter = rememberEAN13Painter( - data = "9780201379624" - ) - - Image( - painter = painter, - contentDescription = null, - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(.3f) - ) -} - - -@Composable -fun CodeEAN8(){ - val painter = rememberEAN8Painter( - data = "1234567" - ) - - Image( - painter = painter, - contentDescription = null, - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(.3f) - ) -} - -@Composable -fun Code39(){ - val painter = rememberCode39Painter( - data = "WIKIPEDIA" - ) - - Image( - painter = painter, - contentDescription = null, - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(.3f) - ) -} - -@Composable -fun Code93(){ - val painter = rememberCode93Painter( - data = "TEST123" - ) - - Image( - painter = painter, - contentDescription = null, - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(.3f) - .padding(30.dp) - ) +fun AllBarcodes() { + FlowRow(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + OnedCode("ITF", rememberITFPainter("123456734512")) + OnedCode("UPC E", rememberUPCEPainter("02345673")) + OnedCode("UPC A", rememberUPCAPainter("123456789012")) + OnedCode("EAN 13", rememberEAN13Painter("9780201379624")) + OnedCode("EAN 8", rememberEAN8Painter("1234567")) + OnedCode("Code 39", rememberCode39Painter("TEST")) + OnedCode("Code 93", rememberCode93Painter("TEST")) + OnedCode("Code 128", rememberCode128Painter("test")) + OnedCode("Codabar", rememberCodabarPainter("A23342453D")) + OnedCode("QR", rememberQrCodePainter("https://github.com/alexzhirkevich/qrose")) + } } - @Composable -fun Code128(){ - val painter = rememberCode128Painter( - data = "test", - brush = Brush.horizontalGradient(0f to Color.Red, 1f to Color.Blue) - ) - - Image( - painter = painter, - contentDescription = null, +fun OnedCode( + name : String, + code : Painter +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier - .size(350.dp) + .padding(50.dp) + .border(1.dp, Color.Black) .padding(10.dp) - ) + ) { + Image( + painter = code, + contentDescription = null, + modifier = Modifier + .width(300.dp) + .height(100.dp) + ) + Text(name) + } } + @OptIn(ExperimentalResourceApi::class) @Composable fun QrCode(){ diff --git a/qrose-core/build.gradle.kts b/qrose-core/build.gradle.kts new file mode 100644 index 0000000..49168ba --- /dev/null +++ b/qrose-core/build.gradle.kts @@ -0,0 +1,104 @@ +@file:Suppress("DSL_SCOPE_VIOLATION") + +plugins { + id("maven-publish") + id("signing") +} + + +kotlin { + sourceSets.commonMain.dependencies { + implementation(compose.ui) + } +} + +//ext["signing.keyId"] = null +//ext["signing.password"] = null +//ext["signing.secretKeyRingFile"] = null +//ext["ossrhUsername"] = null +//ext["ossrhPassword"] = null + +//extra.apply { +// val publishPropFile = rootProject.file("local.properties") +// if (publishPropFile.exists()) { +// Properties().apply { +// load(publishPropFile.inputStream()) +// }.forEach { name, value -> +// if (name == "signing.secretKeyRingFile") { +// set(name.toString(), rootProject.file(value.toString()).absolutePath) +// } else { +// set(name.toString(), value) +// } +// } +// } else { +// ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID") +// ext["signing.password"] = System.getenv("SIGNING_PASSWORD") +// ext["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE") +// ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME") +// ext["ossrhPassword"]= System.getenv("OSSRH_PASSWORD") +// } +//} +// +//val javadocJar by tasks.registering(Jar::class) { +// archiveClassifier.set("javadoc") +//} +//// https://github.com/gradle/gradle/issues/26091 +//val signingTasks = tasks.withType() +//tasks.withType().configureEach { +// dependsOn(signingTasks) +//} + +//publishing { +// if (rootProject.file("local.properties").exists()) { +// +// repositories { +// maven { +// val releasesRepoUrl = +// "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" +// val snapshotsRepoUrl = +// "https://s01.oss.sonatype.org/content/repositories/snapshots/" +// url = if (version.toString().endsWith("SNAPSHOT")) { +// uri(snapshotsRepoUrl) +// } else { +// uri(releasesRepoUrl) +// } +// credentials { +// username = project.ext.get("ossrhUsername").toString() +// password = project.ext.get("ossrhPassword").toString() +// } +// } +// } +// } +// +// publications.withType { +// artifact(javadocJar) +// pom { +// name.set("QRose") +// description.set("Styled QR code generation library for Compose Multiplatform") +// url.set("https://github.com/alexzhirkevich/qrose") +// +// licenses { +// license { +// name.set("MIT") +// url.set("https://opensource.org/licenses/MIT") +// } +// } +// developers { +// developer { +// id.set("alexzhirkevich") +// name.set("Alexander Zhirkevich") +// email.set("sasha.zhirkevich@gmail.com") +// } +// } +// scm { +// url.set("https://github.com/alexzhirkevich/qrose") +// connection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") +// developerConnection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") +// } +// } +// } +//} + +//signing { +// sign(publishing.publications) +//} \ No newline at end of file diff --git a/qrose-core/src/androidMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt b/qrose-core/src/androidMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt new file mode 100644 index 0000000..b199413 --- /dev/null +++ b/qrose-core/src/androidMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt @@ -0,0 +1,18 @@ +package io.github.alexzhirkevich.qrose + +import android.graphics.Bitmap +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asAndroidBitmap +import java.io.ByteArrayOutputStream + +actual typealias ImageFormat = Bitmap.CompressFormat + +actual fun ImageBitmap.toByteArray( + format: ImageFormat +): ByteArray { + + return ByteArrayOutputStream().use { + asAndroidBitmap().compress(format, 100, it) + it.toByteArray() + } +} \ No newline at end of file diff --git a/qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt b/qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt new file mode 100644 index 0000000..cad66a0 --- /dev/null +++ b/qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt @@ -0,0 +1,135 @@ +package io.github.alexzhirkevich.qrose + +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Canvas +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.drawscope.CanvasDrawScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.toSize +import kotlin.math.ceil + +abstract class CachedPainter : Painter() { + + private var alpha = 1f + private var colorFilter : ColorFilter?= null + + private var cachedSize: Size? = null + + private var cacheDrawScope = DrawCache() + + override fun applyAlpha(alpha: Float): Boolean { + this.alpha = alpha + return true + } + + override fun applyColorFilter(colorFilter: ColorFilter?): Boolean { + this.colorFilter = colorFilter + return true + } + + abstract fun DrawScope.onCache() + + private val block : DrawScope.() -> Unit = { onCache() } + + override fun DrawScope.onDraw() { + if (cachedSize != size) { + + cacheDrawScope.drawCachedImage( + size = IntSize(ceil(size.width).toInt(), ceil(size.height).toInt()), + density = this, + layoutDirection = layoutDirection, + block = block + ) + cachedSize = size + } + cacheDrawScope.drawInto( + target = this, + alpha = alpha, + colorFilter = colorFilter + ) + } +} + +/** + * Creates a drawing environment that directs its drawing commands to an [ImageBitmap] + * which can be drawn directly in another [DrawScope] instance. This is useful to cache + * complicated drawing commands across frames especially if the content has not changed. + * Additionally some drawing operations such as rendering paths are done purely in + * software so it is beneficial to cache the result and render the contents + * directly through a texture as done by [DrawScope.drawImage] + */ +private class DrawCache { + + @PublishedApi internal var mCachedImage: ImageBitmap? = null + private var cachedCanvas: Canvas? = null + private var scopeDensity: Density? = null + private var layoutDirection: LayoutDirection = LayoutDirection.Ltr + private var size: IntSize = IntSize.Zero + + private val cacheScope = CanvasDrawScope() + + /** + * Draw the contents of the lambda with receiver scope into an [ImageBitmap] with the provided + * size. If the same size is provided across calls, the same [ImageBitmap] instance is + * re-used and the contents are cleared out before drawing content in it again + */ + fun drawCachedImage( + size: IntSize, + density: Density, + layoutDirection: LayoutDirection, + block: DrawScope.() -> Unit + ) { + this.scopeDensity = density + this.layoutDirection = layoutDirection + var targetImage = mCachedImage + var targetCanvas = cachedCanvas + if (targetImage == null || + targetCanvas == null || + size.width > targetImage.width || + size.height > targetImage.height + ) { + targetImage = ImageBitmap(size.width, size.height) + targetCanvas = Canvas(targetImage) + + mCachedImage = targetImage + cachedCanvas = targetCanvas + } + this.size = size + cacheScope.draw(density, layoutDirection, targetCanvas, size.toSize()) { + clear() + block() + } + targetImage.prepareToDraw() + } + + /** + * Draw the cached content into the provided [DrawScope] instance + */ + fun drawInto( + target: DrawScope, + alpha: Float = 1.0f, + colorFilter: ColorFilter? = null + ) { + val targetImage = mCachedImage + check(targetImage != null) { + "drawCachedImage must be invoked first before attempting to draw the result " + + "into another destination" + } + target.drawImage(targetImage, srcSize = size, alpha = alpha, colorFilter = colorFilter) + } + + /** + * Helper method to clear contents of the draw environment from the given bounds of the + * DrawScope + */ + private fun DrawScope.clear() { + drawRect(color = Color.Black, blendMode = BlendMode.Clear) + } +} \ No newline at end of file diff --git a/qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/Converters.kt b/qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/Converters.kt new file mode 100644 index 0000000..9afc7b1 --- /dev/null +++ b/qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/Converters.kt @@ -0,0 +1,50 @@ +package io.github.alexzhirkevich.qrose + +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Canvas +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.drawscope.CanvasDrawScope +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection + +expect enum class ImageFormat { + PNG, JPEG, WEBP +} + +/** + * Converts [ImageBitmap] to image with desired [format] and returns its bytes. + * */ +expect fun ImageBitmap.toByteArray(format: ImageFormat = ImageFormat.PNG) : ByteArray + +/** + * Converts [Painter] to image with desired [width], [height] and [format] and returns its bytes. + * */ +fun Painter.toByteArray(width : Int, height: Int, format : ImageFormat = ImageFormat.PNG) : ByteArray = + toImageBitmap(width, height).toByteArray(format) + +/** + * Converts [Painter] to [ImageBitmap] with desired [width], [height], [alpha] and [colorFilter] + * */ +fun Painter.toImageBitmap( + width : Int, + height : Int, + alpha : Float = 1f, + colorFilter: ColorFilter? = null +) : ImageBitmap { + + val bmp = ImageBitmap(width, height) + val canvas = Canvas(bmp) + + CanvasDrawScope().draw( + density = Density(1f, 1f), + layoutDirection = LayoutDirection.Ltr, + canvas = canvas, + size = Size(width.toFloat(), height.toFloat()) + ) { + draw(this@draw.size, alpha, colorFilter) + } + + return bmp +} \ No newline at end of file diff --git a/qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DrawCache.kt b/qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DrawCache.kt new file mode 100644 index 0000000..7a76bd5 --- /dev/null +++ b/qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DrawCache.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.alexzhirkevich.qrose + +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Canvas +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.drawscope.CanvasDrawScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.toSize + diff --git a/qrose-core/src/skikoMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt b/qrose-core/src/skikoMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt new file mode 100644 index 0000000..1b7b4ae --- /dev/null +++ b/qrose-core/src/skikoMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt @@ -0,0 +1,18 @@ +package io.github.alexzhirkevich.qrose + +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asSkiaBitmap +import org.jetbrains.skia.EncodedImageFormat +import org.jetbrains.skia.Image + +actual typealias ImageFormat = EncodedImageFormat + +actual fun ImageBitmap.toByteArray( + format: ImageFormat +): ByteArray { + val data = Image + .makeFromBitmap(asSkiaBitmap()) + .encodeToData(format) ?: error("This painter cannot be encoded to $format") + + return data.bytes +} \ No newline at end of file diff --git a/qrose-oned/build.gradle.kts b/qrose-oned/build.gradle.kts new file mode 100644 index 0000000..a4cc293 --- /dev/null +++ b/qrose-oned/build.gradle.kts @@ -0,0 +1,105 @@ +@file:Suppress("DSL_SCOPE_VIOLATION") + +plugins { + id("maven-publish") + id("signing") +} + + +kotlin { + sourceSets.commonMain.dependencies { + implementation(compose.ui) + api(project(":qrose-core")) + } +} + +//ext["signing.keyId"] = null +//ext["signing.password"] = null +//ext["signing.secretKeyRingFile"] = null +//ext["ossrhUsername"] = null +//ext["ossrhPassword"] = null + +//extra.apply { +// val publishPropFile = rootProject.file("local.properties") +// if (publishPropFile.exists()) { +// Properties().apply { +// load(publishPropFile.inputStream()) +// }.forEach { name, value -> +// if (name == "signing.secretKeyRingFile") { +// set(name.toString(), rootProject.file(value.toString()).absolutePath) +// } else { +// set(name.toString(), value) +// } +// } +// } else { +// ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID") +// ext["signing.password"] = System.getenv("SIGNING_PASSWORD") +// ext["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE") +// ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME") +// ext["ossrhPassword"]= System.getenv("OSSRH_PASSWORD") +// } +//} +// +//val javadocJar by tasks.registering(Jar::class) { +// archiveClassifier.set("javadoc") +//} +//// https://github.com/gradle/gradle/issues/26091 +//val signingTasks = tasks.withType() +//tasks.withType().configureEach { +// dependsOn(signingTasks) +//} + +//publishing { +// if (rootProject.file("local.properties").exists()) { +// +// repositories { +// maven { +// val releasesRepoUrl = +// "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" +// val snapshotsRepoUrl = +// "https://s01.oss.sonatype.org/content/repositories/snapshots/" +// url = if (version.toString().endsWith("SNAPSHOT")) { +// uri(snapshotsRepoUrl) +// } else { +// uri(releasesRepoUrl) +// } +// credentials { +// username = project.ext.get("ossrhUsername").toString() +// password = project.ext.get("ossrhPassword").toString() +// } +// } +// } +// } +// +// publications.withType { +// artifact(javadocJar) +// pom { +// name.set("QRose") +// description.set("Styled QR code generation library for Compose Multiplatform") +// url.set("https://github.com/alexzhirkevich/qrose") +// +// licenses { +// license { +// name.set("MIT") +// url.set("https://opensource.org/licenses/MIT") +// } +// } +// developers { +// developer { +// id.set("alexzhirkevich") +// name.set("Alexander Zhirkevich") +// email.set("sasha.zhirkevich@gmail.com") +// } +// } +// scm { +// url.set("https://github.com/alexzhirkevich/qrose") +// connection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") +// developerConnection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") +// } +// } +// } +//} + +//signing { +// sign(publishing.publications) +//} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt new file mode 100644 index 0000000..606d455 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt @@ -0,0 +1,112 @@ +package io.github.alexzhirkevich.qrose.oned + +internal class Codabar { + + fun encode(contents: String): BooleanArray { + var actualContents = contents + if (actualContents.length < 2) { + // Can't have a start/end guard, so tentatively add default guards + actualContents = DEFAULT_GUARD.toString() + actualContents + DEFAULT_GUARD + } else { + // Verify input and calculate decoded length. + val firstChar = actualContents[0].uppercaseChar() + val lastChar = actualContents[actualContents.length - 1].uppercaseChar() + val startsNormal: Boolean = firstChar in START_END_CHARS + val endsNormal: Boolean = lastChar in START_END_CHARS + val startsAlt: Boolean = firstChar in ALT_START_END_CHARS + val endsAlt: Boolean = lastChar in ALT_START_END_CHARS + if (startsNormal) { + if (!endsNormal) { + throw IllegalArgumentException("Invalid start/end guards: $actualContents") + } + // else already has valid start/end + } else if (startsAlt) { + if (!endsAlt) { + throw IllegalArgumentException("Invalid start/end guards: $actualContents") + } + // else already has valid start/end + } else { + // Doesn't start with a guard + if (endsNormal || endsAlt) { + throw IllegalArgumentException("Invalid start/end guards: $actualContents") + } + // else doesn't end with guard either, so add a default + actualContents = DEFAULT_GUARD.toString() + actualContents + DEFAULT_GUARD + } + } + + // The start character and the end character are decoded to 10 length each. + var resultLength = 20 + for (i in 1 until actualContents.length - 1) { + resultLength += if (actualContents[i].isDigit() || actualContents[i] == '-' || actualContents[i] == '$') { + 9 + } else if (actualContents[i] in CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED) { + 10 + } else { + throw IllegalArgumentException("Cannot encode : '" + actualContents[i] + '\'') + } + } + // A blank is placed between each character. + resultLength += actualContents.length - 1 + val result = BooleanArray(resultLength) + var position = 0 + for (index in actualContents.indices) { + var c = actualContents[index].uppercaseChar() + if (index == 0 || index == actualContents.length - 1) { + // The start/end chars are not in the CodaBarReader.ALPHABET. + when (c) { + 'T' -> c = 'A' + 'N' -> c = 'B' + '*' -> c = 'C' + 'E' -> c = 'D' + } + } + var code = 0 + for (i in ALPHABET.indices) { + // Found any, because I checked above. + if (c == ALPHABET[i]) { + code = CHARACTER_ENCODINGS[i] + break + } + } + var color = true + var counter = 0 + var bit = 0 + while (bit < 7) { // A character consists of 7 digit. + result[position] = color + position++ + if (code shr 6 - bit and 1 == 0 || counter == 1) { + color = !color // Flip the color. + bit++ + counter = 0 + } else { + counter++ + } + } + if (index < actualContents.length - 1) { + result[position] = false + position++ + } + } + return result + } + + companion object { + private val START_END_CHARS = charArrayOf('A', 'B', 'C', 'D') + private val ALT_START_END_CHARS = charArrayOf('T', 'N', '*', 'E') + private val CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED = charArrayOf('/', ':', '+', '.') + private val DEFAULT_GUARD = START_END_CHARS[0] + + private const val ALPHABET_STRING = "0123456789-$:/.+ABCD" + val ALPHABET = ALPHABET_STRING.toCharArray() + + /** + * These represent the encodings of characters, as patterns of wide and narrow bars. The 7 least-significant bits of + * each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow. + */ + val CHARACTER_ENCODINGS = intArrayOf( + 0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9 + 0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E + ) + } +} diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt new file mode 100644 index 0000000..7a8404d --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt @@ -0,0 +1,55 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * Codabar barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberCodabarPainter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + CodabarPainter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +class CodabarPainter( + val data : String, + val brush: Brush = SolidColor(Color.Black), + density : Float = 1f, + builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, +) : SingleDimensionBarcodePainter( + density = density, + code = Codabar().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt new file mode 100644 index 0000000..388ffc0 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt @@ -0,0 +1,647 @@ +package io.github.alexzhirkevich.qrose.oned + +internal class Code128 { + // Results of minimal lookahead for code C + private enum class CType { + UNCODABLE, + ONE_DIGIT, + TWO_DIGITS, + FNC_1 + } + + + fun encode(contents: String, compact : Boolean = true , codeSet : Code128Painter.CodeSet? = null): BooleanArray { + val forcedCodeSet = check(contents, codeSet) + + return if (compact) { + MinimalEncoder().encode(contents) + } else { + encodeFast( + contents, + forcedCodeSet + ) + } + } + + /** + * Encodes minimally using Divide-And-Conquer with Memoization + */ + private class MinimalEncoder { + private enum class Charset { + A, + B, + C, + NONE + } + + private enum class Latch { + A, + B, + C, + SHIFT, + NONE + } + + private var memoizedCost: Array? = null + private var minPath: Array>? = null + + fun encode(contents: String): BooleanArray { + memoizedCost = Array(4) { IntArray(contents.length) } + minPath = Array(4) { + arrayOfNulls( + contents.length + ) + } + encode(contents, Charset.NONE, 0) + val patterns: MutableCollection = ArrayList() + val checkSum = intArrayOf(0) + val checkWeight = intArrayOf(1) + val length = contents.length + var charset = Charset.NONE + var i = 0 + while (i < length) { + val latch = minPath!![charset.ordinal][i] + when (latch) { + Latch.A -> { + charset = Charset.A + addPattern( + patterns, + if (i == 0) CODE_START_A else CODE_CODE_A, + checkSum, + checkWeight, + i + ) + } + + Latch.B -> { + charset = Charset.B + addPattern( + patterns, + if (i == 0) CODE_START_B else CODE_CODE_B, + checkSum, + checkWeight, + i + ) + } + + Latch.C -> { + charset = Charset.C + addPattern( + patterns, + if (i == 0) CODE_START_C else CODE_CODE_C, + checkSum, + checkWeight, + i + ) + } + + Latch.SHIFT -> addPattern(patterns, CODE_SHIFT, checkSum, checkWeight, i) + else -> {} + } + if (charset == Charset.C) { + if (contents[i] == ESCAPE_FNC_1) { + addPattern(patterns, CODE_FNC_1, checkSum, checkWeight, i) + } else { + addPattern( + patterns, + contents.substring(i, i + 2).toInt(), + checkSum, + checkWeight, + i + ) + require(i + 1 < length) //the algorithm never leads to a single trailing digit in character set C + + if (i + 1 < length) { + i++ + } + } + } else { // charset A or B + var patternIndex: Int + patternIndex = + when (contents[i]) { + ESCAPE_FNC_1 -> CODE_FNC_1 + ESCAPE_FNC_2 -> CODE_FNC_2 + ESCAPE_FNC_3 -> CODE_FNC_3 + ESCAPE_FNC_4 -> if (charset == Charset.A && latch != Latch.SHIFT || + charset == Charset.B && latch == Latch.SHIFT + ) { + CODE_FNC_4_A + } else { + CODE_FNC_4_B + } + + else -> contents[i].code - ' '.code + } + if ((charset == Charset.A && latch != Latch.SHIFT || + charset == Charset.B && latch == Latch.SHIFT) && + patternIndex < 0 + ) { + patternIndex += '`'.code + } + addPattern(patterns, patternIndex, checkSum, checkWeight, i) + } + i++ + } + memoizedCost = null + minPath = null + return produceResult(patterns, checkSum[0]) + } + + private fun canEncode(contents: CharSequence, charset: Charset, position: Int): Boolean { + val c = contents[position] + return when (charset) { + Charset.A -> c == ESCAPE_FNC_1 || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4 || A.indexOf( + c + ) >= 0 + + Charset.B -> c == ESCAPE_FNC_1 || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4 || B.indexOf( + c + ) >= 0 + + Charset.C -> c == ESCAPE_FNC_1 || position + 1 < contents.length && + isDigit(c) && isDigit(contents[position + 1]) + + else -> false + } + } + + /** + * Encode the string starting at position position starting with the character set charset + */ + private fun encode(contents: CharSequence, charset: Charset, position: Int): Int { + require(position < contents.length) + val mCost = memoizedCost!![charset.ordinal][position] + if (mCost > 0) { + return mCost + } + var minCost = Int.MAX_VALUE + var minLatch: Latch? = Latch.NONE + val atEnd = position + 1 >= contents.length + val sets = arrayOf(Charset.A, Charset.B) + for (i in 0..1) { + if (canEncode(contents, sets[i], position)) { + var cost = 1 + var latch = Latch.NONE + if (charset != sets[i]) { + cost++ + latch = Latch.valueOf( + sets[i].toString() + ) + } + if (!atEnd) { + cost += encode(contents, sets[i], position + 1) + } + if (cost < minCost) { + minCost = cost + minLatch = latch + } + cost = 1 + if (charset == sets[(i + 1) % 2]) { + cost++ + latch = Latch.SHIFT + if (!atEnd) { + cost += encode(contents, charset, position + 1) + } + if (cost < minCost) { + minCost = cost + minLatch = latch + } + } + } + } + if (canEncode(contents, Charset.C, position)) { + var cost = 1 + var latch = Latch.NONE + if (charset != Charset.C) { + cost++ + latch = Latch.C + } + val advance = if (contents[position] == ESCAPE_FNC_1) 1 else 2 + if (position + advance < contents.length) { + cost += encode(contents, Charset.C, position + advance) + } + if (cost < minCost) { + minCost = cost + minLatch = latch + } + } + if (minCost == Int.MAX_VALUE) { + throw IllegalArgumentException("Bad character in input: ASCII value=" + contents[position].code) + } + memoizedCost!![charset.ordinal][position] = minCost + minPath!![charset.ordinal][position] = minLatch + return minCost + } + + companion object { + const val A = + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u0000\u0001\u0002" + + "\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000B\u000C\r\u000E\u000F\u0010\u0011" + + "\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F" + + "\u00FF" + const val B = + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr" + + "stuvwxyz{|}~\u007F\u00FF" + private const val CODE_SHIFT = 98 + + private fun addPattern( + patterns: MutableCollection, + patternIndex: Int, + checkSum: IntArray, + checkWeight: IntArray, + position: Int, + ) { + patterns.add(CODE_PATTERNS.get(patternIndex)) + if (position != 0) { + checkWeight[0]++ + } + checkSum[0] += patternIndex * checkWeight[0] + } + + private fun isDigit(c: Char): Boolean { + return c in '0'..'9' + } + } + } + + + companion object { + + private const val CODE_START_A = 103 + private const val CODE_START_B = 104 + private const val CODE_START_C = 105 + internal const val CODE_CODE_A = 101 + internal const val CODE_CODE_B = 100 + internal const val CODE_CODE_C = 99 + private const val CODE_STOP = 106 + + // Dummy characters used to specify control characters in input + private const val ESCAPE_FNC_1 = '\u00f1' + private const val ESCAPE_FNC_2 = '\u00f2' + private const val ESCAPE_FNC_3 = '\u00f3' + private const val ESCAPE_FNC_4 = '\u00f4' + private const val CODE_FNC_1 = 102 // Code A, Code B, Code C + private const val CODE_FNC_2 = 97 // Code A, Code B + private const val CODE_FNC_3 = 96 // Code A, Code B + private const val CODE_FNC_4_A = 101 // Code A + private const val CODE_FNC_4_B = 100 // Code B + + private fun check(contents: String, codeSet : Code128Painter.CodeSet?): Int { + + // Check content + val length = contents.length + for (i in 0 until length) { + val c = contents[i] + when (c) { + ESCAPE_FNC_1, ESCAPE_FNC_2, ESCAPE_FNC_3, ESCAPE_FNC_4 -> {} + else -> if (c.code > 127) { + // no full Latin-1 character set available at the moment + // shift and manual code change are not supported + throw IllegalArgumentException("Bad character in input: ASCII value=" + c.code) + } + } + when (codeSet) { + Code128Painter.CodeSet.A -> // allows no ascii above 95 (no lower caps, no special symbols) + if (c.code in 96..127) { + throw IllegalArgumentException("Bad character in input for forced code set A: ASCII value=" + c.code) + } + + Code128Painter.CodeSet.B -> // allows no ascii below 32 (terminal symbols) + if (c.code < 32) { + throw IllegalArgumentException("Bad character in input for forced code set B: ASCII value=" + c.code) + } + + Code128Painter.CodeSet.C -> // allows only numbers and no FNC 2/3/4 + if (c.code < 48 || c.code in 58..127 || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4) { + throw IllegalArgumentException("Bad character in input for forced code set C: ASCII value=" + c.code) + } + + else -> {} + } + } + return codeSet?.v ?: -1 + } + + private fun encodeFast(contents: String, forcedCodeSet: Int): BooleanArray { + val length = contents.length + val patterns: MutableCollection = ArrayList() // temporary storage for patterns + var checkSum = 0 + var checkWeight = 1 + var codeSet = 0 // selected code (CODE_CODE_B or CODE_CODE_C) + var position = 0 // position in contents + while (position < length) { + //Select code to use + var newCodeSet: Int + newCodeSet = if (forcedCodeSet == -1) { + chooseCode(contents, position, codeSet) + } else { + forcedCodeSet + } + + //Get the pattern index + var patternIndex: Int + if (newCodeSet == codeSet) { + // Encode the current character + // First handle escapes + when (contents[position]) { + ESCAPE_FNC_1 -> patternIndex = CODE_FNC_1 + ESCAPE_FNC_2 -> patternIndex = CODE_FNC_2 + ESCAPE_FNC_3 -> patternIndex = CODE_FNC_3 + ESCAPE_FNC_4 -> patternIndex = if (codeSet == CODE_CODE_A) { + CODE_FNC_4_A + } else { + CODE_FNC_4_B + } + + else -> when (codeSet) { + CODE_CODE_A -> { + patternIndex = contents[position].code - ' '.code + if (patternIndex < 0) { + // everything below a space character comes behind the underscore in the code patterns table + patternIndex += '`'.code + } + } + + CODE_CODE_B -> patternIndex = contents[position].code - ' '.code + else -> { + // CODE_CODE_C + if (position + 1 == length) { + // this is the last character, but the encoding is C, which always encodes two characers + throw IllegalArgumentException("Bad number of characters for digit only encoding.") + } + patternIndex = contents.substring(position, position + 2).toInt() + position++ // Also incremented below + } + } + } + position++ + } else { + // Should we change the current code? + // Do we have a code set? + patternIndex = if (codeSet == 0) { + // No, we don't have a code set + when (newCodeSet) { + CODE_CODE_A -> CODE_START_A + CODE_CODE_B -> CODE_START_B + else -> CODE_START_C + } + } else { + // Yes, we have a code set + newCodeSet + } + codeSet = newCodeSet + } + + // Get the pattern + patterns.add(CODE_PATTERNS[patternIndex]) + + // Compute checksum + checkSum += patternIndex * checkWeight + if (position != 0) { + checkWeight++ + } + } + return produceResult(patterns, checkSum) + } + + fun produceResult(patterns: MutableCollection, checkSum: Int): BooleanArray { + // Compute and append checksum + val checkSumMod = checkSum % 103 + if (checkSumMod < 0) { + throw IllegalArgumentException("Unable to compute a valid input checksum") + } + patterns.add(CODE_PATTERNS[checkSumMod]) + + // Append stop code + patterns.add(CODE_PATTERNS[CODE_STOP]) + + // Compute code width + var codeWidth = 0 + for (pattern in patterns) { + for (width in pattern) { + codeWidth += width + } + } + + // Compute result + val result = BooleanArray(codeWidth) + var pos = 0 + for (pattern in patterns) { + pos += appendPattern(result, pos, pattern, true) + } + return result + } + + private fun findCType(value: CharSequence, start: Int): CType { + val last = value.length + if (start >= last) { + return CType.UNCODABLE + } + var c = value[start] + if (c == ESCAPE_FNC_1) { + return CType.FNC_1 + } + if (c < '0' || c > '9') { + return CType.UNCODABLE + } + if (start + 1 >= last) { + return CType.ONE_DIGIT + } + c = value[start + 1] + return if (c < '0' || c > '9') { + CType.ONE_DIGIT + } else CType.TWO_DIGITS + } + + private fun chooseCode(value: CharSequence, start: Int, oldCode: Int): Int { + var lookahead = findCType(value, start) + if (lookahead == CType.ONE_DIGIT) { + return if (oldCode == CODE_CODE_A) { + CODE_CODE_A + } else CODE_CODE_B + } + if (lookahead == CType.UNCODABLE) { + if (start < value.length) { + val c = value[start] + if (c < ' ' || oldCode == CODE_CODE_A && (c < '`' || c >= ESCAPE_FNC_1 && c <= ESCAPE_FNC_4)) { + // can continue in code A, encodes ASCII 0 to 95 or FNC1 to FNC4 + return CODE_CODE_A + } + } + return CODE_CODE_B // no choice + } + if (oldCode == CODE_CODE_A && lookahead == CType.FNC_1) { + return CODE_CODE_A + } + if (oldCode == CODE_CODE_C) { // can continue in code C + return CODE_CODE_C + } + if (oldCode == CODE_CODE_B) { + if (lookahead == CType.FNC_1) { + return CODE_CODE_B // can continue in code B + } + // Seen two consecutive digits, see what follows + lookahead = findCType(value, start + 2) + if (lookahead == CType.UNCODABLE || lookahead == CType.ONE_DIGIT) { + return CODE_CODE_B // not worth switching now + } + if (lookahead == CType.FNC_1) { // two digits, then FNC_1... + lookahead = findCType(value, start + 3) + return if (lookahead == CType.TWO_DIGITS) { // then two more digits, switch + CODE_CODE_C + } else { + CODE_CODE_B // otherwise not worth switching + } + } + // At this point, there are at least 4 consecutive digits. + // Look ahead to choose whether to switch now or on the next round. + var index = start + 4 + while (findCType(value, index).also { lookahead = it } == CType.TWO_DIGITS) { + index += 2 + } + return if (lookahead == CType.ONE_DIGIT) { // odd number of digits, switch later + CODE_CODE_B + } else CODE_CODE_C + // even number of digits, switch now + } + // Here oldCode == 0, which means we are choosing the initial code + if (lookahead == CType.FNC_1) { // ignore FNC_1 + lookahead = findCType(value, start + 1) + } + return if (lookahead == CType.TWO_DIGITS) { // at least two digits, start in code C + CODE_CODE_C + } else CODE_CODE_B + } + } +} + +internal fun appendPattern( + target: BooleanArray, + pos: Int, + pattern: IntArray, + startColor: Boolean, +): Int { + var pos = pos + var color = startColor + var numAdded = 0 + for (len in pattern) { + for (j in 0 until len) { + target[pos++] = color + } + numAdded += len + color = !color // flip color after each segment + } + return numAdded +} + +private val CODE_PATTERNS by lazy { + arrayOf( + intArrayOf(2, 1, 2, 2, 2, 2), + intArrayOf(2, 2, 2, 1, 2, 2), + intArrayOf(2, 2, 2, 2, 2, 1), + intArrayOf(1, 2, 1, 2, 2, 3), + intArrayOf(1, 2, 1, 3, 2, 2), + intArrayOf(1, 3, 1, 2, 2, 2), + intArrayOf(1, 2, 2, 2, 1, 3), + intArrayOf(1, 2, 2, 3, 1, 2), + intArrayOf(1, 3, 2, 2, 1, 2), + intArrayOf(2, 2, 1, 2, 1, 3), + intArrayOf(2, 2, 1, 3, 1, 2), + intArrayOf(2, 3, 1, 2, 1, 2), + intArrayOf(1, 1, 2, 2, 3, 2), + intArrayOf(1, 2, 2, 1, 3, 2), + intArrayOf(1, 2, 2, 2, 3, 1), + intArrayOf(1, 1, 3, 2, 2, 2), + intArrayOf(1, 2, 3, 1, 2, 2), + intArrayOf(1, 2, 3, 2, 2, 1), + intArrayOf(2, 2, 3, 2, 1, 1), + intArrayOf(2, 2, 1, 1, 3, 2), + intArrayOf(2, 2, 1, 2, 3, 1), + intArrayOf(2, 1, 3, 2, 1, 2), + intArrayOf(2, 2, 3, 1, 1, 2), + intArrayOf(3, 1, 2, 1, 3, 1), + intArrayOf(3, 1, 1, 2, 2, 2), + intArrayOf(3, 2, 1, 1, 2, 2), + intArrayOf(3, 2, 1, 2, 2, 1), + intArrayOf(3, 1, 2, 2, 1, 2), + intArrayOf(3, 2, 2, 1, 1, 2), + intArrayOf(3, 2, 2, 2, 1, 1), + intArrayOf(2, 1, 2, 1, 2, 3), + intArrayOf(2, 1, 2, 3, 2, 1), + intArrayOf(2, 3, 2, 1, 2, 1), + intArrayOf(1, 1, 1, 3, 2, 3), + intArrayOf(1, 3, 1, 1, 2, 3), + intArrayOf(1, 3, 1, 3, 2, 1), + intArrayOf(1, 1, 2, 3, 1, 3), + intArrayOf(1, 3, 2, 1, 1, 3), + intArrayOf(1, 3, 2, 3, 1, 1), + intArrayOf(2, 1, 1, 3, 1, 3), + intArrayOf(2, 3, 1, 1, 1, 3), + intArrayOf(2, 3, 1, 3, 1, 1), + intArrayOf(1, 1, 2, 1, 3, 3), + intArrayOf(1, 1, 2, 3, 3, 1), + intArrayOf(1, 3, 2, 1, 3, 1), + intArrayOf(1, 1, 3, 1, 2, 3), + intArrayOf(1, 1, 3, 3, 2, 1), + intArrayOf(1, 3, 3, 1, 2, 1), + intArrayOf(3, 1, 3, 1, 2, 1), + intArrayOf(2, 1, 1, 3, 3, 1), + intArrayOf(2, 3, 1, 1, 3, 1), + intArrayOf(2, 1, 3, 1, 1, 3), + intArrayOf(2, 1, 3, 3, 1, 1), + intArrayOf(2, 1, 3, 1, 3, 1), + intArrayOf(3, 1, 1, 1, 2, 3), + intArrayOf(3, 1, 1, 3, 2, 1), + intArrayOf(3, 3, 1, 1, 2, 1), + intArrayOf(3, 1, 2, 1, 1, 3), + intArrayOf(3, 1, 2, 3, 1, 1), + intArrayOf(3, 3, 2, 1, 1, 1), + intArrayOf(3, 1, 4, 1, 1, 1), + intArrayOf(2, 2, 1, 4, 1, 1), + intArrayOf(4, 3, 1, 1, 1, 1), + intArrayOf(1, 1, 1, 2, 2, 4), + intArrayOf(1, 1, 1, 4, 2, 2), + intArrayOf(1, 2, 1, 1, 2, 4), + intArrayOf(1, 2, 1, 4, 2, 1), + intArrayOf(1, 4, 1, 1, 2, 2), + intArrayOf(1, 4, 1, 2, 2, 1), + intArrayOf(1, 1, 2, 2, 1, 4), + intArrayOf(1, 1, 2, 4, 1, 2), + intArrayOf(1, 2, 2, 1, 1, 4), + intArrayOf(1, 2, 2, 4, 1, 1), + intArrayOf(1, 4, 2, 1, 1, 2), + intArrayOf(1, 4, 2, 2, 1, 1), + intArrayOf(2, 4, 1, 2, 1, 1), + intArrayOf(2, 2, 1, 1, 1, 4), + intArrayOf(4, 1, 3, 1, 1, 1), + intArrayOf(2, 4, 1, 1, 1, 2), + intArrayOf(1, 3, 4, 1, 1, 1), + intArrayOf(1, 1, 1, 2, 4, 2), + intArrayOf(1, 2, 1, 1, 4, 2), + intArrayOf(1, 2, 1, 2, 4, 1), + intArrayOf(1, 1, 4, 2, 1, 2), + intArrayOf(1, 2, 4, 1, 1, 2), + intArrayOf(1, 2, 4, 2, 1, 1), + intArrayOf(4, 1, 1, 2, 1, 2), + intArrayOf(4, 2, 1, 1, 1, 2), + intArrayOf(4, 2, 1, 2, 1, 1), + intArrayOf(2, 1, 2, 1, 4, 1), + intArrayOf(2, 1, 4, 1, 2, 1), + intArrayOf(4, 1, 2, 1, 2, 1), + intArrayOf(1, 1, 1, 1, 4, 3), + intArrayOf(1, 1, 1, 3, 4, 1), + intArrayOf(1, 3, 1, 1, 4, 1), + intArrayOf(1, 1, 4, 1, 1, 3), + intArrayOf(1, 1, 4, 3, 1, 1), + intArrayOf(4, 1, 1, 1, 1, 3), + intArrayOf(4, 1, 1, 3, 1, 1), + intArrayOf(1, 1, 3, 1, 4, 1), + intArrayOf(1, 1, 4, 1, 3, 1), + intArrayOf(3, 1, 1, 1, 4, 1), + intArrayOf(4, 1, 1, 1, 3, 1), + intArrayOf(2, 1, 1, 4, 1, 2), + intArrayOf(2, 1, 1, 2, 1, 4), + intArrayOf(2, 1, 1, 2, 3, 2), + intArrayOf(2, 3, 3, 1, 1, 1, 2) + ) +} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt new file mode 100644 index 0000000..6342381 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt @@ -0,0 +1,75 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + + +/** + * Code 128 barcode painter + * + * @param brush code brush + * @param compact Specifies whether to use compact mode for Code-128 code. This can yield slightly smaller bar codes. This option and [forceCodeSet] are mutually exclusive. + * @param forceCodeSet Forces which encoding will be used. Currently only used for Code-128 code sets. This option and CODE128_COMPACT are mutually exclusive + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberCode128Painter( + data: String, + brush: Brush = SolidColor(Color.Black), + compact : Boolean = true, + forceCodeSet : Code128Painter.CodeSet? = null, + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush, forceCodeSet) { + runCatching { + Code128Painter( + data = data, + density = density.density, + brush = brush, + compact = compact, + codeSet = forceCodeSet, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + + +@Immutable +class Code128Painter( + val data : String, + val brush: Brush = SolidColor(Color.Black), + val compact : Boolean = true, + val codeSet : CodeSet? = null, + density: Float, + builder : BarcodePathBuilder= ::defaultOneDBarcodeBuilder +) : SingleDimensionBarcodePainter( + density = density, + code = Code128().encode(data, compact, codeSet).toList(), + brush = brush, + builder = builder +) { + + enum class CodeSet(internal val v: Int) { + A(Code128.CODE_CODE_A), + B(Code128.CODE_CODE_B), + C(Code128.CODE_CODE_C) + } +} diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt new file mode 100644 index 0000000..71b55d9 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt @@ -0,0 +1,121 @@ +package io.github.alexzhirkevich.qrose.oned + +internal class Code39 { + + fun encode(contents: String): BooleanArray { + var actualContent = contents + var length = actualContent.length + if (length > 80) { + throw IllegalArgumentException( + "Requested contents should be less than 80 digits long, but got $length" + ) + } + for (i in 0 until length) { + val indexInString: Int = ALPHABET_STRING.indexOf(actualContent[i]) + if (indexInString < 0) { + actualContent = tryToConvertToExtendedMode(actualContent) + length = actualContent.length + if (length > 80) { + throw IllegalArgumentException( + "Requested contents should be less than 80 digits long, but got " + + length + " (extended full ASCII mode)" + ) + } + break + } + } + val widths = IntArray(9) + val codeWidth = 24 + 1 + (13 * length) + val result = BooleanArray(codeWidth) + toIntArray(ASTERISK_ENCODING, widths) + var pos = appendPattern(result, 0, widths, true) + val narrowWhite = intArrayOf(1) + pos += appendPattern(result, pos, narrowWhite, false) + //append next character to byte matrix + for (i in 0 until length) { + val indexInString: Int = ALPHABET_STRING.indexOf(actualContent[i]) + toIntArray(CHARACTER_ENCODINGS[indexInString], widths) + pos += appendPattern(result, pos, widths, true) + pos += appendPattern(result, pos, narrowWhite, false) + } + toIntArray(ASTERISK_ENCODING, widths) + appendPattern(result, pos, widths, true) + return result + } + + companion object { + + + const val ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. \$/+%" + + val CHARACTER_ENCODINGS by lazy { + intArrayOf( + 0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, // 0-9 + // 0-9 + 0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, // A-J + // A-J + 0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, // K-T + // K-T + 0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x0A8, // U-$ + // U-$ + 0x0A2, 0x08A, 0x02A // /-% + // /-% + ) + } + + val ASTERISK_ENCODING = 0x094 + + private fun toIntArray(a: Int, toReturn: IntArray) { + for (i in 0..8) { + val temp = a and (1 shl (8 - i)) + toReturn[i] = if (temp == 0) 1 else 2 + } + } + + private fun tryToConvertToExtendedMode(contents: String): String { + val length = contents.length + val extendedContent = StringBuilder() + for (i in 0 until length) { + val character = contents[i] + when (character) { + '\u0000' -> extendedContent.append("%U") + ' ', '-', '.' -> extendedContent.append(character) + '@' -> extendedContent.append("%V") + '`' -> extendedContent.append("%W") + else -> if (character.code <= 26) { + extendedContent.append('$') + extendedContent.append(('A'.code + (character.code - 1)).toChar()) + } else if (character < ' ') { + extendedContent.append('%') + extendedContent.append(('A'.code + (character.code - 27)).toChar()) + } else if ((character <= ',') || (character == '/') || (character == ':')) { + extendedContent.append('/') + extendedContent.append(('A'.code + (character.code - 33)).toChar()) + } else if (character <= '9') { + extendedContent.append(('0'.code + (character.code - 48)).toChar()) + } else if (character <= '?') { + extendedContent.append('%') + extendedContent.append(('F'.code + (character.code - 59)).toChar()) + } else if (character <= 'Z') { + extendedContent.append(('A'.code + (character.code - 65)).toChar()) + } else if (character <= '_') { + extendedContent.append('%') + extendedContent.append(('K'.code + (character.code - 91)).toChar()) + } else if (character <= 'z') { + extendedContent.append('+') + extendedContent.append(('A'.code + (character.code - 97)).toChar()) + } else if (character.code <= 127) { + extendedContent.append('%') + extendedContent.append(('P'.code + (character.code - 123)).toChar()) + } else { + throw IllegalArgumentException( + "Requested content contains a non-encodable character: '" + contents[i] + "'" + ) + } + } + } + return extendedContent.toString() + } + } +} + diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt new file mode 100644 index 0000000..84f31e9 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt @@ -0,0 +1,57 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * Code 39 barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberCode39Painter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + Code39Painter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +@Immutable +class Code39Painter( + val data : String, + val brush : Brush = SolidColor(Color.Black), + density: Float = 1f, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : SingleDimensionBarcodePainter( + density = density, + code = Code39().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt new file mode 100644 index 0000000..df88aa9 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt @@ -0,0 +1,100 @@ +package io.github.alexzhirkevich.qrose.oned + + +internal class Code93 { + + fun encode(contents: String): BooleanArray { + var actualContents = contents + var length = actualContents.length + if (length > 80) { + throw IllegalArgumentException( + "Requested contents should be less than 80 digits long, but got $length" + ) + } + for (i in 0 until length) { + val indexInString: Int = Code39.ALPHABET_STRING.indexOf(actualContents[i]) + if (indexInString < 0) { + actualContents = tryToConvertToExtendedMode(actualContents) + length = actualContents.length + if (length > 80) { + throw IllegalArgumentException( + "Requested contents should be less than 80 digits long, but got " + + length + " (extended full ASCII mode)" + ) + } + break + } + } + val widths = IntArray(9) + val codeWidth = 24 + 1 + (13 * length) + val result = BooleanArray(codeWidth) + toIntArray(Code39.ASTERISK_ENCODING, widths) + var pos = appendPattern(result, 0, widths, true) + val narrowWhite = intArrayOf(1) + pos += appendPattern(result, pos, narrowWhite, false) + //append next character to byte matrix + for (i in 0 until length) { + val indexInString: Int = Code39.ALPHABET_STRING.indexOf(actualContents[i]) + toIntArray(Code39.CHARACTER_ENCODINGS.get(indexInString), widths) + pos += appendPattern(result, pos, widths, true) + pos += appendPattern(result, pos, narrowWhite, false) + } + toIntArray(Code39.ASTERISK_ENCODING, widths) + appendPattern(result, pos, widths, true) + return result + } + + companion object { + private fun toIntArray(a: Int, toReturn: IntArray) { + for (i in 0..8) { + val temp = a and (1 shl (8 - i)) + toReturn[i] = if (temp == 0) 1 else 2 + } + } + + private fun tryToConvertToExtendedMode(contents: String): String { + val length = contents.length + val extendedContent: StringBuilder = StringBuilder() + for (i in 0 until length) { + val character = contents[i] + when (character) { + '\u0000' -> extendedContent.append("%U") + ' ', '-', '.' -> extendedContent.append(character) + '@' -> extendedContent.append("%V") + '`' -> extendedContent.append("%W") + else -> if (character.code <= 26) { + extendedContent.append('$') + extendedContent.append(('A'.code + (character.code - 1)).toChar()) + } else if (character < ' ') { + extendedContent.append('%') + extendedContent.append(('A'.code + (character.code - 27)).toChar()) + } else if ((character <= ',') || (character == '/') || (character == ':')) { + extendedContent.append('/') + extendedContent.append(('A'.code + (character.code - 33)).toChar()) + } else if (character <= '9') { + extendedContent.append(('0'.code + (character.code - 48)).toChar()) + } else if (character <= '?') { + extendedContent.append('%') + extendedContent.append(('F'.code + (character.code - 59)).toChar()) + } else if (character <= 'Z') { + extendedContent.append(('A'.code + (character.code - 65)).toChar()) + } else if (character <= '_') { + extendedContent.append('%') + extendedContent.append(('K'.code + (character.code - 91)).toChar()) + } else if (character <= 'z') { + extendedContent.append('+') + extendedContent.append(('A'.code + (character.code - 97)).toChar()) + } else if (character.code <= 127) { + extendedContent.append('%') + extendedContent.append(('P'.code + (character.code - 123)).toChar()) + } else { + throw IllegalArgumentException( + "Requested content contains a non-encodable character: '" + contents[i] + "'" + ) + } + } + } + return extendedContent.toString() + } + } +} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt new file mode 100644 index 0000000..f4434ea --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt @@ -0,0 +1,59 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * Code 93 barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberCode93Painter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + Code93Painter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +@Immutable +class Code93Painter( + val data : String, + val brush : Brush = SolidColor(Color.Black), + density: Float = 1f, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : SingleDimensionBarcodePainter( + density = density, + code = Code93().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt new file mode 100644 index 0000000..e314b85 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt @@ -0,0 +1,56 @@ +package io.github.alexzhirkevich.qrose.oned + + +internal class CodeEAN13 { + + private val CODE_WIDTH = 3 + 7 * 6 + // left bars + 5 + 7 * 6 + // right bars + 3 // end guard + + + fun encode(contents: String): BooleanArray { + var actualContents = contents + val length = actualContents.length + when (length) { + 12 -> { + actualContents += getStandardUPCEANChecksum(actualContents) + } + + 13 -> if (!checkStandardUPCEANChecksum(actualContents)) { + throw IllegalArgumentException("Contents do not pass checksum") + } + + else -> throw IllegalArgumentException( + "Requested contents should be 12 or 13 digits long, but got $length" + ) + } + actualContents.requireNumeric() + val firstDigit = actualContents[0].digitToIntOrNull() ?: -1 + val parities: Int = UpcEan.FIRST_DIGIT_ENCODINGS[firstDigit] + val result = BooleanArray(CODE_WIDTH) + var pos = 0 + pos += appendPattern(result, pos, UpcEan.START_END_PATTERN, true) + + // See EAN13Reader for a description of how the first digit & left bars are encoded + for (i in 1..6) { + var digit = actualContents[i].digitToIntOrNull() ?: -1 + if (parities shr 6 - i and 1 == 1) { + digit += 10 + } + pos += appendPattern( + result, + pos, + UpcEan.L_AND_G_PATTERNS.get(digit), + false + ) + } + pos += appendPattern(result, pos, UpcEan.MIDDLE_PATTERN, false) + for (i in 7..12) { + val digit = actualContents[i].digitToIntOrNull() ?: -1 + pos += appendPattern(result, pos, UpcEan.L_PATTERNS[digit], true) + } + appendPattern(result, pos, UpcEan.START_END_PATTERN, true) + return result + } + +} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt new file mode 100644 index 0000000..3cc3c5e --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt @@ -0,0 +1,58 @@ +package io.github.alexzhirkevich.qrose.oned + +internal class CodeEAN8 { + private val CODE_WIDTH = 3 + 7 * 4 + // left bars + 5 + 7 * 4 + // right bars + 3 // end guard + + + /** + * @return a byte array of horizontal pixels (false = white, true = black) + */ + fun encode(contents: String): BooleanArray { + var actualContentns = contents + val length = actualContentns.length + when (length) { + 7 -> { + // No check digit present, calculate it and add it + val check: Int = getStandardUPCEANChecksum(actualContentns) + actualContentns += check + } + + 8 -> if (!checkStandardUPCEANChecksum(actualContentns)) { + throw IllegalArgumentException("Contents do not pass checksum") + } + + else -> throw IllegalArgumentException( + "Requested contents should be 7 or 8 digits long, but got $length" + ) + } + actualContentns.requireNumeric() + val result = BooleanArray(CODE_WIDTH) + var pos = 0 + pos += appendPattern(result, pos, UpcEan.START_END_PATTERN, true) + for (i in 0..3) { + val digit = actualContentns[i].digitToIntOrNull() ?: -1 + pos += appendPattern( + result, + pos, + UpcEan.L_PATTERNS[digit], + false + ) + } + pos += appendPattern( + result, + pos, + UpcEan.MIDDLE_PATTERN, + false + ) + for (i in 4..7) { + val digit = actualContentns[i].digitToIntOrNull() ?: -1 + pos += appendPattern(result, pos, UpcEan.L_PATTERNS[digit], true) + } + appendPattern(result, pos, UpcEan.START_END_PATTERN, true) + return result + } + +} + diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt new file mode 100644 index 0000000..9fabb66 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt @@ -0,0 +1,59 @@ +package io.github.alexzhirkevich.qrose.oned + + +internal class CodeITF { + + fun encode(contents: String): BooleanArray { + val length = contents.length + if (length % 2 != 0) { + throw IllegalArgumentException("The length of the input should be even") + } + if (length > 80) { + throw IllegalArgumentException( + "Requested contents should be less than 80 digits long, but got $length" + ) + } + contents.requireNumeric() + val result = BooleanArray(9 + 9 * length) + var pos = appendPattern(result, 0, START_PATTERN, true) + var i = 0 + while (i < length) { + val one = contents[i].digitToIntOrNull() ?: -1 + val two = contents[i + 1].digitToIntOrNull() ?: -1 + val encoding = IntArray(10) + for (j in 0..4) { + encoding[2 * j] = PATTERNS[one][j] + encoding[2 * j + 1] = PATTERNS[two][j] + } + pos += appendPattern(result, pos, encoding, true) + i += 2 + } + appendPattern(result, pos, END_PATTERN, true) + return result + } + + companion object { + private val START_PATTERN = intArrayOf(1, 1, 1, 1) + private val END_PATTERN = intArrayOf(3, 1, 1) + private const val W = 3 // Pixel width of a 3x wide line + private const val N = 1 // Pixed width of a narrow line + + // See ITFReader.PATTERNS + private val PATTERNS = arrayOf( + intArrayOf(N, N, W, W, N), + intArrayOf(W, N, N, N, W), + intArrayOf( + N, W, N, N, W + ), + intArrayOf(W, W, N, N, N), + intArrayOf(N, N, W, N, W), + intArrayOf(W, N, W, N, N), + intArrayOf( + N, W, W, N, N + ), + intArrayOf(N, N, N, W, W), + intArrayOf(W, N, N, W, N), + intArrayOf(N, W, N, W, N) + ) + } +} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt new file mode 100644 index 0000000..d18d82c --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt @@ -0,0 +1,5 @@ +package io.github.alexzhirkevich.qrose.oned + +class CodeUPCA { + fun encode(contents : String) = CodeEAN13().encode("0$contents") +} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt new file mode 100644 index 0000000..83b2787 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt @@ -0,0 +1,49 @@ +package io.github.alexzhirkevich.qrose.oned + + +class CodeUPCE { + + fun encode(contents: String): BooleanArray { + var contents = contents + val length = contents.length + when (length) { + 7 -> { + // No check digit present, calculate it and add it + val check: Int = getStandardUPCEANChecksum(convertUPCEtoUPCA(contents)) + contents += check + } + + 8 -> if (!checkStandardUPCEANChecksum(convertUPCEtoUPCA(contents))) { + throw IllegalArgumentException("Contents do not pass checksum") + } + + else -> throw IllegalArgumentException( + "Requested contents should be 7 or 8 digits long, but got $length" + ) + } + contents.requireNumeric() + val firstDigit = contents[0].digitToIntOrNull() ?: -1 + if (firstDigit != 0 && firstDigit != 1) { + throw IllegalArgumentException("Number system must be 0 or 1") + } + val checkDigit = contents[7].digitToIntOrNull() ?: -1 + val parities: Int = + UpcEan.NUMSYS_AND_CHECK_DIGIT_PATTERNS.get(firstDigit).get(checkDigit) + val result = BooleanArray(CODE_WIDTH) + var pos = appendPattern(result, 0, UpcEan.START_END_PATTERN, true) + for (i in 1..6) { + var digit = contents[i].digitToIntOrNull() ?: -1 + if (parities shr 6 - i and 1 == 1) { + digit += 10 + } + pos += appendPattern(result, pos, UpcEan.L_AND_G_PATTERNS.get(digit), false) + } + appendPattern(result, pos, UpcEan.END_PATTERN, false) + return result + } + + companion object { + private const val CODE_WIDTH = 3 + 7 * 6 + // bars + 6 // end guard + } +} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt new file mode 100644 index 0000000..2dd1aeb --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt @@ -0,0 +1,55 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * EAN13 barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberEAN13Painter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + EAN13Painter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +class EAN13Painter( + val data : String, + val brush: Brush = SolidColor(Color.Black), + density : Float = 1f, + builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, +) : SingleDimensionBarcodePainter( + density = density, + code = CodeEAN13().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt new file mode 100644 index 0000000..9762860 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt @@ -0,0 +1,55 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * EAN8 barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberEAN8Painter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + EAN8Painter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +class EAN8Painter( + val data : String, + val brush: Brush = SolidColor(Color.Black), + density : Float = 1f, + builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, +) : SingleDimensionBarcodePainter( + density = density, + code = CodeEAN8().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt new file mode 100644 index 0000000..e3b974b --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt @@ -0,0 +1,55 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * ETF barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberITFPainter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + ITFPainter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +class ITFPainter( + val data : String, + val brush: Brush = SolidColor(Color.Black), + density : Float = 1f, + builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, +) : SingleDimensionBarcodePainter( + density = density, + code = CodeITF().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt new file mode 100644 index 0000000..b5d6a09 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt @@ -0,0 +1,50 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.util.fastForEachIndexed +import io.github.alexzhirkevich.qrose.CachedPainter + +typealias BarcodePathBuilder = (size : Size, code : List) -> Path + +sealed class SingleDimensionBarcodePainter( + density: Float, + private val code : List, + private val brush: Brush, + private val builder : BarcodePathBuilder +) : CachedPainter(){ + + override fun DrawScope.onCache() { + drawPath( + path = builder(size, code), + brush = brush + ) + } + + override val intrinsicSize: Size = Size( + width = density * 3 * code.size, + height = 60 * density + ) +} + +@PublishedApi +internal fun defaultOneDBarcodeBuilder(size : Size, data : List): Path = Path().apply { + + val width = size.width / data.size + + data.fastForEachIndexed { i, b -> + if (b) { + addRect( + Rect( + left = i * width, + top = 0f, + right = (i + 1) * width, + bottom = size.height + ) + ) + } + } +} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt new file mode 100644 index 0000000..436dd8e --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt @@ -0,0 +1,55 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * UPC A barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberUPCAPainter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + UPCAPainter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +class UPCAPainter( + val data : String, + val brush: Brush = SolidColor(Color.Black), + density : Float = 1f, + builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, +) : SingleDimensionBarcodePainter( + density = density, + code = CodeUPCA().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt new file mode 100644 index 0000000..2a65646 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt @@ -0,0 +1,55 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * UPC E barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberUPCEPainter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + UPCEPainter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +class UPCEPainter( + val data : String, + val brush: Brush = SolidColor(Color.Black), + density : Float = 1f, + builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, +) : SingleDimensionBarcodePainter( + density = density, + code = CodeUPCE().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt new file mode 100644 index 0000000..977f625 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt @@ -0,0 +1,145 @@ +package io.github.alexzhirkevich.qrose.oned + +internal object UpcEan { + + val NUMSYS_AND_CHECK_DIGIT_PATTERNS = arrayOf( + intArrayOf(0x38, 0x34, 0x32, 0x31, 0x2C, 0x26, 0x23, 0x2A, 0x29, 0x25), + intArrayOf(0x07, 0x0B, 0x0D, 0x0E, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A) + ) + /** + * Start/end guard pattern. + */ + val START_END_PATTERN by lazy { + intArrayOf(1, 1, 1) + } + + /** + * Pattern marking the middle of a UPC/EAN pattern, separating the two halves. + */ + val MIDDLE_PATTERN by lazy { + intArrayOf(1, 1, 1, 1, 1) + } + + /** + * end guard pattern. + */ + val END_PATTERN by lazy { + intArrayOf(1, 1, 1, 1, 1, 1) + } + /** + * "Odd", or "L" patterns used to encode UPC/EAN digits. + */ + val L_PATTERNS by lazy { + arrayOf( + intArrayOf(3, 2, 1, 1), + intArrayOf(2, 2, 2, 1), + intArrayOf(2, 1, 2, 2), + intArrayOf(1, 4, 1, 1), + intArrayOf(1, 1, 3, 2), + intArrayOf(1, 2, 3, 1), + intArrayOf(1, 1, 1, 4), + intArrayOf(1, 3, 1, 2), + intArrayOf(1, 2, 1, 3), + intArrayOf(3, 1, 1, 2) + ) + } + + val L_AND_G_PATTERNS by lazy { + buildList(20) { + addAll(L_PATTERNS) + + for (i in 10..19) { + val widths: IntArray = L_PATTERNS[i - 10] + val reversedWidths = IntArray(widths.size) + for (j in widths.indices) { + reversedWidths[j] = widths[widths.size - j - 1] + } + add(reversedWidths) + } + } + } + + val FIRST_DIGIT_ENCODINGS by lazy { + intArrayOf( + 0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A + ) + } +} + +internal fun String.requireNumeric() = require(all { it.isDigit() }){ + "Input should only contain digits 0-9" +} + +internal fun convertUPCEtoUPCA(upce: String): String { + val upceChars = upce.toCharArray(1, 7) + val result = StringBuilder(12) + result.append(upce[0]) + val lastChar = upceChars[5] + when (lastChar) { + '0', '1', '2' -> { + result.appendRange(upceChars, 0, 0 + 2) + result.append(lastChar) + result.append("0000") + result.appendRange(upceChars, 2, 3) + } + + '3' -> { + result.appendRange(upceChars, 0, 3) + result.append("00000") + result.appendRange(upceChars, 3, 2) + } + + '4' -> { + result.appendRange(upceChars, 0, 4) + result.append("00000") + result.append(upceChars[4]) + } + + else -> { + result.appendRange(upceChars, 0, 5) + result.append("0000") + result.append(lastChar) + } + } + // Only append check digit in conversion if supplied + if (upce.length >= 8) { + result.append(upce[7]) + } + return result.toString() +} + +internal fun checkStandardUPCEANChecksum(s: CharSequence): Boolean { + val length = s.length + if (length == 0) { + return false + } + val check = s[length - 1].digitToIntOrNull() ?: -1 + return getStandardUPCEANChecksum(s.subSequence(0, length - 1)) == check +} + +internal fun getStandardUPCEANChecksum(s: CharSequence): Int { + val length = s.length + var sum = 0 + run { + var i = length - 1 + while (i >= 0) { + val digit = s[i].code - '0'.code + if (digit < 0 || digit > 9) { + throw IllegalStateException("Illegal contents") + } + sum += digit + i -= 2 + } + } + sum *= 3 + var i = length - 2 + while (i >= 0) { + val digit = s[i].code - '0'.code + if (digit < 0 || digit > 9) { + throw IllegalStateException("Illegal contents") + } + sum += digit + i -= 2 + } + return (1000 - sum) % 10 +} \ No newline at end of file diff --git a/qrose/build.gradle.kts b/qrose/build.gradle.kts index 49168ba..a4cc293 100644 --- a/qrose/build.gradle.kts +++ b/qrose/build.gradle.kts @@ -9,6 +9,7 @@ plugins { kotlin { sourceSets.commonMain.dependencies { implementation(compose.ui) + api(project(":qrose-core")) } } diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt new file mode 100644 index 0000000..606d455 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt @@ -0,0 +1,112 @@ +package io.github.alexzhirkevich.qrose.oned + +internal class Codabar { + + fun encode(contents: String): BooleanArray { + var actualContents = contents + if (actualContents.length < 2) { + // Can't have a start/end guard, so tentatively add default guards + actualContents = DEFAULT_GUARD.toString() + actualContents + DEFAULT_GUARD + } else { + // Verify input and calculate decoded length. + val firstChar = actualContents[0].uppercaseChar() + val lastChar = actualContents[actualContents.length - 1].uppercaseChar() + val startsNormal: Boolean = firstChar in START_END_CHARS + val endsNormal: Boolean = lastChar in START_END_CHARS + val startsAlt: Boolean = firstChar in ALT_START_END_CHARS + val endsAlt: Boolean = lastChar in ALT_START_END_CHARS + if (startsNormal) { + if (!endsNormal) { + throw IllegalArgumentException("Invalid start/end guards: $actualContents") + } + // else already has valid start/end + } else if (startsAlt) { + if (!endsAlt) { + throw IllegalArgumentException("Invalid start/end guards: $actualContents") + } + // else already has valid start/end + } else { + // Doesn't start with a guard + if (endsNormal || endsAlt) { + throw IllegalArgumentException("Invalid start/end guards: $actualContents") + } + // else doesn't end with guard either, so add a default + actualContents = DEFAULT_GUARD.toString() + actualContents + DEFAULT_GUARD + } + } + + // The start character and the end character are decoded to 10 length each. + var resultLength = 20 + for (i in 1 until actualContents.length - 1) { + resultLength += if (actualContents[i].isDigit() || actualContents[i] == '-' || actualContents[i] == '$') { + 9 + } else if (actualContents[i] in CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED) { + 10 + } else { + throw IllegalArgumentException("Cannot encode : '" + actualContents[i] + '\'') + } + } + // A blank is placed between each character. + resultLength += actualContents.length - 1 + val result = BooleanArray(resultLength) + var position = 0 + for (index in actualContents.indices) { + var c = actualContents[index].uppercaseChar() + if (index == 0 || index == actualContents.length - 1) { + // The start/end chars are not in the CodaBarReader.ALPHABET. + when (c) { + 'T' -> c = 'A' + 'N' -> c = 'B' + '*' -> c = 'C' + 'E' -> c = 'D' + } + } + var code = 0 + for (i in ALPHABET.indices) { + // Found any, because I checked above. + if (c == ALPHABET[i]) { + code = CHARACTER_ENCODINGS[i] + break + } + } + var color = true + var counter = 0 + var bit = 0 + while (bit < 7) { // A character consists of 7 digit. + result[position] = color + position++ + if (code shr 6 - bit and 1 == 0 || counter == 1) { + color = !color // Flip the color. + bit++ + counter = 0 + } else { + counter++ + } + } + if (index < actualContents.length - 1) { + result[position] = false + position++ + } + } + return result + } + + companion object { + private val START_END_CHARS = charArrayOf('A', 'B', 'C', 'D') + private val ALT_START_END_CHARS = charArrayOf('T', 'N', '*', 'E') + private val CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED = charArrayOf('/', ':', '+', '.') + private val DEFAULT_GUARD = START_END_CHARS[0] + + private const val ALPHABET_STRING = "0123456789-$:/.+ABCD" + val ALPHABET = ALPHABET_STRING.toCharArray() + + /** + * These represent the encodings of characters, as patterns of wide and narrow bars. The 7 least-significant bits of + * each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow. + */ + val CHARACTER_ENCODINGS = intArrayOf( + 0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9 + 0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E + ) + } +} diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt new file mode 100644 index 0000000..7a8404d --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt @@ -0,0 +1,55 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * Codabar barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberCodabarPainter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + CodabarPainter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +class CodabarPainter( + val data : String, + val brush: Brush = SolidColor(Color.Black), + density : Float = 1f, + builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, +) : SingleDimensionBarcodePainter( + density = density, + code = Codabar().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt index c7c6421..e314b85 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt @@ -9,14 +9,14 @@ internal class CodeEAN13 { fun encode(contents: String): BooleanArray { - var contents = contents - val length = contents.length + var actualContents = contents + val length = actualContents.length when (length) { 12 -> { - contents += CodeEAN8.getStandardUPCEANChecksum(contents) + actualContents += getStandardUPCEANChecksum(actualContents) } - 13 -> if (!CodeEAN8.checkStandardUPCEANChecksum(contents)) { + 13 -> if (!checkStandardUPCEANChecksum(actualContents)) { throw IllegalArgumentException("Contents do not pass checksum") } @@ -24,32 +24,32 @@ internal class CodeEAN13 { "Requested contents should be 12 or 13 digits long, but got $length" ) } - contents.requireNumeric() - val firstDigit = contents[0].digitToIntOrNull() ?: -1 - val parities: Int = CodeEAN8.FIRST_DIGIT_ENCODINGS[firstDigit] + actualContents.requireNumeric() + val firstDigit = actualContents[0].digitToIntOrNull() ?: -1 + val parities: Int = UpcEan.FIRST_DIGIT_ENCODINGS[firstDigit] val result = BooleanArray(CODE_WIDTH) var pos = 0 - pos += appendPattern(result, pos, CodeEAN8.START_END_PATTERN, true) + pos += appendPattern(result, pos, UpcEan.START_END_PATTERN, true) // See EAN13Reader for a description of how the first digit & left bars are encoded for (i in 1..6) { - var digit = contents[i].digitToIntOrNull() ?: -1 + var digit = actualContents[i].digitToIntOrNull() ?: -1 if (parities shr 6 - i and 1 == 1) { digit += 10 } pos += appendPattern( result, pos, - CodeEAN8.L_AND_G_PATTERNS.get(digit), + UpcEan.L_AND_G_PATTERNS.get(digit), false ) } - pos += appendPattern(result, pos, CodeEAN8.MIDDLE_PATTERN, false) + pos += appendPattern(result, pos, UpcEan.MIDDLE_PATTERN, false) for (i in 7..12) { - val digit = contents[i].digitToIntOrNull() ?: -1 - pos += appendPattern(result, pos, CodeEAN8.L_PATTERNS.get(digit), true) + val digit = actualContents[i].digitToIntOrNull() ?: -1 + pos += appendPattern(result, pos, UpcEan.L_PATTERNS[digit], true) } - appendPattern(result, pos, CodeEAN8.START_END_PATTERN, true) + appendPattern(result, pos, UpcEan.START_END_PATTERN, true) return result } diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt index ef82cf6..3cc3c5e 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt @@ -30,134 +30,29 @@ internal class CodeEAN8 { actualContentns.requireNumeric() val result = BooleanArray(CODE_WIDTH) var pos = 0 - pos += appendPattern(result, pos, START_END_PATTERN, true) + pos += appendPattern(result, pos, UpcEan.START_END_PATTERN, true) for (i in 0..3) { val digit = actualContentns[i].digitToIntOrNull() ?: -1 pos += appendPattern( result, pos, - L_PATTERNS.get(digit), + UpcEan.L_PATTERNS[digit], false ) } pos += appendPattern( result, pos, - MIDDLE_PATTERN, + UpcEan.MIDDLE_PATTERN, false ) for (i in 4..7) { val digit = actualContentns[i].digitToIntOrNull() ?: -1 - pos += appendPattern(result, pos, L_PATTERNS.get(digit), true) + pos += appendPattern(result, pos, UpcEan.L_PATTERNS[digit], true) } - appendPattern(result, pos, START_END_PATTERN, true) + appendPattern(result, pos, UpcEan.START_END_PATTERN, true) return result } - companion object { - fun checkStandardUPCEANChecksum(s: CharSequence): Boolean { - val length = s.length - if (length == 0) { - return false - } - val check = s[length - 1].digitToIntOrNull() ?: -1 - return getStandardUPCEANChecksum(s.subSequence(0, length - 1)) == check - } - - fun getStandardUPCEANChecksum(s: CharSequence): Int { - val length = s.length - var sum = 0 - run { - var i = length - 1 - while (i >= 0) { - val digit = s[i].code - '0'.code - if (digit < 0 || digit > 9) { - throw IllegalStateException("Illegal contents") - } - sum += digit - i -= 2 - } - } - sum *= 3 - var i = length - 2 - while (i >= 0) { - val digit = s[i].code - '0'.code - if (digit < 0 || digit > 9) { - throw IllegalStateException("Illegal contents") - } - sum += digit - i -= 2 - } - return (1000 - sum) % 10 - } - - // These two values are critical for determining how permissive the decoding will be. - // We've arrived at these values through a lot of trial and error. Setting them any higher - // lets false positives creep in quickly. - private const val MAX_AVG_VARIANCE = 0.48f - private const val MAX_INDIVIDUAL_VARIANCE = 0.7f - - /** - * Start/end guard pattern. - */ - val START_END_PATTERN by lazy { - intArrayOf(1, 1, 1) - } - - /** - * Pattern marking the middle of a UPC/EAN pattern, separating the two halves. - */ - val MIDDLE_PATTERN by lazy { - intArrayOf(1, 1, 1, 1, 1) - } - - /** - * end guard pattern. - */ - val END_PATTERN by lazy { - intArrayOf(1, 1, 1, 1, 1, 1) - } - /** - * "Odd", or "L" patterns used to encode UPC/EAN digits. - */ - val L_PATTERNS by lazy { - arrayOf( - intArrayOf(3, 2, 1, 1), - intArrayOf(2, 2, 2, 1), - intArrayOf(2, 1, 2, 2), - intArrayOf(1, 4, 1, 1), - intArrayOf(1, 1, 3, 2), - intArrayOf(1, 2, 3, 1), - intArrayOf(1, 1, 1, 4), - intArrayOf(1, 3, 1, 2), - intArrayOf(1, 2, 1, 3), - intArrayOf(3, 1, 1, 2) - ) - } - - val L_AND_G_PATTERNS by lazy { - buildList(20) { - addAll(L_PATTERNS) - - for (i in 10..19) { - val widths: IntArray = L_PATTERNS[i - 10] - val reversedWidths = IntArray(widths.size) - for (j in widths.indices) { - reversedWidths[j] = widths[widths.size - j - 1] - } - add(reversedWidths) - } - } - } - - val FIRST_DIGIT_ENCODINGS by lazy { - intArrayOf( - 0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A - ) - } - } } -internal fun String.requireNumeric() = require(all { it.isDigit() }){ - "Input should only contain digits 0-9" -} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt new file mode 100644 index 0000000..9fabb66 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt @@ -0,0 +1,59 @@ +package io.github.alexzhirkevich.qrose.oned + + +internal class CodeITF { + + fun encode(contents: String): BooleanArray { + val length = contents.length + if (length % 2 != 0) { + throw IllegalArgumentException("The length of the input should be even") + } + if (length > 80) { + throw IllegalArgumentException( + "Requested contents should be less than 80 digits long, but got $length" + ) + } + contents.requireNumeric() + val result = BooleanArray(9 + 9 * length) + var pos = appendPattern(result, 0, START_PATTERN, true) + var i = 0 + while (i < length) { + val one = contents[i].digitToIntOrNull() ?: -1 + val two = contents[i + 1].digitToIntOrNull() ?: -1 + val encoding = IntArray(10) + for (j in 0..4) { + encoding[2 * j] = PATTERNS[one][j] + encoding[2 * j + 1] = PATTERNS[two][j] + } + pos += appendPattern(result, pos, encoding, true) + i += 2 + } + appendPattern(result, pos, END_PATTERN, true) + return result + } + + companion object { + private val START_PATTERN = intArrayOf(1, 1, 1, 1) + private val END_PATTERN = intArrayOf(3, 1, 1) + private const val W = 3 // Pixel width of a 3x wide line + private const val N = 1 // Pixed width of a narrow line + + // See ITFReader.PATTERNS + private val PATTERNS = arrayOf( + intArrayOf(N, N, W, W, N), + intArrayOf(W, N, N, N, W), + intArrayOf( + N, W, N, N, W + ), + intArrayOf(W, W, N, N, N), + intArrayOf(N, N, W, N, W), + intArrayOf(W, N, W, N, N), + intArrayOf( + N, W, W, N, N + ), + intArrayOf(N, N, N, W, W), + intArrayOf(W, N, N, W, N), + intArrayOf(N, W, N, W, N) + ) + } +} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt new file mode 100644 index 0000000..d18d82c --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt @@ -0,0 +1,5 @@ +package io.github.alexzhirkevich.qrose.oned + +class CodeUPCA { + fun encode(contents : String) = CodeEAN13().encode("0$contents") +} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt new file mode 100644 index 0000000..83b2787 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt @@ -0,0 +1,49 @@ +package io.github.alexzhirkevich.qrose.oned + + +class CodeUPCE { + + fun encode(contents: String): BooleanArray { + var contents = contents + val length = contents.length + when (length) { + 7 -> { + // No check digit present, calculate it and add it + val check: Int = getStandardUPCEANChecksum(convertUPCEtoUPCA(contents)) + contents += check + } + + 8 -> if (!checkStandardUPCEANChecksum(convertUPCEtoUPCA(contents))) { + throw IllegalArgumentException("Contents do not pass checksum") + } + + else -> throw IllegalArgumentException( + "Requested contents should be 7 or 8 digits long, but got $length" + ) + } + contents.requireNumeric() + val firstDigit = contents[0].digitToIntOrNull() ?: -1 + if (firstDigit != 0 && firstDigit != 1) { + throw IllegalArgumentException("Number system must be 0 or 1") + } + val checkDigit = contents[7].digitToIntOrNull() ?: -1 + val parities: Int = + UpcEan.NUMSYS_AND_CHECK_DIGIT_PATTERNS.get(firstDigit).get(checkDigit) + val result = BooleanArray(CODE_WIDTH) + var pos = appendPattern(result, 0, UpcEan.START_END_PATTERN, true) + for (i in 1..6) { + var digit = contents[i].digitToIntOrNull() ?: -1 + if (parities shr 6 - i and 1 == 1) { + digit += 10 + } + pos += appendPattern(result, pos, UpcEan.L_AND_G_PATTERNS.get(digit), false) + } + appendPattern(result, pos, UpcEan.END_PATTERN, false) + return result + } + + companion object { + private const val CODE_WIDTH = 3 + 7 * 6 + // bars + 6 // end guard + } +} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt index 52c8343..2dd1aeb 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt @@ -11,7 +11,7 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalDensity /** - * EAN13 barcode barcode painter + * EAN13 barcode painter * * @param brush code brush * @param onError called when input content is invalid diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt new file mode 100644 index 0000000..e3b974b --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt @@ -0,0 +1,55 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * ETF barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberITFPainter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + ITFPainter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +class ITFPainter( + val data : String, + val brush: Brush = SolidColor(Color.Black), + density : Float = 1f, + builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, +) : SingleDimensionBarcodePainter( + density = density, + code = CodeITF().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt new file mode 100644 index 0000000..436dd8e --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt @@ -0,0 +1,55 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * UPC A barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberUPCAPainter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + UPCAPainter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +class UPCAPainter( + val data : String, + val brush: Brush = SolidColor(Color.Black), + density : Float = 1f, + builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, +) : SingleDimensionBarcodePainter( + density = density, + code = CodeUPCA().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt new file mode 100644 index 0000000..2a65646 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt @@ -0,0 +1,55 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity + +/** + * UPC E barcode painter + * + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * */ +@Composable +fun rememberUPCEPainter( + data: String, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder +) : Painter { + val density = LocalDensity.current + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(density, data, brush) { + runCatching { + UPCEPainter( + data = data, + density = density.density, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + +class UPCEPainter( + val data : String, + val brush: Brush = SolidColor(Color.Black), + density : Float = 1f, + builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, +) : SingleDimensionBarcodePainter( + density = density, + code = CodeUPCE().encode(data).toList(), + brush = brush, + builder = builder +) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt new file mode 100644 index 0000000..977f625 --- /dev/null +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt @@ -0,0 +1,145 @@ +package io.github.alexzhirkevich.qrose.oned + +internal object UpcEan { + + val NUMSYS_AND_CHECK_DIGIT_PATTERNS = arrayOf( + intArrayOf(0x38, 0x34, 0x32, 0x31, 0x2C, 0x26, 0x23, 0x2A, 0x29, 0x25), + intArrayOf(0x07, 0x0B, 0x0D, 0x0E, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A) + ) + /** + * Start/end guard pattern. + */ + val START_END_PATTERN by lazy { + intArrayOf(1, 1, 1) + } + + /** + * Pattern marking the middle of a UPC/EAN pattern, separating the two halves. + */ + val MIDDLE_PATTERN by lazy { + intArrayOf(1, 1, 1, 1, 1) + } + + /** + * end guard pattern. + */ + val END_PATTERN by lazy { + intArrayOf(1, 1, 1, 1, 1, 1) + } + /** + * "Odd", or "L" patterns used to encode UPC/EAN digits. + */ + val L_PATTERNS by lazy { + arrayOf( + intArrayOf(3, 2, 1, 1), + intArrayOf(2, 2, 2, 1), + intArrayOf(2, 1, 2, 2), + intArrayOf(1, 4, 1, 1), + intArrayOf(1, 1, 3, 2), + intArrayOf(1, 2, 3, 1), + intArrayOf(1, 1, 1, 4), + intArrayOf(1, 3, 1, 2), + intArrayOf(1, 2, 1, 3), + intArrayOf(3, 1, 1, 2) + ) + } + + val L_AND_G_PATTERNS by lazy { + buildList(20) { + addAll(L_PATTERNS) + + for (i in 10..19) { + val widths: IntArray = L_PATTERNS[i - 10] + val reversedWidths = IntArray(widths.size) + for (j in widths.indices) { + reversedWidths[j] = widths[widths.size - j - 1] + } + add(reversedWidths) + } + } + } + + val FIRST_DIGIT_ENCODINGS by lazy { + intArrayOf( + 0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A + ) + } +} + +internal fun String.requireNumeric() = require(all { it.isDigit() }){ + "Input should only contain digits 0-9" +} + +internal fun convertUPCEtoUPCA(upce: String): String { + val upceChars = upce.toCharArray(1, 7) + val result = StringBuilder(12) + result.append(upce[0]) + val lastChar = upceChars[5] + when (lastChar) { + '0', '1', '2' -> { + result.appendRange(upceChars, 0, 0 + 2) + result.append(lastChar) + result.append("0000") + result.appendRange(upceChars, 2, 3) + } + + '3' -> { + result.appendRange(upceChars, 0, 3) + result.append("00000") + result.appendRange(upceChars, 3, 2) + } + + '4' -> { + result.appendRange(upceChars, 0, 4) + result.append("00000") + result.append(upceChars[4]) + } + + else -> { + result.appendRange(upceChars, 0, 5) + result.append("0000") + result.append(lastChar) + } + } + // Only append check digit in conversion if supplied + if (upce.length >= 8) { + result.append(upce[7]) + } + return result.toString() +} + +internal fun checkStandardUPCEANChecksum(s: CharSequence): Boolean { + val length = s.length + if (length == 0) { + return false + } + val check = s[length - 1].digitToIntOrNull() ?: -1 + return getStandardUPCEANChecksum(s.subSequence(0, length - 1)) == check +} + +internal fun getStandardUPCEANChecksum(s: CharSequence): Int { + val length = s.length + var sum = 0 + run { + var i = length - 1 + while (i >= 0) { + val digit = s[i].code - '0'.code + if (digit < 0 || digit > 9) { + throw IllegalStateException("Illegal contents") + } + sum += digit + i -= 2 + } + } + sum *= 3 + var i = length - 2 + while (i >= 0) { + val digit = s[i].code - '0'.code + if (digit < 0 || digit > 9) { + throw IllegalStateException("Illegal contents") + } + sum += digit + i -= 2 + } + return (1000 - sum) % 10 +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 5c44080..5ac6d6e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,6 +19,8 @@ dependencyResolutionManagement { rootProject.name = "QRose" include(":qrose") +include(":qrose-core") +include(":qrose-oned") include(":example:desktopApp") include(":example:webApp") From 4ec1c474e8a6a07dd9d46ba5bded77f7cbeb86a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D0=B8=D1=80=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Mon, 4 Mar 2024 01:15:41 +0300 Subject: [PATCH 07/13] release/1.0.0 --- .github/workflows/build.yml | 42 ++ README.md | 23 +- build.gradle.kts | 98 +++ .../io/github/alexzhirkevich/shared/App.kt | 37 +- qrose-core/build.gradle.kts | 99 --- qrose-core/publish.properties | 1 + .../alexzhirkevich/qrose}/DelicateQRoseApi.kt | 2 +- qrose-oned/build.gradle.kts | 98 --- qrose-oned/publish.properties | 2 + .../qrose/oned/BarcodeEncoder.kt | 14 + .../qrose/oned/BarcodePainter.kt | 128 ++++ .../alexzhirkevich/qrose/oned/BarcodeType.kt | 14 + .../oned/{Codabar.kt => CodabarEncoder.kt} | 36 +- .../qrose/oned/CodabarPainter.kt | 55 -- .../alexzhirkevich/qrose/oned/Code128.kt | 647 ------------------ .../qrose/oned/Code128Encoder.kt | 428 ++++++------ .../qrose/oned/Code128Painter.kt | 59 +- .../alexzhirkevich/qrose/oned/Code39.kt | 121 ---- .../qrose/oned/Code39Encoder.kt | 118 ++++ .../qrose/oned/Code39Painter.kt | 57 -- .../alexzhirkevich/qrose/oned/Code93.kt | 100 --- .../qrose/oned/Code93Encoder.kt | 98 +++ .../qrose/oned/Code93Painter.kt | 59 -- .../alexzhirkevich/qrose/oned/CodeEAN13.kt | 56 -- .../qrose/oned/CodeEAN13Encoder.kt | 4 +- .../alexzhirkevich/qrose/oned/CodeEAN8.kt | 58 -- .../qrose/oned/CodeEAN8Encoder.kt | 7 +- .../alexzhirkevich/qrose/oned/CodeITF.kt | 59 -- .../qrose/oned/CodeITFEncoder.kt | 48 +- .../alexzhirkevich/qrose/oned/CodeUPCA.kt | 5 - .../qrose/oned/CodeUPCAEncoder.kt | 5 + .../oned/{CodeUPCE.kt => CodeUPCEEncoder.kt} | 11 +- .../alexzhirkevich/qrose/oned/EAN13Painter.kt | 55 -- .../alexzhirkevich/qrose/oned/EAN8Painter.kt | 55 -- .../alexzhirkevich/qrose/oned/ITFPainter.kt | 55 -- .../qrose/oned/SingleDimensionBarcode.kt | 50 -- .../alexzhirkevich/qrose/oned/UPCAPainter.kt | 55 -- .../alexzhirkevich/qrose/oned/UPCEPainter.kt | 55 -- qrose/build.gradle.kts | 97 --- qrose/publish.properties | 1 + .../alexzhirkevich/qrose/oned/Codabar.kt | 112 --- .../qrose/oned/CodabarPainter.kt | 55 -- .../qrose/oned/Code128Painter.kt | 75 -- .../alexzhirkevich/qrose/oned/Code39.kt | 121 ---- .../qrose/oned/Code39Painter.kt | 57 -- .../alexzhirkevich/qrose/oned/Code93.kt | 100 --- .../qrose/oned/Code93Painter.kt | 59 -- .../alexzhirkevich/qrose/oned/CodeUPCA.kt | 5 - .../alexzhirkevich/qrose/oned/CodeUPCE.kt | 49 -- .../alexzhirkevich/qrose/oned/EAN13Painter.kt | 55 -- .../alexzhirkevich/qrose/oned/EAN8Painter.kt | 55 -- .../alexzhirkevich/qrose/oned/ITFPainter.kt | 55 -- .../qrose/oned/SingleDimensionBarcode.kt | 50 -- .../alexzhirkevich/qrose/oned/UPCAPainter.kt | 55 -- .../alexzhirkevich/qrose/oned/UPCEPainter.kt | 55 -- .../alexzhirkevich/qrose/oned/UpcEanUtils.kt | 145 ---- .../dsl/InternalQrOptionsBuilderScope.kt | 2 +- .../options/dsl/QrOptionsBuilderScope.kt | 2 +- settings.gradle.kts | 1 - 59 files changed, 845 insertions(+), 3275 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 qrose-core/publish.properties rename {qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options => qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose}/DelicateQRoseApi.kt (77%) create mode 100644 qrose-oned/publish.properties create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodeEncoder.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodePainter.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodeType.kt rename qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/{Codabar.kt => CodabarEncoder.kt} (78%) delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt rename qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt => qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Encoder.kt (59%) delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Encoder.kt delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Encoder.kt delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt rename qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt => qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13Encoder.kt (94%) delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt rename qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt => qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8Encoder.kt (90%) delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt rename qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt => qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITFEncoder.kt (55%) delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt create mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCAEncoder.kt rename qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/{CodeUPCE.kt => CodeUPCEEncoder.kt} (89%) delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt delete mode 100644 qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt create mode 100644 qrose/publish.properties delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c2dc51b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,42 @@ +name: Build + +on: + push: + paths-ignore: + - '**.md' + pull_request: + paths-ignore: + - '**.md' + +jobs: + build: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v1 + + - name: Setup JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: 17 + + - uses: gradle/gradle-build-action@v2.4.2 + with: + gradle-home-cache-cleanup: true + - name: Make gradlew executable + run: chmod +x ./gradlew + - name: Assemble + run: ./gradlew assemble --stacktrace + - name: Publish + if: startsWith(github.ref, 'refs/tags/') + run: ./gradlew publish + env: + OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} + OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + GPG_KEY: ${{ secrets.GPG_KEY }} + GPG_KEY_PWD: ${{ secrets.GPG_KEY_PWD }} + diff --git a/README.md b/README.md index cd26963..be22035 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ![badge-macOS](https://img.shields.io/badge/Platform-macOS-purple) ![badge-web](https://img.shields.io/badge/Platform-Web-blue) -QR code design library for Compose Multiplatform +Barcode generation library for Compose Multiplatform Screenshot 2023-10-10 at 10 34 05 @@ -16,6 +16,7 @@ Why QRose? - **Efficient** - declare and render codes synchronously right from the composition in 60+ fps; - **Scalable** - no raster bitmaps, only scalable vector graphics; - **Multiplatform** - supports all the targets supported by Compose Multiplatform. +- **Multiformat** - multiple formats supported : QR, UPC, EAN, Code 128, etc. # Installation @@ -23,7 +24,12 @@ Why QRose? ```gradle dependencies { - implementation("io.github.alexzhirkevich:qrose:1.0.0-beta3") + + // For QR codes + implementation("io.github.alexzhirkevich:qrose:1.0.0") + + // For single-dimension barcodes (UPC,EAN, Code128, ...) + implementation("io.github.alexzhirkevich:qrose:1.0.0-oned") } ``` @@ -31,19 +37,24 @@ dependencies { ## Basic -You can create code right in composition using `rememberQrCodePainter`. -Or do it outside of Compose scope by instantiating a `QrCodePainter` class. +You can create code right in composition using `rememberQrCodePainter`, `rememberBarcodePainter`. +Or use `QrCodePainter`, `BarcodePainter` to crate it outside compose. ```kotlin Image( painter = rememberQrCodePainter("https://example.com"), - contentDescription = "QR code" + contentDescription = "QR code referring to the example.com website" +) + +Image( + painter = rememberBarcodePainter("9780201379624", BarcodeType.EAN13), + contentDescription = "EAN barcode for some product" ) ``` ## Design -There are some overloads of `rememberQrCodePainter` including DSL constructor: +QR codes have flexible styling options, for example: ```kotlin val logoPainter : Painter = painterResource("logo.png") diff --git a/build.gradle.kts b/build.gradle.kts index a670154..a8cd209 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,17 @@ @file: Suppress("DSL_SCOPE_VIOLATION") + +import org.jetbrains.compose.internal.utils.localPropertiesFile +import java.util.Properties +import java.util.Base64 + plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.android.application).apply(false) alias(libs.plugins.compose).apply(false) alias(libs.plugins.dokka).apply(false) + id("maven-publish") + id("signing") } //tasks.register("clean", Delete::class) { @@ -19,6 +26,20 @@ val composePlugin = libs.plugins.compose val ver = libs.versions.qrose.get() +if (System.getenv("OSSRH_PASSWORD") != null){ + ext["OSSRH_USERNAME"] = System.getenv("OSSRH_USERNAME") + ext["OSSRH_PASSWORD"] = System.getenv("OSSRH_PASSWORD") + ext["GPG_KEY"] = System.getenv("GPG_KEY") + ext["GPG_KEY_PWD"] = System.getenv("GPG_KEY_PWD") +} else { + val properties = Properties().apply { + load(localPropertiesFile.inputStream()) + } + properties.forEach { + ext[it.key.toString()] = it.value.toString() + } +} + subprojects { if (!name.startsWith("qrose")) return@subprojects @@ -26,6 +47,8 @@ subprojects { this.group = "io.github.alexzhirkevich" this.version = ver + plugins.apply("maven-publish") + plugins.apply("signing") plugins.apply(kmpPlugin.get().pluginId) plugins.apply(androidLibPlugin.get().pluginId) plugins.apply(composePlugin.get().pluginId) @@ -95,6 +118,81 @@ subprojects { targetCompatibility = JavaVersion.toVersion(_jvmTarget) } } + + val javadocJar by tasks.registering(Jar::class) { + archiveClassifier.set("javadoc") + } + + val signingTasks = tasks.withType() + + tasks.withType().configureEach { + dependsOn(signingTasks) + } + + publishing { + + repositories.maven { + val releasesRepoUrl = + "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" + val snapshotsRepoUrl = + "https://s01.oss.sonatype.org/content/repositories/snapshots/" + url = if (version.toString().endsWith("SNAPSHOT")) { + uri(snapshotsRepoUrl) + } else { + uri(releasesRepoUrl) + } + credentials { + if (rootProject.ext.has("OSSRH_PASSWORD")) { + password = rootProject.ext["OSSRH_PASSWORD"] as String + } + if (rootProject.ext.has("OSSRH_USERNAME")) { + username = rootProject.ext["OSSRH_USERNAME"] as String + } + } + } + + val publishProperties = Properties().apply { + load(file("publish.properties").inputStream()) + } + + publications.withType { + artifact(javadocJar) + pom { + name.set(this@subprojects.name) + description.set(publishProperties.getProperty("description")) + url.set("https://github.com/alexzhirkevich/qrose") + + licenses { + license { + name.set("MIT") + url.set("https://opensource.org/licenses/MIT") + } + } + developers { + developer { + id.set("alexzhirkevich") + name.set("Alexander Zhirkevich") + email.set("sasha.zhirkevich@gmail.com") + } + } + scm { + url.set("https://github.com/alexzhirkevich/qrose") + connection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") + developerConnection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") + } + } + } + } + + if (rootProject.ext.has("GPG_KEY") && rootProject.ext.has("GPG_KEY_PWD") ) { + signing { + useInMemoryPgpKeys( + Base64.getDecoder().decode(rootProject.ext["GPG_KEY"] as String).decodeToString(), + rootProject.ext["GPG_KEY_PWD"] as String, + ) + sign(publishing.publications) + } + } } android { namespace = "io.github.alexzhirkevich.qrose" diff --git a/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt b/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt index 69b37df..8b78ec0 100644 --- a/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt +++ b/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt @@ -6,20 +6,15 @@ import androidx.compose.foundation.border import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.DatePicker -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.material3.TextField -import androidx.compose.material3.rememberDatePickerState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -32,18 +27,11 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.dp +import io.github.alexzhirkevich.qrose.DelicateQRoseApi import io.github.alexzhirkevich.qrose.QrData import io.github.alexzhirkevich.qrose.email -import io.github.alexzhirkevich.qrose.oned.rememberCodabarPainter -import io.github.alexzhirkevich.qrose.oned.rememberCode128Painter -import io.github.alexzhirkevich.qrose.oned.rememberCode39Painter -import io.github.alexzhirkevich.qrose.oned.rememberCode93Painter -import io.github.alexzhirkevich.qrose.oned.rememberEAN13Painter -import io.github.alexzhirkevich.qrose.oned.rememberEAN8Painter -import io.github.alexzhirkevich.qrose.oned.rememberITFPainter -import io.github.alexzhirkevich.qrose.oned.rememberUPCAPainter -import io.github.alexzhirkevich.qrose.oned.rememberUPCEPainter -import io.github.alexzhirkevich.qrose.options.DelicateQRoseApi +import io.github.alexzhirkevich.qrose.oned.BarcodeType +import io.github.alexzhirkevich.qrose.oned.rememberBarcodePainter import io.github.alexzhirkevich.qrose.options.QrBallShape import io.github.alexzhirkevich.qrose.options.QrBrush import io.github.alexzhirkevich.qrose.options.QrCodeShape @@ -65,7 +53,6 @@ import qrose.example.shared.generated.resources.Res import qrose.example.shared.generated.resources.jc import qrose.example.shared.generated.resources.jcbg -@OptIn(ExperimentalMaterial3Api::class) @Composable fun App() { AllBarcodes() @@ -75,15 +62,15 @@ fun App() { @Composable fun AllBarcodes() { FlowRow(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { - OnedCode("ITF", rememberITFPainter("123456734512")) - OnedCode("UPC E", rememberUPCEPainter("02345673")) - OnedCode("UPC A", rememberUPCAPainter("123456789012")) - OnedCode("EAN 13", rememberEAN13Painter("9780201379624")) - OnedCode("EAN 8", rememberEAN8Painter("1234567")) - OnedCode("Code 39", rememberCode39Painter("TEST")) - OnedCode("Code 93", rememberCode93Painter("TEST")) - OnedCode("Code 128", rememberCode128Painter("test")) - OnedCode("Codabar", rememberCodabarPainter("A23342453D")) + OnedCode("ITF", rememberBarcodePainter("123456734512", BarcodeType.ITF)) + OnedCode("UPC E", rememberBarcodePainter("02345673", BarcodeType.UPCE)) + OnedCode("UPC A", rememberBarcodePainter("123456789012", BarcodeType.UPCA)) + OnedCode("EAN 13", rememberBarcodePainter("9780201379624", BarcodeType.EAN13)) + OnedCode("EAN 8", rememberBarcodePainter("1234567", BarcodeType.EAN8)) + OnedCode("Code 39", rememberBarcodePainter("TEST", BarcodeType.Code39)) + OnedCode("Code 93", rememberBarcodePainter("TEST", BarcodeType.Code93)) + OnedCode("Code 128", rememberBarcodePainter("test", BarcodeType.Code128)) + OnedCode("Codabar", rememberBarcodePainter("A23342453D", BarcodeType.Codabar)) OnedCode("QR", rememberQrCodePainter("https://github.com/alexzhirkevich/qrose")) } } diff --git a/qrose-core/build.gradle.kts b/qrose-core/build.gradle.kts index 49168ba..acc73aa 100644 --- a/qrose-core/build.gradle.kts +++ b/qrose-core/build.gradle.kts @@ -1,104 +1,5 @@ -@file:Suppress("DSL_SCOPE_VIOLATION") - -plugins { - id("maven-publish") - id("signing") -} - - kotlin { sourceSets.commonMain.dependencies { implementation(compose.ui) } } - -//ext["signing.keyId"] = null -//ext["signing.password"] = null -//ext["signing.secretKeyRingFile"] = null -//ext["ossrhUsername"] = null -//ext["ossrhPassword"] = null - -//extra.apply { -// val publishPropFile = rootProject.file("local.properties") -// if (publishPropFile.exists()) { -// Properties().apply { -// load(publishPropFile.inputStream()) -// }.forEach { name, value -> -// if (name == "signing.secretKeyRingFile") { -// set(name.toString(), rootProject.file(value.toString()).absolutePath) -// } else { -// set(name.toString(), value) -// } -// } -// } else { -// ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID") -// ext["signing.password"] = System.getenv("SIGNING_PASSWORD") -// ext["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE") -// ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME") -// ext["ossrhPassword"]= System.getenv("OSSRH_PASSWORD") -// } -//} -// -//val javadocJar by tasks.registering(Jar::class) { -// archiveClassifier.set("javadoc") -//} -//// https://github.com/gradle/gradle/issues/26091 -//val signingTasks = tasks.withType() -//tasks.withType().configureEach { -// dependsOn(signingTasks) -//} - -//publishing { -// if (rootProject.file("local.properties").exists()) { -// -// repositories { -// maven { -// val releasesRepoUrl = -// "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" -// val snapshotsRepoUrl = -// "https://s01.oss.sonatype.org/content/repositories/snapshots/" -// url = if (version.toString().endsWith("SNAPSHOT")) { -// uri(snapshotsRepoUrl) -// } else { -// uri(releasesRepoUrl) -// } -// credentials { -// username = project.ext.get("ossrhUsername").toString() -// password = project.ext.get("ossrhPassword").toString() -// } -// } -// } -// } -// -// publications.withType { -// artifact(javadocJar) -// pom { -// name.set("QRose") -// description.set("Styled QR code generation library for Compose Multiplatform") -// url.set("https://github.com/alexzhirkevich/qrose") -// -// licenses { -// license { -// name.set("MIT") -// url.set("https://opensource.org/licenses/MIT") -// } -// } -// developers { -// developer { -// id.set("alexzhirkevich") -// name.set("Alexander Zhirkevich") -// email.set("sasha.zhirkevich@gmail.com") -// } -// } -// scm { -// url.set("https://github.com/alexzhirkevich/qrose") -// connection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") -// developerConnection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") -// } -// } -// } -//} - -//signing { -// sign(publishing.publications) -//} \ No newline at end of file diff --git a/qrose-core/publish.properties b/qrose-core/publish.properties new file mode 100644 index 0000000..7436c1f --- /dev/null +++ b/qrose-core/publish.properties @@ -0,0 +1 @@ +description=QRose shared module diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/DelicateQRoseApi.kt b/qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DelicateQRoseApi.kt similarity index 77% rename from qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/DelicateQRoseApi.kt rename to qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DelicateQRoseApi.kt index 6062578..5b1dc76 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/DelicateQRoseApi.kt +++ b/qrose-core/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DelicateQRoseApi.kt @@ -1,4 +1,4 @@ -package io.github.alexzhirkevich.qrose.options +package io.github.alexzhirkevich.qrose @RequiresOptIn( message = "This API may negatively impact QR code functionality", diff --git a/qrose-oned/build.gradle.kts b/qrose-oned/build.gradle.kts index a4cc293..d8fda39 100644 --- a/qrose-oned/build.gradle.kts +++ b/qrose-oned/build.gradle.kts @@ -1,10 +1,3 @@ -@file:Suppress("DSL_SCOPE_VIOLATION") - -plugins { - id("maven-publish") - id("signing") -} - kotlin { sourceSets.commonMain.dependencies { @@ -12,94 +5,3 @@ kotlin { api(project(":qrose-core")) } } - -//ext["signing.keyId"] = null -//ext["signing.password"] = null -//ext["signing.secretKeyRingFile"] = null -//ext["ossrhUsername"] = null -//ext["ossrhPassword"] = null - -//extra.apply { -// val publishPropFile = rootProject.file("local.properties") -// if (publishPropFile.exists()) { -// Properties().apply { -// load(publishPropFile.inputStream()) -// }.forEach { name, value -> -// if (name == "signing.secretKeyRingFile") { -// set(name.toString(), rootProject.file(value.toString()).absolutePath) -// } else { -// set(name.toString(), value) -// } -// } -// } else { -// ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID") -// ext["signing.password"] = System.getenv("SIGNING_PASSWORD") -// ext["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE") -// ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME") -// ext["ossrhPassword"]= System.getenv("OSSRH_PASSWORD") -// } -//} -// -//val javadocJar by tasks.registering(Jar::class) { -// archiveClassifier.set("javadoc") -//} -//// https://github.com/gradle/gradle/issues/26091 -//val signingTasks = tasks.withType() -//tasks.withType().configureEach { -// dependsOn(signingTasks) -//} - -//publishing { -// if (rootProject.file("local.properties").exists()) { -// -// repositories { -// maven { -// val releasesRepoUrl = -// "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" -// val snapshotsRepoUrl = -// "https://s01.oss.sonatype.org/content/repositories/snapshots/" -// url = if (version.toString().endsWith("SNAPSHOT")) { -// uri(snapshotsRepoUrl) -// } else { -// uri(releasesRepoUrl) -// } -// credentials { -// username = project.ext.get("ossrhUsername").toString() -// password = project.ext.get("ossrhPassword").toString() -// } -// } -// } -// } -// -// publications.withType { -// artifact(javadocJar) -// pom { -// name.set("QRose") -// description.set("Styled QR code generation library for Compose Multiplatform") -// url.set("https://github.com/alexzhirkevich/qrose") -// -// licenses { -// license { -// name.set("MIT") -// url.set("https://opensource.org/licenses/MIT") -// } -// } -// developers { -// developer { -// id.set("alexzhirkevich") -// name.set("Alexander Zhirkevich") -// email.set("sasha.zhirkevich@gmail.com") -// } -// } -// scm { -// url.set("https://github.com/alexzhirkevich/qrose") -// connection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") -// developerConnection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") -// } -// } -// } -//} - -//signing { -// sign(publishing.publications) -//} \ No newline at end of file diff --git a/qrose-oned/publish.properties b/qrose-oned/publish.properties new file mode 100644 index 0000000..e405094 --- /dev/null +++ b/qrose-oned/publish.properties @@ -0,0 +1,2 @@ + +description=Compose Multiplatform single-dimension barcodes \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodeEncoder.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodeEncoder.kt new file mode 100644 index 0000000..f00491d --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodeEncoder.kt @@ -0,0 +1,14 @@ +package io.github.alexzhirkevich.qrose.oned + +/** + * Single dimension barcode encoder. + * */ +interface BarcodeEncoder { + + /** + * Encodes string [data] into barcode [BooleanArray] where 1 is a black bar and 0 is a white bar. + * + * Illegal contents can throw exceptions + * */ + fun encode(data : String) : BooleanArray +} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodePainter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodePainter.kt new file mode 100644 index 0000000..8ea1097 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodePainter.kt @@ -0,0 +1,128 @@ +package io.github.alexzhirkevich.qrose.oned + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.painter.Painter +import io.github.alexzhirkevich.qrose.CachedPainter + + +typealias BarcodePathBuilder = (size : Size, code : BooleanArray) -> Path + +/** + * Remember barcode painter + * + * @param data code contents. Invalid contents can throw an exception + * @param type barcode type + * @param brush code brush + * @param onError called when input content is invalid + * @param builder build code path using painter size and encoded boolean list + * + * @see BarcodePainter + * */ +@Composable +@Stable +fun rememberBarcodePainter( + data : String, + type: BarcodeType, + brush: Brush = SolidColor(Color.Black), + onError : (Throwable) -> Painter = { throw it }, + builder: BarcodePathBuilder = ::defaultBarcodeBuilder +) : Painter { + + val updatedBuilder by rememberUpdatedState(builder) + + return remember(data, brush) { + runCatching { + BarcodePainter( + data = data, + type = type, + brush = brush, + builder = { size, code -> + updatedBuilder(size, code) + }, + ) + }.getOrElse(onError) + } +} + + +/** + * Create barcode painter + * + * @param code encoded barcode data + * @param brush code brush + * @param builder build code path using painter size and encoded boolean list + * + * @see rememberBarcodePainter + * */ +@Immutable +class BarcodePainter( + private val code : BooleanArray, + private val brush: Brush = SolidColor(Color.Black), + private val builder : BarcodePathBuilder = ::defaultBarcodeBuilder +) : CachedPainter() { + + /** + * Create barcode painter + * + * @param data code contents. Invalid contents can throw an exception + * @param type barcode type + * @param brush code brush + * @param builder build code path using painter size and encoded boolean list + * + * @see rememberBarcodePainter + * */ + constructor( + data : String, + type: BarcodeType, + brush: Brush = SolidColor(Color.Black), + builder: BarcodePathBuilder = ::defaultBarcodeBuilder + ) : this( + code = type.encoder.encode(data), + brush = brush, + builder = builder + ) + + override fun DrawScope.onCache() { + drawPath( + path = builder(size, code), + brush = brush + ) + } + + override val intrinsicSize: Size = Size( + width = 6f * code.size, + height = 120f + ) +} + +@PublishedApi +@Stable +internal fun defaultBarcodeBuilder(size : Size, data : BooleanArray): Path = Path().apply { + + val width = size.width / data.size + + data.forEachIndexed { i, b -> + if (b) { + addRect( + Rect( + left = i * width, + top = 0f, + right = (i + 1) * width, + bottom = size.height + ) + ) + } + } +} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodeType.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodeType.kt new file mode 100644 index 0000000..610d3a7 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodeType.kt @@ -0,0 +1,14 @@ +package io.github.alexzhirkevich.qrose.oned + +enum class BarcodeType(val encoder: BarcodeEncoder) { + + Codabar(CodabarEncoder), + Code39(Code39Encoder), + Code93(Code39Encoder), + Code128(Code128Encoder), + EAN8(CodeEAN8Encoder), + EAN13(CodeEAN13Encoder), + ITF(CodeITFEncoder), + UPCA(CodeUPCAEncoder), + UPCE(CodeUPCEEncoder) +} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarEncoder.kt similarity index 78% rename from qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt rename to qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarEncoder.kt index 606d455..3e6b221 100644 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarEncoder.kt @@ -1,8 +1,8 @@ package io.github.alexzhirkevich.qrose.oned -internal class Codabar { +internal object CodabarEncoder : BarcodeEncoder { - fun encode(contents: String): BooleanArray { + override fun encode(contents: String): BooleanArray { var actualContents = contents if (actualContents.length < 2) { // Can't have a start/end guard, so tentatively add default guards @@ -91,22 +91,20 @@ internal class Codabar { return result } - companion object { - private val START_END_CHARS = charArrayOf('A', 'B', 'C', 'D') - private val ALT_START_END_CHARS = charArrayOf('T', 'N', '*', 'E') - private val CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED = charArrayOf('/', ':', '+', '.') - private val DEFAULT_GUARD = START_END_CHARS[0] + private val START_END_CHARS = charArrayOf('A', 'B', 'C', 'D') + private val ALT_START_END_CHARS = charArrayOf('T', 'N', '*', 'E') + private val CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED = charArrayOf('/', ':', '+', '.') + private val DEFAULT_GUARD = START_END_CHARS[0] - private const val ALPHABET_STRING = "0123456789-$:/.+ABCD" - val ALPHABET = ALPHABET_STRING.toCharArray() + private const val ALPHABET_STRING = "0123456789-$:/.+ABCD" + private val ALPHABET = ALPHABET_STRING.toCharArray() - /** - * These represent the encodings of characters, as patterns of wide and narrow bars. The 7 least-significant bits of - * each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow. - */ - val CHARACTER_ENCODINGS = intArrayOf( - 0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9 - 0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E - ) - } -} + /** + * These represent the encodings of characters, as patterns of wide and narrow bars. The 7 least-significant bits of + * each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow. + */ + private val CHARACTER_ENCODINGS = intArrayOf( + 0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9 + 0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E + ) +} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt deleted file mode 100644 index 7a8404d..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * Codabar barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberCodabarPainter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - CodabarPainter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -class CodabarPainter( - val data : String, - val brush: Brush = SolidColor(Color.Black), - density : Float = 1f, - builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, -) : SingleDimensionBarcodePainter( - density = density, - code = Codabar().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt deleted file mode 100644 index 388ffc0..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt +++ /dev/null @@ -1,647 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -internal class Code128 { - // Results of minimal lookahead for code C - private enum class CType { - UNCODABLE, - ONE_DIGIT, - TWO_DIGITS, - FNC_1 - } - - - fun encode(contents: String, compact : Boolean = true , codeSet : Code128Painter.CodeSet? = null): BooleanArray { - val forcedCodeSet = check(contents, codeSet) - - return if (compact) { - MinimalEncoder().encode(contents) - } else { - encodeFast( - contents, - forcedCodeSet - ) - } - } - - /** - * Encodes minimally using Divide-And-Conquer with Memoization - */ - private class MinimalEncoder { - private enum class Charset { - A, - B, - C, - NONE - } - - private enum class Latch { - A, - B, - C, - SHIFT, - NONE - } - - private var memoizedCost: Array? = null - private var minPath: Array>? = null - - fun encode(contents: String): BooleanArray { - memoizedCost = Array(4) { IntArray(contents.length) } - minPath = Array(4) { - arrayOfNulls( - contents.length - ) - } - encode(contents, Charset.NONE, 0) - val patterns: MutableCollection = ArrayList() - val checkSum = intArrayOf(0) - val checkWeight = intArrayOf(1) - val length = contents.length - var charset = Charset.NONE - var i = 0 - while (i < length) { - val latch = minPath!![charset.ordinal][i] - when (latch) { - Latch.A -> { - charset = Charset.A - addPattern( - patterns, - if (i == 0) CODE_START_A else CODE_CODE_A, - checkSum, - checkWeight, - i - ) - } - - Latch.B -> { - charset = Charset.B - addPattern( - patterns, - if (i == 0) CODE_START_B else CODE_CODE_B, - checkSum, - checkWeight, - i - ) - } - - Latch.C -> { - charset = Charset.C - addPattern( - patterns, - if (i == 0) CODE_START_C else CODE_CODE_C, - checkSum, - checkWeight, - i - ) - } - - Latch.SHIFT -> addPattern(patterns, CODE_SHIFT, checkSum, checkWeight, i) - else -> {} - } - if (charset == Charset.C) { - if (contents[i] == ESCAPE_FNC_1) { - addPattern(patterns, CODE_FNC_1, checkSum, checkWeight, i) - } else { - addPattern( - patterns, - contents.substring(i, i + 2).toInt(), - checkSum, - checkWeight, - i - ) - require(i + 1 < length) //the algorithm never leads to a single trailing digit in character set C - - if (i + 1 < length) { - i++ - } - } - } else { // charset A or B - var patternIndex: Int - patternIndex = - when (contents[i]) { - ESCAPE_FNC_1 -> CODE_FNC_1 - ESCAPE_FNC_2 -> CODE_FNC_2 - ESCAPE_FNC_3 -> CODE_FNC_3 - ESCAPE_FNC_4 -> if (charset == Charset.A && latch != Latch.SHIFT || - charset == Charset.B && latch == Latch.SHIFT - ) { - CODE_FNC_4_A - } else { - CODE_FNC_4_B - } - - else -> contents[i].code - ' '.code - } - if ((charset == Charset.A && latch != Latch.SHIFT || - charset == Charset.B && latch == Latch.SHIFT) && - patternIndex < 0 - ) { - patternIndex += '`'.code - } - addPattern(patterns, patternIndex, checkSum, checkWeight, i) - } - i++ - } - memoizedCost = null - minPath = null - return produceResult(patterns, checkSum[0]) - } - - private fun canEncode(contents: CharSequence, charset: Charset, position: Int): Boolean { - val c = contents[position] - return when (charset) { - Charset.A -> c == ESCAPE_FNC_1 || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4 || A.indexOf( - c - ) >= 0 - - Charset.B -> c == ESCAPE_FNC_1 || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4 || B.indexOf( - c - ) >= 0 - - Charset.C -> c == ESCAPE_FNC_1 || position + 1 < contents.length && - isDigit(c) && isDigit(contents[position + 1]) - - else -> false - } - } - - /** - * Encode the string starting at position position starting with the character set charset - */ - private fun encode(contents: CharSequence, charset: Charset, position: Int): Int { - require(position < contents.length) - val mCost = memoizedCost!![charset.ordinal][position] - if (mCost > 0) { - return mCost - } - var minCost = Int.MAX_VALUE - var minLatch: Latch? = Latch.NONE - val atEnd = position + 1 >= contents.length - val sets = arrayOf(Charset.A, Charset.B) - for (i in 0..1) { - if (canEncode(contents, sets[i], position)) { - var cost = 1 - var latch = Latch.NONE - if (charset != sets[i]) { - cost++ - latch = Latch.valueOf( - sets[i].toString() - ) - } - if (!atEnd) { - cost += encode(contents, sets[i], position + 1) - } - if (cost < minCost) { - minCost = cost - minLatch = latch - } - cost = 1 - if (charset == sets[(i + 1) % 2]) { - cost++ - latch = Latch.SHIFT - if (!atEnd) { - cost += encode(contents, charset, position + 1) - } - if (cost < minCost) { - minCost = cost - minLatch = latch - } - } - } - } - if (canEncode(contents, Charset.C, position)) { - var cost = 1 - var latch = Latch.NONE - if (charset != Charset.C) { - cost++ - latch = Latch.C - } - val advance = if (contents[position] == ESCAPE_FNC_1) 1 else 2 - if (position + advance < contents.length) { - cost += encode(contents, Charset.C, position + advance) - } - if (cost < minCost) { - minCost = cost - minLatch = latch - } - } - if (minCost == Int.MAX_VALUE) { - throw IllegalArgumentException("Bad character in input: ASCII value=" + contents[position].code) - } - memoizedCost!![charset.ordinal][position] = minCost - minPath!![charset.ordinal][position] = minLatch - return minCost - } - - companion object { - const val A = - " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u0000\u0001\u0002" + - "\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000B\u000C\r\u000E\u000F\u0010\u0011" + - "\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F" + - "\u00FF" - const val B = - " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr" + - "stuvwxyz{|}~\u007F\u00FF" - private const val CODE_SHIFT = 98 - - private fun addPattern( - patterns: MutableCollection, - patternIndex: Int, - checkSum: IntArray, - checkWeight: IntArray, - position: Int, - ) { - patterns.add(CODE_PATTERNS.get(patternIndex)) - if (position != 0) { - checkWeight[0]++ - } - checkSum[0] += patternIndex * checkWeight[0] - } - - private fun isDigit(c: Char): Boolean { - return c in '0'..'9' - } - } - } - - - companion object { - - private const val CODE_START_A = 103 - private const val CODE_START_B = 104 - private const val CODE_START_C = 105 - internal const val CODE_CODE_A = 101 - internal const val CODE_CODE_B = 100 - internal const val CODE_CODE_C = 99 - private const val CODE_STOP = 106 - - // Dummy characters used to specify control characters in input - private const val ESCAPE_FNC_1 = '\u00f1' - private const val ESCAPE_FNC_2 = '\u00f2' - private const val ESCAPE_FNC_3 = '\u00f3' - private const val ESCAPE_FNC_4 = '\u00f4' - private const val CODE_FNC_1 = 102 // Code A, Code B, Code C - private const val CODE_FNC_2 = 97 // Code A, Code B - private const val CODE_FNC_3 = 96 // Code A, Code B - private const val CODE_FNC_4_A = 101 // Code A - private const val CODE_FNC_4_B = 100 // Code B - - private fun check(contents: String, codeSet : Code128Painter.CodeSet?): Int { - - // Check content - val length = contents.length - for (i in 0 until length) { - val c = contents[i] - when (c) { - ESCAPE_FNC_1, ESCAPE_FNC_2, ESCAPE_FNC_3, ESCAPE_FNC_4 -> {} - else -> if (c.code > 127) { - // no full Latin-1 character set available at the moment - // shift and manual code change are not supported - throw IllegalArgumentException("Bad character in input: ASCII value=" + c.code) - } - } - when (codeSet) { - Code128Painter.CodeSet.A -> // allows no ascii above 95 (no lower caps, no special symbols) - if (c.code in 96..127) { - throw IllegalArgumentException("Bad character in input for forced code set A: ASCII value=" + c.code) - } - - Code128Painter.CodeSet.B -> // allows no ascii below 32 (terminal symbols) - if (c.code < 32) { - throw IllegalArgumentException("Bad character in input for forced code set B: ASCII value=" + c.code) - } - - Code128Painter.CodeSet.C -> // allows only numbers and no FNC 2/3/4 - if (c.code < 48 || c.code in 58..127 || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4) { - throw IllegalArgumentException("Bad character in input for forced code set C: ASCII value=" + c.code) - } - - else -> {} - } - } - return codeSet?.v ?: -1 - } - - private fun encodeFast(contents: String, forcedCodeSet: Int): BooleanArray { - val length = contents.length - val patterns: MutableCollection = ArrayList() // temporary storage for patterns - var checkSum = 0 - var checkWeight = 1 - var codeSet = 0 // selected code (CODE_CODE_B or CODE_CODE_C) - var position = 0 // position in contents - while (position < length) { - //Select code to use - var newCodeSet: Int - newCodeSet = if (forcedCodeSet == -1) { - chooseCode(contents, position, codeSet) - } else { - forcedCodeSet - } - - //Get the pattern index - var patternIndex: Int - if (newCodeSet == codeSet) { - // Encode the current character - // First handle escapes - when (contents[position]) { - ESCAPE_FNC_1 -> patternIndex = CODE_FNC_1 - ESCAPE_FNC_2 -> patternIndex = CODE_FNC_2 - ESCAPE_FNC_3 -> patternIndex = CODE_FNC_3 - ESCAPE_FNC_4 -> patternIndex = if (codeSet == CODE_CODE_A) { - CODE_FNC_4_A - } else { - CODE_FNC_4_B - } - - else -> when (codeSet) { - CODE_CODE_A -> { - patternIndex = contents[position].code - ' '.code - if (patternIndex < 0) { - // everything below a space character comes behind the underscore in the code patterns table - patternIndex += '`'.code - } - } - - CODE_CODE_B -> patternIndex = contents[position].code - ' '.code - else -> { - // CODE_CODE_C - if (position + 1 == length) { - // this is the last character, but the encoding is C, which always encodes two characers - throw IllegalArgumentException("Bad number of characters for digit only encoding.") - } - patternIndex = contents.substring(position, position + 2).toInt() - position++ // Also incremented below - } - } - } - position++ - } else { - // Should we change the current code? - // Do we have a code set? - patternIndex = if (codeSet == 0) { - // No, we don't have a code set - when (newCodeSet) { - CODE_CODE_A -> CODE_START_A - CODE_CODE_B -> CODE_START_B - else -> CODE_START_C - } - } else { - // Yes, we have a code set - newCodeSet - } - codeSet = newCodeSet - } - - // Get the pattern - patterns.add(CODE_PATTERNS[patternIndex]) - - // Compute checksum - checkSum += patternIndex * checkWeight - if (position != 0) { - checkWeight++ - } - } - return produceResult(patterns, checkSum) - } - - fun produceResult(patterns: MutableCollection, checkSum: Int): BooleanArray { - // Compute and append checksum - val checkSumMod = checkSum % 103 - if (checkSumMod < 0) { - throw IllegalArgumentException("Unable to compute a valid input checksum") - } - patterns.add(CODE_PATTERNS[checkSumMod]) - - // Append stop code - patterns.add(CODE_PATTERNS[CODE_STOP]) - - // Compute code width - var codeWidth = 0 - for (pattern in patterns) { - for (width in pattern) { - codeWidth += width - } - } - - // Compute result - val result = BooleanArray(codeWidth) - var pos = 0 - for (pattern in patterns) { - pos += appendPattern(result, pos, pattern, true) - } - return result - } - - private fun findCType(value: CharSequence, start: Int): CType { - val last = value.length - if (start >= last) { - return CType.UNCODABLE - } - var c = value[start] - if (c == ESCAPE_FNC_1) { - return CType.FNC_1 - } - if (c < '0' || c > '9') { - return CType.UNCODABLE - } - if (start + 1 >= last) { - return CType.ONE_DIGIT - } - c = value[start + 1] - return if (c < '0' || c > '9') { - CType.ONE_DIGIT - } else CType.TWO_DIGITS - } - - private fun chooseCode(value: CharSequence, start: Int, oldCode: Int): Int { - var lookahead = findCType(value, start) - if (lookahead == CType.ONE_DIGIT) { - return if (oldCode == CODE_CODE_A) { - CODE_CODE_A - } else CODE_CODE_B - } - if (lookahead == CType.UNCODABLE) { - if (start < value.length) { - val c = value[start] - if (c < ' ' || oldCode == CODE_CODE_A && (c < '`' || c >= ESCAPE_FNC_1 && c <= ESCAPE_FNC_4)) { - // can continue in code A, encodes ASCII 0 to 95 or FNC1 to FNC4 - return CODE_CODE_A - } - } - return CODE_CODE_B // no choice - } - if (oldCode == CODE_CODE_A && lookahead == CType.FNC_1) { - return CODE_CODE_A - } - if (oldCode == CODE_CODE_C) { // can continue in code C - return CODE_CODE_C - } - if (oldCode == CODE_CODE_B) { - if (lookahead == CType.FNC_1) { - return CODE_CODE_B // can continue in code B - } - // Seen two consecutive digits, see what follows - lookahead = findCType(value, start + 2) - if (lookahead == CType.UNCODABLE || lookahead == CType.ONE_DIGIT) { - return CODE_CODE_B // not worth switching now - } - if (lookahead == CType.FNC_1) { // two digits, then FNC_1... - lookahead = findCType(value, start + 3) - return if (lookahead == CType.TWO_DIGITS) { // then two more digits, switch - CODE_CODE_C - } else { - CODE_CODE_B // otherwise not worth switching - } - } - // At this point, there are at least 4 consecutive digits. - // Look ahead to choose whether to switch now or on the next round. - var index = start + 4 - while (findCType(value, index).also { lookahead = it } == CType.TWO_DIGITS) { - index += 2 - } - return if (lookahead == CType.ONE_DIGIT) { // odd number of digits, switch later - CODE_CODE_B - } else CODE_CODE_C - // even number of digits, switch now - } - // Here oldCode == 0, which means we are choosing the initial code - if (lookahead == CType.FNC_1) { // ignore FNC_1 - lookahead = findCType(value, start + 1) - } - return if (lookahead == CType.TWO_DIGITS) { // at least two digits, start in code C - CODE_CODE_C - } else CODE_CODE_B - } - } -} - -internal fun appendPattern( - target: BooleanArray, - pos: Int, - pattern: IntArray, - startColor: Boolean, -): Int { - var pos = pos - var color = startColor - var numAdded = 0 - for (len in pattern) { - for (j in 0 until len) { - target[pos++] = color - } - numAdded += len - color = !color // flip color after each segment - } - return numAdded -} - -private val CODE_PATTERNS by lazy { - arrayOf( - intArrayOf(2, 1, 2, 2, 2, 2), - intArrayOf(2, 2, 2, 1, 2, 2), - intArrayOf(2, 2, 2, 2, 2, 1), - intArrayOf(1, 2, 1, 2, 2, 3), - intArrayOf(1, 2, 1, 3, 2, 2), - intArrayOf(1, 3, 1, 2, 2, 2), - intArrayOf(1, 2, 2, 2, 1, 3), - intArrayOf(1, 2, 2, 3, 1, 2), - intArrayOf(1, 3, 2, 2, 1, 2), - intArrayOf(2, 2, 1, 2, 1, 3), - intArrayOf(2, 2, 1, 3, 1, 2), - intArrayOf(2, 3, 1, 2, 1, 2), - intArrayOf(1, 1, 2, 2, 3, 2), - intArrayOf(1, 2, 2, 1, 3, 2), - intArrayOf(1, 2, 2, 2, 3, 1), - intArrayOf(1, 1, 3, 2, 2, 2), - intArrayOf(1, 2, 3, 1, 2, 2), - intArrayOf(1, 2, 3, 2, 2, 1), - intArrayOf(2, 2, 3, 2, 1, 1), - intArrayOf(2, 2, 1, 1, 3, 2), - intArrayOf(2, 2, 1, 2, 3, 1), - intArrayOf(2, 1, 3, 2, 1, 2), - intArrayOf(2, 2, 3, 1, 1, 2), - intArrayOf(3, 1, 2, 1, 3, 1), - intArrayOf(3, 1, 1, 2, 2, 2), - intArrayOf(3, 2, 1, 1, 2, 2), - intArrayOf(3, 2, 1, 2, 2, 1), - intArrayOf(3, 1, 2, 2, 1, 2), - intArrayOf(3, 2, 2, 1, 1, 2), - intArrayOf(3, 2, 2, 2, 1, 1), - intArrayOf(2, 1, 2, 1, 2, 3), - intArrayOf(2, 1, 2, 3, 2, 1), - intArrayOf(2, 3, 2, 1, 2, 1), - intArrayOf(1, 1, 1, 3, 2, 3), - intArrayOf(1, 3, 1, 1, 2, 3), - intArrayOf(1, 3, 1, 3, 2, 1), - intArrayOf(1, 1, 2, 3, 1, 3), - intArrayOf(1, 3, 2, 1, 1, 3), - intArrayOf(1, 3, 2, 3, 1, 1), - intArrayOf(2, 1, 1, 3, 1, 3), - intArrayOf(2, 3, 1, 1, 1, 3), - intArrayOf(2, 3, 1, 3, 1, 1), - intArrayOf(1, 1, 2, 1, 3, 3), - intArrayOf(1, 1, 2, 3, 3, 1), - intArrayOf(1, 3, 2, 1, 3, 1), - intArrayOf(1, 1, 3, 1, 2, 3), - intArrayOf(1, 1, 3, 3, 2, 1), - intArrayOf(1, 3, 3, 1, 2, 1), - intArrayOf(3, 1, 3, 1, 2, 1), - intArrayOf(2, 1, 1, 3, 3, 1), - intArrayOf(2, 3, 1, 1, 3, 1), - intArrayOf(2, 1, 3, 1, 1, 3), - intArrayOf(2, 1, 3, 3, 1, 1), - intArrayOf(2, 1, 3, 1, 3, 1), - intArrayOf(3, 1, 1, 1, 2, 3), - intArrayOf(3, 1, 1, 3, 2, 1), - intArrayOf(3, 3, 1, 1, 2, 1), - intArrayOf(3, 1, 2, 1, 1, 3), - intArrayOf(3, 1, 2, 3, 1, 1), - intArrayOf(3, 3, 2, 1, 1, 1), - intArrayOf(3, 1, 4, 1, 1, 1), - intArrayOf(2, 2, 1, 4, 1, 1), - intArrayOf(4, 3, 1, 1, 1, 1), - intArrayOf(1, 1, 1, 2, 2, 4), - intArrayOf(1, 1, 1, 4, 2, 2), - intArrayOf(1, 2, 1, 1, 2, 4), - intArrayOf(1, 2, 1, 4, 2, 1), - intArrayOf(1, 4, 1, 1, 2, 2), - intArrayOf(1, 4, 1, 2, 2, 1), - intArrayOf(1, 1, 2, 2, 1, 4), - intArrayOf(1, 1, 2, 4, 1, 2), - intArrayOf(1, 2, 2, 1, 1, 4), - intArrayOf(1, 2, 2, 4, 1, 1), - intArrayOf(1, 4, 2, 1, 1, 2), - intArrayOf(1, 4, 2, 2, 1, 1), - intArrayOf(2, 4, 1, 2, 1, 1), - intArrayOf(2, 2, 1, 1, 1, 4), - intArrayOf(4, 1, 3, 1, 1, 1), - intArrayOf(2, 4, 1, 1, 1, 2), - intArrayOf(1, 3, 4, 1, 1, 1), - intArrayOf(1, 1, 1, 2, 4, 2), - intArrayOf(1, 2, 1, 1, 4, 2), - intArrayOf(1, 2, 1, 2, 4, 1), - intArrayOf(1, 1, 4, 2, 1, 2), - intArrayOf(1, 2, 4, 1, 1, 2), - intArrayOf(1, 2, 4, 2, 1, 1), - intArrayOf(4, 1, 1, 2, 1, 2), - intArrayOf(4, 2, 1, 1, 1, 2), - intArrayOf(4, 2, 1, 2, 1, 1), - intArrayOf(2, 1, 2, 1, 4, 1), - intArrayOf(2, 1, 4, 1, 2, 1), - intArrayOf(4, 1, 2, 1, 2, 1), - intArrayOf(1, 1, 1, 1, 4, 3), - intArrayOf(1, 1, 1, 3, 4, 1), - intArrayOf(1, 3, 1, 1, 4, 1), - intArrayOf(1, 1, 4, 1, 1, 3), - intArrayOf(1, 1, 4, 3, 1, 1), - intArrayOf(4, 1, 1, 1, 1, 3), - intArrayOf(4, 1, 1, 3, 1, 1), - intArrayOf(1, 1, 3, 1, 4, 1), - intArrayOf(1, 1, 4, 1, 3, 1), - intArrayOf(3, 1, 1, 1, 4, 1), - intArrayOf(4, 1, 1, 1, 3, 1), - intArrayOf(2, 1, 1, 4, 1, 2), - intArrayOf(2, 1, 1, 2, 1, 4), - intArrayOf(2, 1, 1, 2, 3, 2), - intArrayOf(2, 3, 3, 1, 1, 1, 2) - ) -} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Encoder.kt similarity index 59% rename from qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt rename to qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Encoder.kt index 388ffc0..77f6874 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128.kt +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Encoder.kt @@ -1,6 +1,6 @@ package io.github.alexzhirkevich.qrose.oned -internal class Code128 { +internal object Code128Encoder : BarcodeEncoder { // Results of minimal lookahead for code C private enum class CType { UNCODABLE, @@ -9,8 +9,15 @@ internal class Code128 { FNC_1 } + override fun encode(data: String): BooleanArray { + return encode(contents = data) + } - fun encode(contents: String, compact : Boolean = true , codeSet : Code128Painter.CodeSet? = null): BooleanArray { + fun encode( + contents: String, + compact: Boolean = true, + codeSet: Code128Type? = null + ): BooleanArray { val forcedCodeSet = check(contents, codeSet) return if (compact) { @@ -265,253 +272,250 @@ internal class Code128 { } - companion object { - - private const val CODE_START_A = 103 - private const val CODE_START_B = 104 - private const val CODE_START_C = 105 - internal const val CODE_CODE_A = 101 - internal const val CODE_CODE_B = 100 - internal const val CODE_CODE_C = 99 - private const val CODE_STOP = 106 - - // Dummy characters used to specify control characters in input - private const val ESCAPE_FNC_1 = '\u00f1' - private const val ESCAPE_FNC_2 = '\u00f2' - private const val ESCAPE_FNC_3 = '\u00f3' - private const val ESCAPE_FNC_4 = '\u00f4' - private const val CODE_FNC_1 = 102 // Code A, Code B, Code C - private const val CODE_FNC_2 = 97 // Code A, Code B - private const val CODE_FNC_3 = 96 // Code A, Code B - private const val CODE_FNC_4_A = 101 // Code A - private const val CODE_FNC_4_B = 100 // Code B - - private fun check(contents: String, codeSet : Code128Painter.CodeSet?): Int { - - // Check content - val length = contents.length - for (i in 0 until length) { - val c = contents[i] - when (c) { - ESCAPE_FNC_1, ESCAPE_FNC_2, ESCAPE_FNC_3, ESCAPE_FNC_4 -> {} - else -> if (c.code > 127) { - // no full Latin-1 character set available at the moment - // shift and manual code change are not supported - throw IllegalArgumentException("Bad character in input: ASCII value=" + c.code) - } + private const val CODE_START_A = 103 + private const val CODE_START_B = 104 + private const val CODE_START_C = 105 + internal const val CODE_CODE_A = 101 + internal const val CODE_CODE_B = 100 + internal const val CODE_CODE_C = 99 + private const val CODE_STOP = 106 + + // Dummy characters used to specify control characters in input + private const val ESCAPE_FNC_1 = '\u00f1' + private const val ESCAPE_FNC_2 = '\u00f2' + private const val ESCAPE_FNC_3 = '\u00f3' + private const val ESCAPE_FNC_4 = '\u00f4' + private const val CODE_FNC_1 = 102 // Code A, Code B, Code C + private const val CODE_FNC_2 = 97 // Code A, Code B + private const val CODE_FNC_3 = 96 // Code A, Code B + private const val CODE_FNC_4_A = 101 // Code A + private const val CODE_FNC_4_B = 100 // Code B + + private fun check(contents: String, codeSet: Code128Type?): Int { + + // Check content + val length = contents.length + for (i in 0 until length) { + val c = contents[i] + when (c) { + ESCAPE_FNC_1, ESCAPE_FNC_2, ESCAPE_FNC_3, ESCAPE_FNC_4 -> {} + else -> if (c.code > 127) { + // no full Latin-1 character set available at the moment + // shift and manual code change are not supported + throw IllegalArgumentException("Bad character in input: ASCII value=" + c.code) } - when (codeSet) { - Code128Painter.CodeSet.A -> // allows no ascii above 95 (no lower caps, no special symbols) - if (c.code in 96..127) { - throw IllegalArgumentException("Bad character in input for forced code set A: ASCII value=" + c.code) - } + } + when (codeSet) { + Code128Type.A -> // allows no ascii above 95 (no lower caps, no special symbols) + if (c.code in 96..127) { + throw IllegalArgumentException("Bad character in input for forced code set A: ASCII value=" + c.code) + } - Code128Painter.CodeSet.B -> // allows no ascii below 32 (terminal symbols) - if (c.code < 32) { - throw IllegalArgumentException("Bad character in input for forced code set B: ASCII value=" + c.code) - } + Code128Type.B -> // allows no ascii below 32 (terminal symbols) + if (c.code < 32) { + throw IllegalArgumentException("Bad character in input for forced code set B: ASCII value=" + c.code) + } - Code128Painter.CodeSet.C -> // allows only numbers and no FNC 2/3/4 - if (c.code < 48 || c.code in 58..127 || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4) { - throw IllegalArgumentException("Bad character in input for forced code set C: ASCII value=" + c.code) - } + Code128Type.C -> // allows only numbers and no FNC 2/3/4 + if (c.code < 48 || c.code in 58..127 || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4) { + throw IllegalArgumentException("Bad character in input for forced code set C: ASCII value=" + c.code) + } - else -> {} - } + else -> {} } - return codeSet?.v ?: -1 } + return codeSet?.v ?: -1 + } - private fun encodeFast(contents: String, forcedCodeSet: Int): BooleanArray { - val length = contents.length - val patterns: MutableCollection = ArrayList() // temporary storage for patterns - var checkSum = 0 - var checkWeight = 1 - var codeSet = 0 // selected code (CODE_CODE_B or CODE_CODE_C) - var position = 0 // position in contents - while (position < length) { - //Select code to use - var newCodeSet: Int - newCodeSet = if (forcedCodeSet == -1) { - chooseCode(contents, position, codeSet) - } else { - forcedCodeSet - } + private fun encodeFast(contents: String, forcedCodeSet: Int): BooleanArray { + val length = contents.length + val patterns: MutableCollection = ArrayList() // temporary storage for patterns + var checkSum = 0 + var checkWeight = 1 + var codeSet = 0 // selected code (CODE_CODE_B or CODE_CODE_C) + var position = 0 // position in contents + while (position < length) { + //Select code to use + var newCodeSet: Int + newCodeSet = if (forcedCodeSet == -1) { + chooseCode(contents, position, codeSet) + } else { + forcedCodeSet + } - //Get the pattern index - var patternIndex: Int - if (newCodeSet == codeSet) { - // Encode the current character - // First handle escapes - when (contents[position]) { - ESCAPE_FNC_1 -> patternIndex = CODE_FNC_1 - ESCAPE_FNC_2 -> patternIndex = CODE_FNC_2 - ESCAPE_FNC_3 -> patternIndex = CODE_FNC_3 - ESCAPE_FNC_4 -> patternIndex = if (codeSet == CODE_CODE_A) { - CODE_FNC_4_A - } else { - CODE_FNC_4_B - } + //Get the pattern index + var patternIndex: Int + if (newCodeSet == codeSet) { + // Encode the current character + // First handle escapes + when (contents[position]) { + ESCAPE_FNC_1 -> patternIndex = CODE_FNC_1 + ESCAPE_FNC_2 -> patternIndex = CODE_FNC_2 + ESCAPE_FNC_3 -> patternIndex = CODE_FNC_3 + ESCAPE_FNC_4 -> patternIndex = if (codeSet == CODE_CODE_A) { + CODE_FNC_4_A + } else { + CODE_FNC_4_B + } - else -> when (codeSet) { - CODE_CODE_A -> { - patternIndex = contents[position].code - ' '.code - if (patternIndex < 0) { - // everything below a space character comes behind the underscore in the code patterns table - patternIndex += '`'.code - } + else -> when (codeSet) { + CODE_CODE_A -> { + patternIndex = contents[position].code - ' '.code + if (patternIndex < 0) { + // everything below a space character comes behind the underscore in the code patterns table + patternIndex += '`'.code } + } - CODE_CODE_B -> patternIndex = contents[position].code - ' '.code - else -> { - // CODE_CODE_C - if (position + 1 == length) { - // this is the last character, but the encoding is C, which always encodes two characers - throw IllegalArgumentException("Bad number of characters for digit only encoding.") - } - patternIndex = contents.substring(position, position + 2).toInt() - position++ // Also incremented below + CODE_CODE_B -> patternIndex = contents[position].code - ' '.code + else -> { + // CODE_CODE_C + if (position + 1 == length) { + // this is the last character, but the encoding is C, which always encodes two characers + throw IllegalArgumentException("Bad number of characters for digit only encoding.") } + patternIndex = contents.substring(position, position + 2).toInt() + position++ // Also incremented below } } - position++ - } else { - // Should we change the current code? - // Do we have a code set? - patternIndex = if (codeSet == 0) { - // No, we don't have a code set - when (newCodeSet) { - CODE_CODE_A -> CODE_START_A - CODE_CODE_B -> CODE_START_B - else -> CODE_START_C - } - } else { - // Yes, we have a code set - newCodeSet + } + position++ + } else { + // Should we change the current code? + // Do we have a code set? + patternIndex = if (codeSet == 0) { + // No, we don't have a code set + when (newCodeSet) { + CODE_CODE_A -> CODE_START_A + CODE_CODE_B -> CODE_START_B + else -> CODE_START_C } - codeSet = newCodeSet + } else { + // Yes, we have a code set + newCodeSet } + codeSet = newCodeSet + } - // Get the pattern - patterns.add(CODE_PATTERNS[patternIndex]) + // Get the pattern + patterns.add(CODE_PATTERNS[patternIndex]) - // Compute checksum - checkSum += patternIndex * checkWeight - if (position != 0) { - checkWeight++ - } + // Compute checksum + checkSum += patternIndex * checkWeight + if (position != 0) { + checkWeight++ } - return produceResult(patterns, checkSum) } + return produceResult(patterns, checkSum) + } - fun produceResult(patterns: MutableCollection, checkSum: Int): BooleanArray { - // Compute and append checksum - val checkSumMod = checkSum % 103 - if (checkSumMod < 0) { - throw IllegalArgumentException("Unable to compute a valid input checksum") - } - patterns.add(CODE_PATTERNS[checkSumMod]) + fun produceResult(patterns: MutableCollection, checkSum: Int): BooleanArray { + // Compute and append checksum + val checkSumMod = checkSum % 103 + if (checkSumMod < 0) { + throw IllegalArgumentException("Unable to compute a valid input checksum") + } + patterns.add(CODE_PATTERNS[checkSumMod]) - // Append stop code - patterns.add(CODE_PATTERNS[CODE_STOP]) + // Append stop code + patterns.add(CODE_PATTERNS[CODE_STOP]) - // Compute code width - var codeWidth = 0 - for (pattern in patterns) { - for (width in pattern) { - codeWidth += width - } + // Compute code width + var codeWidth = 0 + for (pattern in patterns) { + for (width in pattern) { + codeWidth += width } + } - // Compute result - val result = BooleanArray(codeWidth) - var pos = 0 - for (pattern in patterns) { - pos += appendPattern(result, pos, pattern, true) - } - return result + // Compute result + val result = BooleanArray(codeWidth) + var pos = 0 + for (pattern in patterns) { + pos += appendPattern(result, pos, pattern, true) } + return result + } - private fun findCType(value: CharSequence, start: Int): CType { - val last = value.length - if (start >= last) { - return CType.UNCODABLE - } - var c = value[start] - if (c == ESCAPE_FNC_1) { - return CType.FNC_1 - } - if (c < '0' || c > '9') { - return CType.UNCODABLE - } - if (start + 1 >= last) { - return CType.ONE_DIGIT - } - c = value[start + 1] - return if (c < '0' || c > '9') { - CType.ONE_DIGIT - } else CType.TWO_DIGITS + private fun findCType(value: CharSequence, start: Int): CType { + val last = value.length + if (start >= last) { + return CType.UNCODABLE + } + var c = value[start] + if (c == ESCAPE_FNC_1) { + return CType.FNC_1 + } + if (c < '0' || c > '9') { + return CType.UNCODABLE + } + if (start + 1 >= last) { + return CType.ONE_DIGIT } + c = value[start + 1] + return if (c < '0' || c > '9') { + CType.ONE_DIGIT + } else CType.TWO_DIGITS + } - private fun chooseCode(value: CharSequence, start: Int, oldCode: Int): Int { - var lookahead = findCType(value, start) - if (lookahead == CType.ONE_DIGIT) { - return if (oldCode == CODE_CODE_A) { - CODE_CODE_A - } else CODE_CODE_B - } - if (lookahead == CType.UNCODABLE) { - if (start < value.length) { - val c = value[start] - if (c < ' ' || oldCode == CODE_CODE_A && (c < '`' || c >= ESCAPE_FNC_1 && c <= ESCAPE_FNC_4)) { - // can continue in code A, encodes ASCII 0 to 95 or FNC1 to FNC4 - return CODE_CODE_A - } + private fun chooseCode(value: CharSequence, start: Int, oldCode: Int): Int { + var lookahead = findCType(value, start) + if (lookahead == CType.ONE_DIGIT) { + return if (oldCode == CODE_CODE_A) { + CODE_CODE_A + } else CODE_CODE_B + } + if (lookahead == CType.UNCODABLE) { + if (start < value.length) { + val c = value[start] + if (c < ' ' || oldCode == CODE_CODE_A && (c < '`' || c >= ESCAPE_FNC_1 && c <= ESCAPE_FNC_4)) { + // can continue in code A, encodes ASCII 0 to 95 or FNC1 to FNC4 + return CODE_CODE_A } - return CODE_CODE_B // no choice } - if (oldCode == CODE_CODE_A && lookahead == CType.FNC_1) { - return CODE_CODE_A + return CODE_CODE_B // no choice + } + if (oldCode == CODE_CODE_A && lookahead == CType.FNC_1) { + return CODE_CODE_A + } + if (oldCode == CODE_CODE_C) { // can continue in code C + return CODE_CODE_C + } + if (oldCode == CODE_CODE_B) { + if (lookahead == CType.FNC_1) { + return CODE_CODE_B // can continue in code B } - if (oldCode == CODE_CODE_C) { // can continue in code C - return CODE_CODE_C + // Seen two consecutive digits, see what follows + lookahead = findCType(value, start + 2) + if (lookahead == CType.UNCODABLE || lookahead == CType.ONE_DIGIT) { + return CODE_CODE_B // not worth switching now } - if (oldCode == CODE_CODE_B) { - if (lookahead == CType.FNC_1) { - return CODE_CODE_B // can continue in code B - } - // Seen two consecutive digits, see what follows - lookahead = findCType(value, start + 2) - if (lookahead == CType.UNCODABLE || lookahead == CType.ONE_DIGIT) { - return CODE_CODE_B // not worth switching now - } - if (lookahead == CType.FNC_1) { // two digits, then FNC_1... - lookahead = findCType(value, start + 3) - return if (lookahead == CType.TWO_DIGITS) { // then two more digits, switch - CODE_CODE_C - } else { - CODE_CODE_B // otherwise not worth switching - } - } - // At this point, there are at least 4 consecutive digits. - // Look ahead to choose whether to switch now or on the next round. - var index = start + 4 - while (findCType(value, index).also { lookahead = it } == CType.TWO_DIGITS) { - index += 2 + if (lookahead == CType.FNC_1) { // two digits, then FNC_1... + lookahead = findCType(value, start + 3) + return if (lookahead == CType.TWO_DIGITS) { // then two more digits, switch + CODE_CODE_C + } else { + CODE_CODE_B // otherwise not worth switching } - return if (lookahead == CType.ONE_DIGIT) { // odd number of digits, switch later - CODE_CODE_B - } else CODE_CODE_C - // even number of digits, switch now } - // Here oldCode == 0, which means we are choosing the initial code - if (lookahead == CType.FNC_1) { // ignore FNC_1 - lookahead = findCType(value, start + 1) + // At this point, there are at least 4 consecutive digits. + // Look ahead to choose whether to switch now or on the next round. + var index = start + 4 + while (findCType(value, index).also { lookahead = it } == CType.TWO_DIGITS) { + index += 2 } - return if (lookahead == CType.TWO_DIGITS) { // at least two digits, start in code C - CODE_CODE_C - } else CODE_CODE_B + return if (lookahead == CType.ONE_DIGIT) { // odd number of digits, switch later + CODE_CODE_B + } else CODE_CODE_C + // even number of digits, switch now + } + // Here oldCode == 0, which means we are choosing the initial code + if (lookahead == CType.FNC_1) { // ignore FNC_1 + lookahead = findCType(value, start + 1) } + return if (lookahead == CType.TWO_DIGITS) { // at least two digits, start in code C + CODE_CODE_C + } else CODE_CODE_B } } diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt index 6342381..d9098a0 100644 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt @@ -1,7 +1,7 @@ package io.github.alexzhirkevich.qrose.oned import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState @@ -9,37 +9,36 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity /** * Code 128 barcode painter * * @param brush code brush - * @param compact Specifies whether to use compact mode for Code-128 code. This can yield slightly smaller bar codes. This option and [forceCodeSet] are mutually exclusive. - * @param forceCodeSet Forces which encoding will be used. Currently only used for Code-128 code sets. This option and CODE128_COMPACT are mutually exclusive - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list + * @param compact Specifies whether to use compact mode for Code-128 code. + * This can yield slightly smaller bar codes. This option and [forceCodeSet] are mutually exclusive. + * @param forceCodeSet Forces which encoding will be used. This option and [compact] are mutually exclusive. + * @param onError called when input content is invalid. + * @param builder build code path using painter size and encoded boolean list. + * + * @see Code128Painter * */ @Composable -fun rememberCode128Painter( +internal fun rememberCode128Painter( data: String, brush: Brush = SolidColor(Color.Black), compact : Boolean = true, - forceCodeSet : Code128Painter.CodeSet? = null, + forceCodeSet : Code128Type? = null, onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder + builder : BarcodePathBuilder = ::defaultBarcodeBuilder ) : Painter { - val density = LocalDensity.current - val updatedBuilder by rememberUpdatedState(builder) - return remember(density, data, brush, forceCodeSet) { + return remember(data, brush, forceCodeSet) { runCatching { Code128Painter( data = data, - density = density.density, brush = brush, compact = compact, codeSet = forceCodeSet, @@ -51,25 +50,21 @@ fun rememberCode128Painter( } } +enum class Code128Type(internal val v: Int) { + A(Code128Encoder.CODE_CODE_A), + B(Code128Encoder.CODE_CODE_B), + C(Code128Encoder.CODE_CODE_C) +} -@Immutable -class Code128Painter( - val data : String, - val brush: Brush = SolidColor(Color.Black), - val compact : Boolean = true, - val codeSet : CodeSet? = null, - density: Float, - builder : BarcodePathBuilder= ::defaultOneDBarcodeBuilder -) : SingleDimensionBarcodePainter( - density = density, - code = Code128().encode(data, compact, codeSet).toList(), +@Stable +fun Code128Painter( + data : String, + brush: Brush = SolidColor(Color.Black), + compact : Boolean = true, + codeSet : Code128Type? = null, + builder : BarcodePathBuilder= ::defaultBarcodeBuilder +) = BarcodePainter( + code = Code128Encoder.encode(data, compact, codeSet), brush = brush, builder = builder -) { - - enum class CodeSet(internal val v: Int) { - A(Code128.CODE_CODE_A), - B(Code128.CODE_CODE_B), - C(Code128.CODE_CODE_C) - } -} +) diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt deleted file mode 100644 index 71b55d9..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt +++ /dev/null @@ -1,121 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -internal class Code39 { - - fun encode(contents: String): BooleanArray { - var actualContent = contents - var length = actualContent.length - if (length > 80) { - throw IllegalArgumentException( - "Requested contents should be less than 80 digits long, but got $length" - ) - } - for (i in 0 until length) { - val indexInString: Int = ALPHABET_STRING.indexOf(actualContent[i]) - if (indexInString < 0) { - actualContent = tryToConvertToExtendedMode(actualContent) - length = actualContent.length - if (length > 80) { - throw IllegalArgumentException( - "Requested contents should be less than 80 digits long, but got " + - length + " (extended full ASCII mode)" - ) - } - break - } - } - val widths = IntArray(9) - val codeWidth = 24 + 1 + (13 * length) - val result = BooleanArray(codeWidth) - toIntArray(ASTERISK_ENCODING, widths) - var pos = appendPattern(result, 0, widths, true) - val narrowWhite = intArrayOf(1) - pos += appendPattern(result, pos, narrowWhite, false) - //append next character to byte matrix - for (i in 0 until length) { - val indexInString: Int = ALPHABET_STRING.indexOf(actualContent[i]) - toIntArray(CHARACTER_ENCODINGS[indexInString], widths) - pos += appendPattern(result, pos, widths, true) - pos += appendPattern(result, pos, narrowWhite, false) - } - toIntArray(ASTERISK_ENCODING, widths) - appendPattern(result, pos, widths, true) - return result - } - - companion object { - - - const val ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. \$/+%" - - val CHARACTER_ENCODINGS by lazy { - intArrayOf( - 0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, // 0-9 - // 0-9 - 0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, // A-J - // A-J - 0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, // K-T - // K-T - 0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x0A8, // U-$ - // U-$ - 0x0A2, 0x08A, 0x02A // /-% - // /-% - ) - } - - val ASTERISK_ENCODING = 0x094 - - private fun toIntArray(a: Int, toReturn: IntArray) { - for (i in 0..8) { - val temp = a and (1 shl (8 - i)) - toReturn[i] = if (temp == 0) 1 else 2 - } - } - - private fun tryToConvertToExtendedMode(contents: String): String { - val length = contents.length - val extendedContent = StringBuilder() - for (i in 0 until length) { - val character = contents[i] - when (character) { - '\u0000' -> extendedContent.append("%U") - ' ', '-', '.' -> extendedContent.append(character) - '@' -> extendedContent.append("%V") - '`' -> extendedContent.append("%W") - else -> if (character.code <= 26) { - extendedContent.append('$') - extendedContent.append(('A'.code + (character.code - 1)).toChar()) - } else if (character < ' ') { - extendedContent.append('%') - extendedContent.append(('A'.code + (character.code - 27)).toChar()) - } else if ((character <= ',') || (character == '/') || (character == ':')) { - extendedContent.append('/') - extendedContent.append(('A'.code + (character.code - 33)).toChar()) - } else if (character <= '9') { - extendedContent.append(('0'.code + (character.code - 48)).toChar()) - } else if (character <= '?') { - extendedContent.append('%') - extendedContent.append(('F'.code + (character.code - 59)).toChar()) - } else if (character <= 'Z') { - extendedContent.append(('A'.code + (character.code - 65)).toChar()) - } else if (character <= '_') { - extendedContent.append('%') - extendedContent.append(('K'.code + (character.code - 91)).toChar()) - } else if (character <= 'z') { - extendedContent.append('+') - extendedContent.append(('A'.code + (character.code - 97)).toChar()) - } else if (character.code <= 127) { - extendedContent.append('%') - extendedContent.append(('P'.code + (character.code - 123)).toChar()) - } else { - throw IllegalArgumentException( - "Requested content contains a non-encodable character: '" + contents[i] + "'" - ) - } - } - } - return extendedContent.toString() - } - } -} - diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Encoder.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Encoder.kt new file mode 100644 index 0000000..646005f --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Encoder.kt @@ -0,0 +1,118 @@ +package io.github.alexzhirkevich.qrose.oned + +internal object Code39Encoder : BarcodeEncoder { + + override fun encode(contents: String): BooleanArray { + var actualContent = contents + var length = actualContent.length + if (length > 80) { + throw IllegalArgumentException( + "Requested contents should be less than 80 digits long, but got $length" + ) + } + for (i in 0 until length) { + val indexInString: Int = ALPHABET_STRING.indexOf(actualContent[i]) + if (indexInString < 0) { + actualContent = tryToConvertToExtendedMode(actualContent) + length = actualContent.length + if (length > 80) { + throw IllegalArgumentException( + "Requested contents should be less than 80 digits long, but got " + + length + " (extended full ASCII mode)" + ) + } + break + } + } + val widths = IntArray(9) + val codeWidth = 24 + 1 + (13 * length) + val result = BooleanArray(codeWidth) + toIntArray(ASTERISK_ENCODING, widths) + var pos = appendPattern(result, 0, widths, true) + val narrowWhite = intArrayOf(1) + pos += appendPattern(result, pos, narrowWhite, false) + //append next character to byte matrix + for (i in 0 until length) { + val indexInString: Int = ALPHABET_STRING.indexOf(actualContent[i]) + toIntArray(CHARACTER_ENCODINGS[indexInString], widths) + pos += appendPattern(result, pos, widths, true) + pos += appendPattern(result, pos, narrowWhite, false) + } + toIntArray(ASTERISK_ENCODING, widths) + appendPattern(result, pos, widths, true) + return result + } + + + const val ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. \$/+%" + + val CHARACTER_ENCODINGS by lazy { + intArrayOf( + 0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, // 0-9 + // 0-9 + 0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, // A-J + // A-J + 0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, // K-T + // K-T + 0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x0A8, // U-$ + // U-$ + 0x0A2, 0x08A, 0x02A // /-% + // /-% + ) + } + + val ASTERISK_ENCODING = 0x094 + + private fun toIntArray(a: Int, toReturn: IntArray) { + for (i in 0..8) { + val temp = a and (1 shl (8 - i)) + toReturn[i] = if (temp == 0) 1 else 2 + } + } + + private fun tryToConvertToExtendedMode(contents: String): String { + val length = contents.length + val extendedContent = StringBuilder() + for (i in 0 until length) { + val character = contents[i] + when (character) { + '\u0000' -> extendedContent.append("%U") + ' ', '-', '.' -> extendedContent.append(character) + '@' -> extendedContent.append("%V") + '`' -> extendedContent.append("%W") + else -> if (character.code <= 26) { + extendedContent.append('$') + extendedContent.append(('A'.code + (character.code - 1)).toChar()) + } else if (character < ' ') { + extendedContent.append('%') + extendedContent.append(('A'.code + (character.code - 27)).toChar()) + } else if ((character <= ',') || (character == '/') || (character == ':')) { + extendedContent.append('/') + extendedContent.append(('A'.code + (character.code - 33)).toChar()) + } else if (character <= '9') { + extendedContent.append(('0'.code + (character.code - 48)).toChar()) + } else if (character <= '?') { + extendedContent.append('%') + extendedContent.append(('F'.code + (character.code - 59)).toChar()) + } else if (character <= 'Z') { + extendedContent.append(('A'.code + (character.code - 65)).toChar()) + } else if (character <= '_') { + extendedContent.append('%') + extendedContent.append(('K'.code + (character.code - 91)).toChar()) + } else if (character <= 'z') { + extendedContent.append('+') + extendedContent.append(('A'.code + (character.code - 97)).toChar()) + } else if (character.code <= 127) { + extendedContent.append('%') + extendedContent.append(('P'.code + (character.code - 123)).toChar()) + } else { + throw IllegalArgumentException( + "Requested content contains a non-encodable character: '" + contents[i] + "'" + ) + } + } + } + return extendedContent.toString() + } +} + diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt deleted file mode 100644 index 84f31e9..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt +++ /dev/null @@ -1,57 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * Code 39 barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberCode39Painter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - Code39Painter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -@Immutable -class Code39Painter( - val data : String, - val brush : Brush = SolidColor(Color.Black), - density: Float = 1f, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : SingleDimensionBarcodePainter( - density = density, - code = Code39().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt deleted file mode 100644 index df88aa9..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt +++ /dev/null @@ -1,100 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - - -internal class Code93 { - - fun encode(contents: String): BooleanArray { - var actualContents = contents - var length = actualContents.length - if (length > 80) { - throw IllegalArgumentException( - "Requested contents should be less than 80 digits long, but got $length" - ) - } - for (i in 0 until length) { - val indexInString: Int = Code39.ALPHABET_STRING.indexOf(actualContents[i]) - if (indexInString < 0) { - actualContents = tryToConvertToExtendedMode(actualContents) - length = actualContents.length - if (length > 80) { - throw IllegalArgumentException( - "Requested contents should be less than 80 digits long, but got " + - length + " (extended full ASCII mode)" - ) - } - break - } - } - val widths = IntArray(9) - val codeWidth = 24 + 1 + (13 * length) - val result = BooleanArray(codeWidth) - toIntArray(Code39.ASTERISK_ENCODING, widths) - var pos = appendPattern(result, 0, widths, true) - val narrowWhite = intArrayOf(1) - pos += appendPattern(result, pos, narrowWhite, false) - //append next character to byte matrix - for (i in 0 until length) { - val indexInString: Int = Code39.ALPHABET_STRING.indexOf(actualContents[i]) - toIntArray(Code39.CHARACTER_ENCODINGS.get(indexInString), widths) - pos += appendPattern(result, pos, widths, true) - pos += appendPattern(result, pos, narrowWhite, false) - } - toIntArray(Code39.ASTERISK_ENCODING, widths) - appendPattern(result, pos, widths, true) - return result - } - - companion object { - private fun toIntArray(a: Int, toReturn: IntArray) { - for (i in 0..8) { - val temp = a and (1 shl (8 - i)) - toReturn[i] = if (temp == 0) 1 else 2 - } - } - - private fun tryToConvertToExtendedMode(contents: String): String { - val length = contents.length - val extendedContent: StringBuilder = StringBuilder() - for (i in 0 until length) { - val character = contents[i] - when (character) { - '\u0000' -> extendedContent.append("%U") - ' ', '-', '.' -> extendedContent.append(character) - '@' -> extendedContent.append("%V") - '`' -> extendedContent.append("%W") - else -> if (character.code <= 26) { - extendedContent.append('$') - extendedContent.append(('A'.code + (character.code - 1)).toChar()) - } else if (character < ' ') { - extendedContent.append('%') - extendedContent.append(('A'.code + (character.code - 27)).toChar()) - } else if ((character <= ',') || (character == '/') || (character == ':')) { - extendedContent.append('/') - extendedContent.append(('A'.code + (character.code - 33)).toChar()) - } else if (character <= '9') { - extendedContent.append(('0'.code + (character.code - 48)).toChar()) - } else if (character <= '?') { - extendedContent.append('%') - extendedContent.append(('F'.code + (character.code - 59)).toChar()) - } else if (character <= 'Z') { - extendedContent.append(('A'.code + (character.code - 65)).toChar()) - } else if (character <= '_') { - extendedContent.append('%') - extendedContent.append(('K'.code + (character.code - 91)).toChar()) - } else if (character <= 'z') { - extendedContent.append('+') - extendedContent.append(('A'.code + (character.code - 97)).toChar()) - } else if (character.code <= 127) { - extendedContent.append('%') - extendedContent.append(('P'.code + (character.code - 123)).toChar()) - } else { - throw IllegalArgumentException( - "Requested content contains a non-encodable character: '" + contents[i] + "'" - ) - } - } - } - return extendedContent.toString() - } - } -} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Encoder.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Encoder.kt new file mode 100644 index 0000000..672a5e5 --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Encoder.kt @@ -0,0 +1,98 @@ +package io.github.alexzhirkevich.qrose.oned + + +internal object Code93Encoder : BarcodeEncoder { + + override fun encode(contents: String): BooleanArray { + var actualContents = contents + var length = actualContents.length + if (length > 80) { + throw IllegalArgumentException( + "Requested contents should be less than 80 digits long, but got $length" + ) + } + for (i in 0 until length) { + val indexInString: Int = Code39Encoder.ALPHABET_STRING.indexOf(actualContents[i]) + if (indexInString < 0) { + actualContents = tryToConvertToExtendedMode(actualContents) + length = actualContents.length + if (length > 80) { + throw IllegalArgumentException( + "Requested contents should be less than 80 digits long, but got " + + length + " (extended full ASCII mode)" + ) + } + break + } + } + val widths = IntArray(9) + val codeWidth = 24 + 1 + (13 * length) + val result = BooleanArray(codeWidth) + toIntArray(Code39Encoder.ASTERISK_ENCODING, widths) + var pos = appendPattern(result, 0, widths, true) + val narrowWhite = intArrayOf(1) + pos += appendPattern(result, pos, narrowWhite, false) + //append next character to byte matrix + for (i in 0 until length) { + val indexInString: Int = Code39Encoder.ALPHABET_STRING.indexOf(actualContents[i]) + toIntArray(Code39Encoder.CHARACTER_ENCODINGS.get(indexInString), widths) + pos += appendPattern(result, pos, widths, true) + pos += appendPattern(result, pos, narrowWhite, false) + } + toIntArray(Code39Encoder.ASTERISK_ENCODING, widths) + appendPattern(result, pos, widths, true) + return result + } + + private fun toIntArray(a: Int, toReturn: IntArray) { + for (i in 0..8) { + val temp = a and (1 shl (8 - i)) + toReturn[i] = if (temp == 0) 1 else 2 + } + } + + private fun tryToConvertToExtendedMode(contents: String): String { + val length = contents.length + val extendedContent: StringBuilder = StringBuilder() + for (i in 0 until length) { + val character = contents[i] + when (character) { + '\u0000' -> extendedContent.append("%U") + ' ', '-', '.' -> extendedContent.append(character) + '@' -> extendedContent.append("%V") + '`' -> extendedContent.append("%W") + else -> if (character.code <= 26) { + extendedContent.append('$') + extendedContent.append(('A'.code + (character.code - 1)).toChar()) + } else if (character < ' ') { + extendedContent.append('%') + extendedContent.append(('A'.code + (character.code - 27)).toChar()) + } else if ((character <= ',') || (character == '/') || (character == ':')) { + extendedContent.append('/') + extendedContent.append(('A'.code + (character.code - 33)).toChar()) + } else if (character <= '9') { + extendedContent.append(('0'.code + (character.code - 48)).toChar()) + } else if (character <= '?') { + extendedContent.append('%') + extendedContent.append(('F'.code + (character.code - 59)).toChar()) + } else if (character <= 'Z') { + extendedContent.append(('A'.code + (character.code - 65)).toChar()) + } else if (character <= '_') { + extendedContent.append('%') + extendedContent.append(('K'.code + (character.code - 91)).toChar()) + } else if (character <= 'z') { + extendedContent.append('+') + extendedContent.append(('A'.code + (character.code - 97)).toChar()) + } else if (character.code <= 127) { + extendedContent.append('%') + extendedContent.append(('P'.code + (character.code - 123)).toChar()) + } else { + throw IllegalArgumentException( + "Requested content contains a non-encodable character: '" + contents[i] + "'" + ) + } + } + } + return extendedContent.toString() + } +} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt deleted file mode 100644 index f4434ea..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * Code 93 barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberCode93Painter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - Code93Painter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -@Immutable -class Code93Painter( - val data : String, - val brush : Brush = SolidColor(Color.Black), - density: Float = 1f, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : SingleDimensionBarcodePainter( - density = density, - code = Code93().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt deleted file mode 100644 index e314b85..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - - -internal class CodeEAN13 { - - private val CODE_WIDTH = 3 + 7 * 6 + // left bars - 5 + 7 * 6 + // right bars - 3 // end guard - - - fun encode(contents: String): BooleanArray { - var actualContents = contents - val length = actualContents.length - when (length) { - 12 -> { - actualContents += getStandardUPCEANChecksum(actualContents) - } - - 13 -> if (!checkStandardUPCEANChecksum(actualContents)) { - throw IllegalArgumentException("Contents do not pass checksum") - } - - else -> throw IllegalArgumentException( - "Requested contents should be 12 or 13 digits long, but got $length" - ) - } - actualContents.requireNumeric() - val firstDigit = actualContents[0].digitToIntOrNull() ?: -1 - val parities: Int = UpcEan.FIRST_DIGIT_ENCODINGS[firstDigit] - val result = BooleanArray(CODE_WIDTH) - var pos = 0 - pos += appendPattern(result, pos, UpcEan.START_END_PATTERN, true) - - // See EAN13Reader for a description of how the first digit & left bars are encoded - for (i in 1..6) { - var digit = actualContents[i].digitToIntOrNull() ?: -1 - if (parities shr 6 - i and 1 == 1) { - digit += 10 - } - pos += appendPattern( - result, - pos, - UpcEan.L_AND_G_PATTERNS.get(digit), - false - ) - } - pos += appendPattern(result, pos, UpcEan.MIDDLE_PATTERN, false) - for (i in 7..12) { - val digit = actualContents[i].digitToIntOrNull() ?: -1 - pos += appendPattern(result, pos, UpcEan.L_PATTERNS[digit], true) - } - appendPattern(result, pos, UpcEan.START_END_PATTERN, true) - return result - } - -} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13Encoder.kt similarity index 94% rename from qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt rename to qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13Encoder.kt index e314b85..2d05066 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13.kt +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN13Encoder.kt @@ -1,14 +1,14 @@ package io.github.alexzhirkevich.qrose.oned -internal class CodeEAN13 { +internal object CodeEAN13Encoder : BarcodeEncoder { private val CODE_WIDTH = 3 + 7 * 6 + // left bars 5 + 7 * 6 + // right bars 3 // end guard - fun encode(contents: String): BooleanArray { + override fun encode(contents: String): BooleanArray { var actualContents = contents val length = actualContents.length when (length) { diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt deleted file mode 100644 index 3cc3c5e..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -internal class CodeEAN8 { - private val CODE_WIDTH = 3 + 7 * 4 + // left bars - 5 + 7 * 4 + // right bars - 3 // end guard - - - /** - * @return a byte array of horizontal pixels (false = white, true = black) - */ - fun encode(contents: String): BooleanArray { - var actualContentns = contents - val length = actualContentns.length - when (length) { - 7 -> { - // No check digit present, calculate it and add it - val check: Int = getStandardUPCEANChecksum(actualContentns) - actualContentns += check - } - - 8 -> if (!checkStandardUPCEANChecksum(actualContentns)) { - throw IllegalArgumentException("Contents do not pass checksum") - } - - else -> throw IllegalArgumentException( - "Requested contents should be 7 or 8 digits long, but got $length" - ) - } - actualContentns.requireNumeric() - val result = BooleanArray(CODE_WIDTH) - var pos = 0 - pos += appendPattern(result, pos, UpcEan.START_END_PATTERN, true) - for (i in 0..3) { - val digit = actualContentns[i].digitToIntOrNull() ?: -1 - pos += appendPattern( - result, - pos, - UpcEan.L_PATTERNS[digit], - false - ) - } - pos += appendPattern( - result, - pos, - UpcEan.MIDDLE_PATTERN, - false - ) - for (i in 4..7) { - val digit = actualContentns[i].digitToIntOrNull() ?: -1 - pos += appendPattern(result, pos, UpcEan.L_PATTERNS[digit], true) - } - appendPattern(result, pos, UpcEan.START_END_PATTERN, true) - return result - } - -} - diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8Encoder.kt similarity index 90% rename from qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt rename to qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8Encoder.kt index 3cc3c5e..6e03c74 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8.kt +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeEAN8Encoder.kt @@ -1,15 +1,12 @@ package io.github.alexzhirkevich.qrose.oned -internal class CodeEAN8 { +internal object CodeEAN8Encoder : BarcodeEncoder { private val CODE_WIDTH = 3 + 7 * 4 + // left bars 5 + 7 * 4 + // right bars 3 // end guard - /** - * @return a byte array of horizontal pixels (false = white, true = black) - */ - fun encode(contents: String): BooleanArray { + override fun encode(contents: String): BooleanArray { var actualContentns = contents val length = actualContentns.length when (length) { diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt deleted file mode 100644 index 9fabb66..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - - -internal class CodeITF { - - fun encode(contents: String): BooleanArray { - val length = contents.length - if (length % 2 != 0) { - throw IllegalArgumentException("The length of the input should be even") - } - if (length > 80) { - throw IllegalArgumentException( - "Requested contents should be less than 80 digits long, but got $length" - ) - } - contents.requireNumeric() - val result = BooleanArray(9 + 9 * length) - var pos = appendPattern(result, 0, START_PATTERN, true) - var i = 0 - while (i < length) { - val one = contents[i].digitToIntOrNull() ?: -1 - val two = contents[i + 1].digitToIntOrNull() ?: -1 - val encoding = IntArray(10) - for (j in 0..4) { - encoding[2 * j] = PATTERNS[one][j] - encoding[2 * j + 1] = PATTERNS[two][j] - } - pos += appendPattern(result, pos, encoding, true) - i += 2 - } - appendPattern(result, pos, END_PATTERN, true) - return result - } - - companion object { - private val START_PATTERN = intArrayOf(1, 1, 1, 1) - private val END_PATTERN = intArrayOf(3, 1, 1) - private const val W = 3 // Pixel width of a 3x wide line - private const val N = 1 // Pixed width of a narrow line - - // See ITFReader.PATTERNS - private val PATTERNS = arrayOf( - intArrayOf(N, N, W, W, N), - intArrayOf(W, N, N, N, W), - intArrayOf( - N, W, N, N, W - ), - intArrayOf(W, W, N, N, N), - intArrayOf(N, N, W, N, W), - intArrayOf(W, N, W, N, N), - intArrayOf( - N, W, W, N, N - ), - intArrayOf(N, N, N, W, W), - intArrayOf(W, N, N, W, N), - intArrayOf(N, W, N, W, N) - ) - } -} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITFEncoder.kt similarity index 55% rename from qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt rename to qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITFEncoder.kt index 9fabb66..0b7e397 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITF.kt +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeITFEncoder.kt @@ -1,9 +1,9 @@ package io.github.alexzhirkevich.qrose.oned -internal class CodeITF { +internal object CodeITFEncoder : BarcodeEncoder { - fun encode(contents: String): BooleanArray { + override fun encode(contents: String): BooleanArray { val length = contents.length if (length % 2 != 0) { throw IllegalArgumentException("The length of the input should be even") @@ -32,28 +32,26 @@ internal class CodeITF { return result } - companion object { - private val START_PATTERN = intArrayOf(1, 1, 1, 1) - private val END_PATTERN = intArrayOf(3, 1, 1) - private const val W = 3 // Pixel width of a 3x wide line - private const val N = 1 // Pixed width of a narrow line + private val START_PATTERN = intArrayOf(1, 1, 1, 1) + private val END_PATTERN = intArrayOf(3, 1, 1) + private const val W = 3 // Pixel width of a 3x wide line + private const val N = 1 // Pixed width of a narrow line - // See ITFReader.PATTERNS - private val PATTERNS = arrayOf( - intArrayOf(N, N, W, W, N), - intArrayOf(W, N, N, N, W), - intArrayOf( - N, W, N, N, W - ), - intArrayOf(W, W, N, N, N), - intArrayOf(N, N, W, N, W), - intArrayOf(W, N, W, N, N), - intArrayOf( - N, W, W, N, N - ), - intArrayOf(N, N, N, W, W), - intArrayOf(W, N, N, W, N), - intArrayOf(N, W, N, W, N) - ) - } + // See ITFReader.PATTERNS + private val PATTERNS = arrayOf( + intArrayOf(N, N, W, W, N), + intArrayOf(W, N, N, N, W), + intArrayOf( + N, W, N, N, W + ), + intArrayOf(W, W, N, N, N), + intArrayOf(N, N, W, N, W), + intArrayOf(W, N, W, N, N), + intArrayOf( + N, W, W, N, N + ), + intArrayOf(N, N, N, W, W), + intArrayOf(W, N, N, W, N), + intArrayOf(N, W, N, W, N) + ) } \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt deleted file mode 100644 index d18d82c..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -class CodeUPCA { - fun encode(contents : String) = CodeEAN13().encode("0$contents") -} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCAEncoder.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCAEncoder.kt new file mode 100644 index 0000000..b3f732a --- /dev/null +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCAEncoder.kt @@ -0,0 +1,5 @@ +package io.github.alexzhirkevich.qrose.oned + +internal object CodeUPCAEncoder: BarcodeEncoder { + override fun encode(contents : String) = CodeEAN13Encoder.encode("0$contents") +} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCEEncoder.kt similarity index 89% rename from qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt rename to qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCEEncoder.kt index 83b2787..d232152 100644 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCEEncoder.kt @@ -1,9 +1,12 @@ package io.github.alexzhirkevich.qrose.oned -class CodeUPCE { +internal object CodeUPCEEncoder : BarcodeEncoder { - fun encode(contents: String): BooleanArray { + private const val CODE_WIDTH = 3 + 7 * 6 + // bars + 6 // end guard + + override fun encode(contents: String): BooleanArray { var contents = contents val length = contents.length when (length) { @@ -42,8 +45,4 @@ class CodeUPCE { return result } - companion object { - private const val CODE_WIDTH = 3 + 7 * 6 + // bars - 6 // end guard - } } \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt deleted file mode 100644 index 2dd1aeb..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * EAN13 barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberEAN13Painter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - EAN13Painter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -class EAN13Painter( - val data : String, - val brush: Brush = SolidColor(Color.Black), - density : Float = 1f, - builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, -) : SingleDimensionBarcodePainter( - density = density, - code = CodeEAN13().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt deleted file mode 100644 index 9762860..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * EAN8 barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberEAN8Painter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - EAN8Painter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -class EAN8Painter( - val data : String, - val brush: Brush = SolidColor(Color.Black), - density : Float = 1f, - builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, -) : SingleDimensionBarcodePainter( - density = density, - code = CodeEAN8().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt deleted file mode 100644 index e3b974b..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * ETF barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberITFPainter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - ITFPainter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -class ITFPainter( - val data : String, - val brush: Brush = SolidColor(Color.Black), - density : Float = 1f, - builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, -) : SingleDimensionBarcodePainter( - density = density, - code = CodeITF().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt deleted file mode 100644 index b5d6a09..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt +++ /dev/null @@ -1,50 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.util.fastForEachIndexed -import io.github.alexzhirkevich.qrose.CachedPainter - -typealias BarcodePathBuilder = (size : Size, code : List) -> Path - -sealed class SingleDimensionBarcodePainter( - density: Float, - private val code : List, - private val brush: Brush, - private val builder : BarcodePathBuilder -) : CachedPainter(){ - - override fun DrawScope.onCache() { - drawPath( - path = builder(size, code), - brush = brush - ) - } - - override val intrinsicSize: Size = Size( - width = density * 3 * code.size, - height = 60 * density - ) -} - -@PublishedApi -internal fun defaultOneDBarcodeBuilder(size : Size, data : List): Path = Path().apply { - - val width = size.width / data.size - - data.fastForEachIndexed { i, b -> - if (b) { - addRect( - Rect( - left = i * width, - top = 0f, - right = (i + 1) * width, - bottom = size.height - ) - ) - } - } -} \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt deleted file mode 100644 index 436dd8e..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * UPC A barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberUPCAPainter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - UPCAPainter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -class UPCAPainter( - val data : String, - val brush: Brush = SolidColor(Color.Black), - density : Float = 1f, - builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, -) : SingleDimensionBarcodePainter( - density = density, - code = CodeUPCA().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt deleted file mode 100644 index 2a65646..0000000 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * UPC E barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberUPCEPainter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - UPCEPainter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -class UPCEPainter( - val data : String, - val brush: Brush = SolidColor(Color.Black), - density : Float = 1f, - builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, -) : SingleDimensionBarcodePainter( - density = density, - code = CodeUPCE().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose/build.gradle.kts b/qrose/build.gradle.kts index a4cc293..dd3e25a 100644 --- a/qrose/build.gradle.kts +++ b/qrose/build.gradle.kts @@ -1,10 +1,3 @@ -@file:Suppress("DSL_SCOPE_VIOLATION") - -plugins { - id("maven-publish") - id("signing") -} - kotlin { sourceSets.commonMain.dependencies { @@ -13,93 +6,3 @@ kotlin { } } -//ext["signing.keyId"] = null -//ext["signing.password"] = null -//ext["signing.secretKeyRingFile"] = null -//ext["ossrhUsername"] = null -//ext["ossrhPassword"] = null - -//extra.apply { -// val publishPropFile = rootProject.file("local.properties") -// if (publishPropFile.exists()) { -// Properties().apply { -// load(publishPropFile.inputStream()) -// }.forEach { name, value -> -// if (name == "signing.secretKeyRingFile") { -// set(name.toString(), rootProject.file(value.toString()).absolutePath) -// } else { -// set(name.toString(), value) -// } -// } -// } else { -// ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID") -// ext["signing.password"] = System.getenv("SIGNING_PASSWORD") -// ext["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE") -// ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME") -// ext["ossrhPassword"]= System.getenv("OSSRH_PASSWORD") -// } -//} -// -//val javadocJar by tasks.registering(Jar::class) { -// archiveClassifier.set("javadoc") -//} -//// https://github.com/gradle/gradle/issues/26091 -//val signingTasks = tasks.withType() -//tasks.withType().configureEach { -// dependsOn(signingTasks) -//} - -//publishing { -// if (rootProject.file("local.properties").exists()) { -// -// repositories { -// maven { -// val releasesRepoUrl = -// "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" -// val snapshotsRepoUrl = -// "https://s01.oss.sonatype.org/content/repositories/snapshots/" -// url = if (version.toString().endsWith("SNAPSHOT")) { -// uri(snapshotsRepoUrl) -// } else { -// uri(releasesRepoUrl) -// } -// credentials { -// username = project.ext.get("ossrhUsername").toString() -// password = project.ext.get("ossrhPassword").toString() -// } -// } -// } -// } -// -// publications.withType { -// artifact(javadocJar) -// pom { -// name.set("QRose") -// description.set("Styled QR code generation library for Compose Multiplatform") -// url.set("https://github.com/alexzhirkevich/qrose") -// -// licenses { -// license { -// name.set("MIT") -// url.set("https://opensource.org/licenses/MIT") -// } -// } -// developers { -// developer { -// id.set("alexzhirkevich") -// name.set("Alexander Zhirkevich") -// email.set("sasha.zhirkevich@gmail.com") -// } -// } -// scm { -// url.set("https://github.com/alexzhirkevich/qrose") -// connection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") -// developerConnection.set("scm:git:git://github.com/alexzhirkevich/qrose.git") -// } -// } -// } -//} - -//signing { -// sign(publishing.publications) -//} \ No newline at end of file diff --git a/qrose/publish.properties b/qrose/publish.properties new file mode 100644 index 0000000..ba7557d --- /dev/null +++ b/qrose/publish.properties @@ -0,0 +1 @@ +description=Styled QR code generator for Compose Multiplatform" diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt deleted file mode 100644 index 606d455..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Codabar.kt +++ /dev/null @@ -1,112 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -internal class Codabar { - - fun encode(contents: String): BooleanArray { - var actualContents = contents - if (actualContents.length < 2) { - // Can't have a start/end guard, so tentatively add default guards - actualContents = DEFAULT_GUARD.toString() + actualContents + DEFAULT_GUARD - } else { - // Verify input and calculate decoded length. - val firstChar = actualContents[0].uppercaseChar() - val lastChar = actualContents[actualContents.length - 1].uppercaseChar() - val startsNormal: Boolean = firstChar in START_END_CHARS - val endsNormal: Boolean = lastChar in START_END_CHARS - val startsAlt: Boolean = firstChar in ALT_START_END_CHARS - val endsAlt: Boolean = lastChar in ALT_START_END_CHARS - if (startsNormal) { - if (!endsNormal) { - throw IllegalArgumentException("Invalid start/end guards: $actualContents") - } - // else already has valid start/end - } else if (startsAlt) { - if (!endsAlt) { - throw IllegalArgumentException("Invalid start/end guards: $actualContents") - } - // else already has valid start/end - } else { - // Doesn't start with a guard - if (endsNormal || endsAlt) { - throw IllegalArgumentException("Invalid start/end guards: $actualContents") - } - // else doesn't end with guard either, so add a default - actualContents = DEFAULT_GUARD.toString() + actualContents + DEFAULT_GUARD - } - } - - // The start character and the end character are decoded to 10 length each. - var resultLength = 20 - for (i in 1 until actualContents.length - 1) { - resultLength += if (actualContents[i].isDigit() || actualContents[i] == '-' || actualContents[i] == '$') { - 9 - } else if (actualContents[i] in CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED) { - 10 - } else { - throw IllegalArgumentException("Cannot encode : '" + actualContents[i] + '\'') - } - } - // A blank is placed between each character. - resultLength += actualContents.length - 1 - val result = BooleanArray(resultLength) - var position = 0 - for (index in actualContents.indices) { - var c = actualContents[index].uppercaseChar() - if (index == 0 || index == actualContents.length - 1) { - // The start/end chars are not in the CodaBarReader.ALPHABET. - when (c) { - 'T' -> c = 'A' - 'N' -> c = 'B' - '*' -> c = 'C' - 'E' -> c = 'D' - } - } - var code = 0 - for (i in ALPHABET.indices) { - // Found any, because I checked above. - if (c == ALPHABET[i]) { - code = CHARACTER_ENCODINGS[i] - break - } - } - var color = true - var counter = 0 - var bit = 0 - while (bit < 7) { // A character consists of 7 digit. - result[position] = color - position++ - if (code shr 6 - bit and 1 == 0 || counter == 1) { - color = !color // Flip the color. - bit++ - counter = 0 - } else { - counter++ - } - } - if (index < actualContents.length - 1) { - result[position] = false - position++ - } - } - return result - } - - companion object { - private val START_END_CHARS = charArrayOf('A', 'B', 'C', 'D') - private val ALT_START_END_CHARS = charArrayOf('T', 'N', '*', 'E') - private val CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED = charArrayOf('/', ':', '+', '.') - private val DEFAULT_GUARD = START_END_CHARS[0] - - private const val ALPHABET_STRING = "0123456789-$:/.+ABCD" - val ALPHABET = ALPHABET_STRING.toCharArray() - - /** - * These represent the encodings of characters, as patterns of wide and narrow bars. The 7 least-significant bits of - * each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow. - */ - val CHARACTER_ENCODINGS = intArrayOf( - 0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9 - 0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E - ) - } -} diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt deleted file mode 100644 index 7a8404d..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodabarPainter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * Codabar barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberCodabarPainter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - CodabarPainter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -class CodabarPainter( - val data : String, - val brush: Brush = SolidColor(Color.Black), - density : Float = 1f, - builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, -) : SingleDimensionBarcodePainter( - density = density, - code = Codabar().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt deleted file mode 100644 index 6342381..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code128Painter.kt +++ /dev/null @@ -1,75 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - - -/** - * Code 128 barcode painter - * - * @param brush code brush - * @param compact Specifies whether to use compact mode for Code-128 code. This can yield slightly smaller bar codes. This option and [forceCodeSet] are mutually exclusive. - * @param forceCodeSet Forces which encoding will be used. Currently only used for Code-128 code sets. This option and CODE128_COMPACT are mutually exclusive - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberCode128Painter( - data: String, - brush: Brush = SolidColor(Color.Black), - compact : Boolean = true, - forceCodeSet : Code128Painter.CodeSet? = null, - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush, forceCodeSet) { - runCatching { - Code128Painter( - data = data, - density = density.density, - brush = brush, - compact = compact, - codeSet = forceCodeSet, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - - -@Immutable -class Code128Painter( - val data : String, - val brush: Brush = SolidColor(Color.Black), - val compact : Boolean = true, - val codeSet : CodeSet? = null, - density: Float, - builder : BarcodePathBuilder= ::defaultOneDBarcodeBuilder -) : SingleDimensionBarcodePainter( - density = density, - code = Code128().encode(data, compact, codeSet).toList(), - brush = brush, - builder = builder -) { - - enum class CodeSet(internal val v: Int) { - A(Code128.CODE_CODE_A), - B(Code128.CODE_CODE_B), - C(Code128.CODE_CODE_C) - } -} diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt deleted file mode 100644 index 71b55d9..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39.kt +++ /dev/null @@ -1,121 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -internal class Code39 { - - fun encode(contents: String): BooleanArray { - var actualContent = contents - var length = actualContent.length - if (length > 80) { - throw IllegalArgumentException( - "Requested contents should be less than 80 digits long, but got $length" - ) - } - for (i in 0 until length) { - val indexInString: Int = ALPHABET_STRING.indexOf(actualContent[i]) - if (indexInString < 0) { - actualContent = tryToConvertToExtendedMode(actualContent) - length = actualContent.length - if (length > 80) { - throw IllegalArgumentException( - "Requested contents should be less than 80 digits long, but got " + - length + " (extended full ASCII mode)" - ) - } - break - } - } - val widths = IntArray(9) - val codeWidth = 24 + 1 + (13 * length) - val result = BooleanArray(codeWidth) - toIntArray(ASTERISK_ENCODING, widths) - var pos = appendPattern(result, 0, widths, true) - val narrowWhite = intArrayOf(1) - pos += appendPattern(result, pos, narrowWhite, false) - //append next character to byte matrix - for (i in 0 until length) { - val indexInString: Int = ALPHABET_STRING.indexOf(actualContent[i]) - toIntArray(CHARACTER_ENCODINGS[indexInString], widths) - pos += appendPattern(result, pos, widths, true) - pos += appendPattern(result, pos, narrowWhite, false) - } - toIntArray(ASTERISK_ENCODING, widths) - appendPattern(result, pos, widths, true) - return result - } - - companion object { - - - const val ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. \$/+%" - - val CHARACTER_ENCODINGS by lazy { - intArrayOf( - 0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, // 0-9 - // 0-9 - 0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, // A-J - // A-J - 0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, // K-T - // K-T - 0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x0A8, // U-$ - // U-$ - 0x0A2, 0x08A, 0x02A // /-% - // /-% - ) - } - - val ASTERISK_ENCODING = 0x094 - - private fun toIntArray(a: Int, toReturn: IntArray) { - for (i in 0..8) { - val temp = a and (1 shl (8 - i)) - toReturn[i] = if (temp == 0) 1 else 2 - } - } - - private fun tryToConvertToExtendedMode(contents: String): String { - val length = contents.length - val extendedContent = StringBuilder() - for (i in 0 until length) { - val character = contents[i] - when (character) { - '\u0000' -> extendedContent.append("%U") - ' ', '-', '.' -> extendedContent.append(character) - '@' -> extendedContent.append("%V") - '`' -> extendedContent.append("%W") - else -> if (character.code <= 26) { - extendedContent.append('$') - extendedContent.append(('A'.code + (character.code - 1)).toChar()) - } else if (character < ' ') { - extendedContent.append('%') - extendedContent.append(('A'.code + (character.code - 27)).toChar()) - } else if ((character <= ',') || (character == '/') || (character == ':')) { - extendedContent.append('/') - extendedContent.append(('A'.code + (character.code - 33)).toChar()) - } else if (character <= '9') { - extendedContent.append(('0'.code + (character.code - 48)).toChar()) - } else if (character <= '?') { - extendedContent.append('%') - extendedContent.append(('F'.code + (character.code - 59)).toChar()) - } else if (character <= 'Z') { - extendedContent.append(('A'.code + (character.code - 65)).toChar()) - } else if (character <= '_') { - extendedContent.append('%') - extendedContent.append(('K'.code + (character.code - 91)).toChar()) - } else if (character <= 'z') { - extendedContent.append('+') - extendedContent.append(('A'.code + (character.code - 97)).toChar()) - } else if (character.code <= 127) { - extendedContent.append('%') - extendedContent.append(('P'.code + (character.code - 123)).toChar()) - } else { - throw IllegalArgumentException( - "Requested content contains a non-encodable character: '" + contents[i] + "'" - ) - } - } - } - return extendedContent.toString() - } - } -} - diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt deleted file mode 100644 index 84f31e9..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code39Painter.kt +++ /dev/null @@ -1,57 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * Code 39 barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberCode39Painter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - Code39Painter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -@Immutable -class Code39Painter( - val data : String, - val brush : Brush = SolidColor(Color.Black), - density: Float = 1f, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : SingleDimensionBarcodePainter( - density = density, - code = Code39().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt deleted file mode 100644 index df88aa9..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93.kt +++ /dev/null @@ -1,100 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - - -internal class Code93 { - - fun encode(contents: String): BooleanArray { - var actualContents = contents - var length = actualContents.length - if (length > 80) { - throw IllegalArgumentException( - "Requested contents should be less than 80 digits long, but got $length" - ) - } - for (i in 0 until length) { - val indexInString: Int = Code39.ALPHABET_STRING.indexOf(actualContents[i]) - if (indexInString < 0) { - actualContents = tryToConvertToExtendedMode(actualContents) - length = actualContents.length - if (length > 80) { - throw IllegalArgumentException( - "Requested contents should be less than 80 digits long, but got " + - length + " (extended full ASCII mode)" - ) - } - break - } - } - val widths = IntArray(9) - val codeWidth = 24 + 1 + (13 * length) - val result = BooleanArray(codeWidth) - toIntArray(Code39.ASTERISK_ENCODING, widths) - var pos = appendPattern(result, 0, widths, true) - val narrowWhite = intArrayOf(1) - pos += appendPattern(result, pos, narrowWhite, false) - //append next character to byte matrix - for (i in 0 until length) { - val indexInString: Int = Code39.ALPHABET_STRING.indexOf(actualContents[i]) - toIntArray(Code39.CHARACTER_ENCODINGS.get(indexInString), widths) - pos += appendPattern(result, pos, widths, true) - pos += appendPattern(result, pos, narrowWhite, false) - } - toIntArray(Code39.ASTERISK_ENCODING, widths) - appendPattern(result, pos, widths, true) - return result - } - - companion object { - private fun toIntArray(a: Int, toReturn: IntArray) { - for (i in 0..8) { - val temp = a and (1 shl (8 - i)) - toReturn[i] = if (temp == 0) 1 else 2 - } - } - - private fun tryToConvertToExtendedMode(contents: String): String { - val length = contents.length - val extendedContent: StringBuilder = StringBuilder() - for (i in 0 until length) { - val character = contents[i] - when (character) { - '\u0000' -> extendedContent.append("%U") - ' ', '-', '.' -> extendedContent.append(character) - '@' -> extendedContent.append("%V") - '`' -> extendedContent.append("%W") - else -> if (character.code <= 26) { - extendedContent.append('$') - extendedContent.append(('A'.code + (character.code - 1)).toChar()) - } else if (character < ' ') { - extendedContent.append('%') - extendedContent.append(('A'.code + (character.code - 27)).toChar()) - } else if ((character <= ',') || (character == '/') || (character == ':')) { - extendedContent.append('/') - extendedContent.append(('A'.code + (character.code - 33)).toChar()) - } else if (character <= '9') { - extendedContent.append(('0'.code + (character.code - 48)).toChar()) - } else if (character <= '?') { - extendedContent.append('%') - extendedContent.append(('F'.code + (character.code - 59)).toChar()) - } else if (character <= 'Z') { - extendedContent.append(('A'.code + (character.code - 65)).toChar()) - } else if (character <= '_') { - extendedContent.append('%') - extendedContent.append(('K'.code + (character.code - 91)).toChar()) - } else if (character <= 'z') { - extendedContent.append('+') - extendedContent.append(('A'.code + (character.code - 97)).toChar()) - } else if (character.code <= 127) { - extendedContent.append('%') - extendedContent.append(('P'.code + (character.code - 123)).toChar()) - } else { - throw IllegalArgumentException( - "Requested content contains a non-encodable character: '" + contents[i] + "'" - ) - } - } - } - return extendedContent.toString() - } - } -} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt deleted file mode 100644 index f4434ea..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/Code93Painter.kt +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * Code 93 barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberCode93Painter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - Code93Painter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -@Immutable -class Code93Painter( - val data : String, - val brush : Brush = SolidColor(Color.Black), - density: Float = 1f, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : SingleDimensionBarcodePainter( - density = density, - code = Code93().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt deleted file mode 100644 index d18d82c..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCA.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -class CodeUPCA { - fun encode(contents : String) = CodeEAN13().encode("0$contents") -} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt deleted file mode 100644 index 83b2787..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/CodeUPCE.kt +++ /dev/null @@ -1,49 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - - -class CodeUPCE { - - fun encode(contents: String): BooleanArray { - var contents = contents - val length = contents.length - when (length) { - 7 -> { - // No check digit present, calculate it and add it - val check: Int = getStandardUPCEANChecksum(convertUPCEtoUPCA(contents)) - contents += check - } - - 8 -> if (!checkStandardUPCEANChecksum(convertUPCEtoUPCA(contents))) { - throw IllegalArgumentException("Contents do not pass checksum") - } - - else -> throw IllegalArgumentException( - "Requested contents should be 7 or 8 digits long, but got $length" - ) - } - contents.requireNumeric() - val firstDigit = contents[0].digitToIntOrNull() ?: -1 - if (firstDigit != 0 && firstDigit != 1) { - throw IllegalArgumentException("Number system must be 0 or 1") - } - val checkDigit = contents[7].digitToIntOrNull() ?: -1 - val parities: Int = - UpcEan.NUMSYS_AND_CHECK_DIGIT_PATTERNS.get(firstDigit).get(checkDigit) - val result = BooleanArray(CODE_WIDTH) - var pos = appendPattern(result, 0, UpcEan.START_END_PATTERN, true) - for (i in 1..6) { - var digit = contents[i].digitToIntOrNull() ?: -1 - if (parities shr 6 - i and 1 == 1) { - digit += 10 - } - pos += appendPattern(result, pos, UpcEan.L_AND_G_PATTERNS.get(digit), false) - } - appendPattern(result, pos, UpcEan.END_PATTERN, false) - return result - } - - companion object { - private const val CODE_WIDTH = 3 + 7 * 6 + // bars - 6 // end guard - } -} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt deleted file mode 100644 index 2dd1aeb..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN13Painter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * EAN13 barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberEAN13Painter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - EAN13Painter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -class EAN13Painter( - val data : String, - val brush: Brush = SolidColor(Color.Black), - density : Float = 1f, - builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, -) : SingleDimensionBarcodePainter( - density = density, - code = CodeEAN13().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt deleted file mode 100644 index 9762860..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/EAN8Painter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * EAN8 barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberEAN8Painter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - EAN8Painter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -class EAN8Painter( - val data : String, - val brush: Brush = SolidColor(Color.Black), - density : Float = 1f, - builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, -) : SingleDimensionBarcodePainter( - density = density, - code = CodeEAN8().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt deleted file mode 100644 index e3b974b..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/ITFPainter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * ETF barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberITFPainter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - ITFPainter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -class ITFPainter( - val data : String, - val brush: Brush = SolidColor(Color.Black), - density : Float = 1f, - builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, -) : SingleDimensionBarcodePainter( - density = density, - code = CodeITF().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt deleted file mode 100644 index b5d6a09..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/SingleDimensionBarcode.kt +++ /dev/null @@ -1,50 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.util.fastForEachIndexed -import io.github.alexzhirkevich.qrose.CachedPainter - -typealias BarcodePathBuilder = (size : Size, code : List) -> Path - -sealed class SingleDimensionBarcodePainter( - density: Float, - private val code : List, - private val brush: Brush, - private val builder : BarcodePathBuilder -) : CachedPainter(){ - - override fun DrawScope.onCache() { - drawPath( - path = builder(size, code), - brush = brush - ) - } - - override val intrinsicSize: Size = Size( - width = density * 3 * code.size, - height = 60 * density - ) -} - -@PublishedApi -internal fun defaultOneDBarcodeBuilder(size : Size, data : List): Path = Path().apply { - - val width = size.width / data.size - - data.fastForEachIndexed { i, b -> - if (b) { - addRect( - Rect( - left = i * width, - top = 0f, - right = (i + 1) * width, - bottom = size.height - ) - ) - } - } -} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt deleted file mode 100644 index 436dd8e..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCAPainter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * UPC A barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberUPCAPainter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - UPCAPainter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -class UPCAPainter( - val data : String, - val brush: Brush = SolidColor(Color.Black), - density : Float = 1f, - builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, -) : SingleDimensionBarcodePainter( - density = density, - code = CodeUPCA().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt deleted file mode 100644 index 2a65646..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UPCEPainter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity - -/** - * UPC E barcode painter - * - * @param brush code brush - * @param onError called when input content is invalid - * @param builder build code path using painter size and encoded boolean list - * */ -@Composable -fun rememberUPCEPainter( - data: String, - brush: Brush = SolidColor(Color.Black), - onError : (Throwable) -> Painter = { throw it }, - builder : BarcodePathBuilder = ::defaultOneDBarcodeBuilder -) : Painter { - val density = LocalDensity.current - - val updatedBuilder by rememberUpdatedState(builder) - - return remember(density, data, brush) { - runCatching { - UPCEPainter( - data = data, - density = density.density, - brush = brush, - builder = { size, code -> - updatedBuilder(size, code) - }, - ) - }.getOrElse(onError) - } -} - -class UPCEPainter( - val data : String, - val brush: Brush = SolidColor(Color.Black), - density : Float = 1f, - builder: BarcodePathBuilder = ::defaultOneDBarcodeBuilder, -) : SingleDimensionBarcodePainter( - density = density, - code = CodeUPCE().encode(data).toList(), - brush = brush, - builder = builder -) \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt deleted file mode 100644 index 977f625..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/UpcEanUtils.kt +++ /dev/null @@ -1,145 +0,0 @@ -package io.github.alexzhirkevich.qrose.oned - -internal object UpcEan { - - val NUMSYS_AND_CHECK_DIGIT_PATTERNS = arrayOf( - intArrayOf(0x38, 0x34, 0x32, 0x31, 0x2C, 0x26, 0x23, 0x2A, 0x29, 0x25), - intArrayOf(0x07, 0x0B, 0x0D, 0x0E, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A) - ) - /** - * Start/end guard pattern. - */ - val START_END_PATTERN by lazy { - intArrayOf(1, 1, 1) - } - - /** - * Pattern marking the middle of a UPC/EAN pattern, separating the two halves. - */ - val MIDDLE_PATTERN by lazy { - intArrayOf(1, 1, 1, 1, 1) - } - - /** - * end guard pattern. - */ - val END_PATTERN by lazy { - intArrayOf(1, 1, 1, 1, 1, 1) - } - /** - * "Odd", or "L" patterns used to encode UPC/EAN digits. - */ - val L_PATTERNS by lazy { - arrayOf( - intArrayOf(3, 2, 1, 1), - intArrayOf(2, 2, 2, 1), - intArrayOf(2, 1, 2, 2), - intArrayOf(1, 4, 1, 1), - intArrayOf(1, 1, 3, 2), - intArrayOf(1, 2, 3, 1), - intArrayOf(1, 1, 1, 4), - intArrayOf(1, 3, 1, 2), - intArrayOf(1, 2, 1, 3), - intArrayOf(3, 1, 1, 2) - ) - } - - val L_AND_G_PATTERNS by lazy { - buildList(20) { - addAll(L_PATTERNS) - - for (i in 10..19) { - val widths: IntArray = L_PATTERNS[i - 10] - val reversedWidths = IntArray(widths.size) - for (j in widths.indices) { - reversedWidths[j] = widths[widths.size - j - 1] - } - add(reversedWidths) - } - } - } - - val FIRST_DIGIT_ENCODINGS by lazy { - intArrayOf( - 0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A - ) - } -} - -internal fun String.requireNumeric() = require(all { it.isDigit() }){ - "Input should only contain digits 0-9" -} - -internal fun convertUPCEtoUPCA(upce: String): String { - val upceChars = upce.toCharArray(1, 7) - val result = StringBuilder(12) - result.append(upce[0]) - val lastChar = upceChars[5] - when (lastChar) { - '0', '1', '2' -> { - result.appendRange(upceChars, 0, 0 + 2) - result.append(lastChar) - result.append("0000") - result.appendRange(upceChars, 2, 3) - } - - '3' -> { - result.appendRange(upceChars, 0, 3) - result.append("00000") - result.appendRange(upceChars, 3, 2) - } - - '4' -> { - result.appendRange(upceChars, 0, 4) - result.append("00000") - result.append(upceChars[4]) - } - - else -> { - result.appendRange(upceChars, 0, 5) - result.append("0000") - result.append(lastChar) - } - } - // Only append check digit in conversion if supplied - if (upce.length >= 8) { - result.append(upce[7]) - } - return result.toString() -} - -internal fun checkStandardUPCEANChecksum(s: CharSequence): Boolean { - val length = s.length - if (length == 0) { - return false - } - val check = s[length - 1].digitToIntOrNull() ?: -1 - return getStandardUPCEANChecksum(s.subSequence(0, length - 1)) == check -} - -internal fun getStandardUPCEANChecksum(s: CharSequence): Int { - val length = s.length - var sum = 0 - run { - var i = length - 1 - while (i >= 0) { - val digit = s[i].code - '0'.code - if (digit < 0 || digit > 9) { - throw IllegalStateException("Illegal contents") - } - sum += digit - i -= 2 - } - } - sum *= 3 - var i = length - 2 - while (i >= 0) { - val digit = s[i].code - '0'.code - if (digit < 0 || digit > 9) { - throw IllegalStateException("Illegal contents") - } - sum += digit - i -= 2 - } - return (1000 - sum) % 10 -} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/dsl/InternalQrOptionsBuilderScope.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/dsl/InternalQrOptionsBuilderScope.kt index 9465486..2e700b7 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/dsl/InternalQrOptionsBuilderScope.kt +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/dsl/InternalQrOptionsBuilderScope.kt @@ -2,7 +2,7 @@ package io.github.alexzhirkevich.qrose.options.dsl import io.github.alexzhirkevich.qrose.options.QrErrorCorrectionLevel import io.github.alexzhirkevich.qrose.options.QrOptions -import io.github.alexzhirkevich.qrose.options.DelicateQRoseApi +import io.github.alexzhirkevich.qrose.DelicateQRoseApi internal class InternalQrOptionsBuilderScope( diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/dsl/QrOptionsBuilderScope.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/dsl/QrOptionsBuilderScope.kt index 6b262cd..6ef5da7 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/dsl/QrOptionsBuilderScope.kt +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/options/dsl/QrOptionsBuilderScope.kt @@ -1,8 +1,8 @@ package io.github.alexzhirkevich.qrose.options.dsl +import io.github.alexzhirkevich.qrose.DelicateQRoseApi import io.github.alexzhirkevich.qrose.options.QrErrorCorrectionLevel import io.github.alexzhirkevich.qrose.options.QrErrorCorrectionLevel.Auto -import io.github.alexzhirkevich.qrose.options.DelicateQRoseApi sealed interface QrOptionsBuilderScope { diff --git a/settings.gradle.kts b/settings.gradle.kts index 5ac6d6e..468af95 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,7 +13,6 @@ dependencyResolutionManagement { google() mavenCentral() maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") - } } From 5e072f3091c04d98319a570f83dee6bebdf035b3 Mon Sep 17 00:00:00 2001 From: Alexander Zhirkevich Date: Mon, 4 Mar 2024 01:19:18 +0300 Subject: [PATCH 08/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index be22035..65aecd2 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ dependencies { implementation("io.github.alexzhirkevich:qrose:1.0.0") // For single-dimension barcodes (UPC,EAN, Code128, ...) - implementation("io.github.alexzhirkevich:qrose:1.0.0-oned") + implementation("io.github.alexzhirkevich:qrose-oned:1.0.0") } ``` From 3b9630b969301fd778d81b38f51810b575229e2d Mon Sep 17 00:00:00 2001 From: Alexander Zhirkevich Date: Mon, 4 Mar 2024 01:23:01 +0300 Subject: [PATCH 09/13] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 65aecd2..92b2501 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Why QRose? - **Efficient** - declare and render codes synchronously right from the composition in 60+ fps; - **Scalable** - no raster bitmaps, only scalable vector graphics; - **Multiplatform** - supports all the targets supported by Compose Multiplatform. -- **Multiformat** - multiple formats supported : QR, UPC, EAN, Code 128, etc. +- **Multiformat** - multiple formats supported: `QR`, `UPC`, `EAN`, `Code 128/93/39`, `Codabar`, `ITF`. # Installation @@ -38,7 +38,7 @@ dependencies { ## Basic You can create code right in composition using `rememberQrCodePainter`, `rememberBarcodePainter`. -Or use `QrCodePainter`, `BarcodePainter` to crate it outside compose. +Or use `QrCodePainter`, `BarcodePainter` to create it outside of Compose. ```kotlin Image( From 7655406e2d6b774c0f2cdc68f8a414e0148a3b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D0=B8=D1=80=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Mon, 4 Mar 2024 01:27:49 +0300 Subject: [PATCH 10/13] remove unused declarations --- .../io/github/alexzhirkevich/shared/App.kt | 1 + .../alexzhirkevich/qrose/toByteArray.kt | 18 ------- .../alexzhirkevich/qrose/CachedPainter.kt | 42 ---------------- .../github/alexzhirkevich/qrose/Converters.kt | 50 ------------------- .../github/alexzhirkevich/qrose/DrawCache.kt | 30 ----------- .../alexzhirkevich/qrose/QrCodePainter.kt | 9 ++-- .../alexzhirkevich/qrose/toByteArray.kt | 18 ------- 7 files changed, 4 insertions(+), 164 deletions(-) delete mode 100644 qrose/src/androidMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/Converters.kt delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DrawCache.kt delete mode 100644 qrose/src/skikoMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt diff --git a/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt b/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt index 8b78ec0..7dda311 100644 --- a/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt +++ b/example/shared/src/commonMain/kotlin/io/github/alexzhirkevich/shared/App.kt @@ -47,6 +47,7 @@ import io.github.alexzhirkevich.qrose.options.image import io.github.alexzhirkevich.qrose.options.roundCorners import io.github.alexzhirkevich.qrose.options.solid import io.github.alexzhirkevich.qrose.rememberQrCodePainter +import io.github.alexzhirkevich.qrose.toByteArray import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.painterResource import qrose.example.shared.generated.resources.Res diff --git a/qrose/src/androidMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt b/qrose/src/androidMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt deleted file mode 100644 index b199413..0000000 --- a/qrose/src/androidMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.alexzhirkevich.qrose - -import android.graphics.Bitmap -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.asAndroidBitmap -import java.io.ByteArrayOutputStream - -actual typealias ImageFormat = Bitmap.CompressFormat - -actual fun ImageBitmap.toByteArray( - format: ImageFormat -): ByteArray { - - return ByteArrayOutputStream().use { - asAndroidBitmap().compress(format, 100, it) - it.toByteArray() - } -} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt index cad66a0..301082b 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt @@ -15,48 +15,6 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.toSize import kotlin.math.ceil -abstract class CachedPainter : Painter() { - - private var alpha = 1f - private var colorFilter : ColorFilter?= null - - private var cachedSize: Size? = null - - private var cacheDrawScope = DrawCache() - - override fun applyAlpha(alpha: Float): Boolean { - this.alpha = alpha - return true - } - - override fun applyColorFilter(colorFilter: ColorFilter?): Boolean { - this.colorFilter = colorFilter - return true - } - - abstract fun DrawScope.onCache() - - private val block : DrawScope.() -> Unit = { onCache() } - - override fun DrawScope.onDraw() { - if (cachedSize != size) { - - cacheDrawScope.drawCachedImage( - size = IntSize(ceil(size.width).toInt(), ceil(size.height).toInt()), - density = this, - layoutDirection = layoutDirection, - block = block - ) - cachedSize = size - } - cacheDrawScope.drawInto( - target = this, - alpha = alpha, - colorFilter = colorFilter - ) - } -} - /** * Creates a drawing environment that directs its drawing commands to an [ImageBitmap] * which can be drawn directly in another [DrawScope] instance. This is useful to cache diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/Converters.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/Converters.kt deleted file mode 100644 index 9afc7b1..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/Converters.kt +++ /dev/null @@ -1,50 +0,0 @@ -package io.github.alexzhirkevich.qrose - -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Canvas -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.drawscope.CanvasDrawScope -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.LayoutDirection - -expect enum class ImageFormat { - PNG, JPEG, WEBP -} - -/** - * Converts [ImageBitmap] to image with desired [format] and returns its bytes. - * */ -expect fun ImageBitmap.toByteArray(format: ImageFormat = ImageFormat.PNG) : ByteArray - -/** - * Converts [Painter] to image with desired [width], [height] and [format] and returns its bytes. - * */ -fun Painter.toByteArray(width : Int, height: Int, format : ImageFormat = ImageFormat.PNG) : ByteArray = - toImageBitmap(width, height).toByteArray(format) - -/** - * Converts [Painter] to [ImageBitmap] with desired [width], [height], [alpha] and [colorFilter] - * */ -fun Painter.toImageBitmap( - width : Int, - height : Int, - alpha : Float = 1f, - colorFilter: ColorFilter? = null -) : ImageBitmap { - - val bmp = ImageBitmap(width, height) - val canvas = Canvas(bmp) - - CanvasDrawScope().draw( - density = Density(1f, 1f), - layoutDirection = LayoutDirection.Ltr, - canvas = canvas, - size = Size(width.toFloat(), height.toFloat()) - ) { - draw(this@draw.size, alpha, colorFilter) - } - - return bmp -} \ No newline at end of file diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DrawCache.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DrawCache.kt deleted file mode 100644 index 7a76bd5..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/DrawCache.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.github.alexzhirkevich.qrose - -import androidx.compose.ui.graphics.BlendMode -import androidx.compose.ui.graphics.Canvas -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.drawscope.CanvasDrawScope -import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.toSize - diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/QrCodePainter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/QrCodePainter.kt index 4a39900..2b1499b 100644 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/QrCodePainter.kt +++ b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/QrCodePainter.kt @@ -8,7 +8,6 @@ import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.Matrix import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathFillType @@ -16,25 +15,23 @@ import androidx.compose.ui.graphics.PathOperation import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.unit.IntSize -import io.github.alexzhirkevich.qrose.options.QrErrorCorrectionLevel -import io.github.alexzhirkevich.qrose.options.QrOptions -import io.github.alexzhirkevich.qrose.options.dsl.QrOptionsBuilderScope import io.github.alexzhirkevich.qrose.options.Neighbors import io.github.alexzhirkevich.qrose.options.QrBrush import io.github.alexzhirkevich.qrose.options.QrBrushMode import io.github.alexzhirkevich.qrose.options.QrCodeMatrix import io.github.alexzhirkevich.qrose.options.QrColors +import io.github.alexzhirkevich.qrose.options.QrErrorCorrectionLevel import io.github.alexzhirkevich.qrose.options.QrLogo import io.github.alexzhirkevich.qrose.options.QrLogoPadding +import io.github.alexzhirkevich.qrose.options.QrOptions import io.github.alexzhirkevich.qrose.options.QrShapeModifier import io.github.alexzhirkevich.qrose.options.QrShapes +import io.github.alexzhirkevich.qrose.options.dsl.QrOptionsBuilderScope import io.github.alexzhirkevich.qrose.options.isSpecified import io.github.alexzhirkevich.qrose.options.neighbors import io.github.alexzhirkevich.qrose.options.newPath import io.github.alexzhirkevich.qrose.qrcode.ErrorCorrectionLevel import io.github.alexzhirkevich.qrose.qrcode.QRCode -import kotlin.math.ceil import kotlin.math.roundToInt /** diff --git a/qrose/src/skikoMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt b/qrose/src/skikoMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt deleted file mode 100644 index 1b7b4ae..0000000 --- a/qrose/src/skikoMain/kotlin/io/github/alexzhirkevich/qrose/toByteArray.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.alexzhirkevich.qrose - -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.asSkiaBitmap -import org.jetbrains.skia.EncodedImageFormat -import org.jetbrains.skia.Image - -actual typealias ImageFormat = EncodedImageFormat - -actual fun ImageBitmap.toByteArray( - format: ImageFormat -): ByteArray { - val data = Image - .makeFromBitmap(asSkiaBitmap()) - .encodeToData(format) ?: error("This painter cannot be encoded to $format") - - return data.bytes -} \ No newline at end of file From 9641cbad24b5ea28cf044eb24daf2d610f2049fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D0=B8=D1=80=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Mon, 4 Mar 2024 01:28:29 +0300 Subject: [PATCH 11/13] remove unused declarations --- .../alexzhirkevich/qrose/CachedPainter.kt | 93 ------------------- 1 file changed, 93 deletions(-) delete mode 100644 qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt diff --git a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt b/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt deleted file mode 100644 index 301082b..0000000 --- a/qrose/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/CachedPainter.kt +++ /dev/null @@ -1,93 +0,0 @@ -package io.github.alexzhirkevich.qrose - -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.BlendMode -import androidx.compose.ui.graphics.Canvas -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.drawscope.CanvasDrawScope -import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.toSize -import kotlin.math.ceil - -/** - * Creates a drawing environment that directs its drawing commands to an [ImageBitmap] - * which can be drawn directly in another [DrawScope] instance. This is useful to cache - * complicated drawing commands across frames especially if the content has not changed. - * Additionally some drawing operations such as rendering paths are done purely in - * software so it is beneficial to cache the result and render the contents - * directly through a texture as done by [DrawScope.drawImage] - */ -private class DrawCache { - - @PublishedApi internal var mCachedImage: ImageBitmap? = null - private var cachedCanvas: Canvas? = null - private var scopeDensity: Density? = null - private var layoutDirection: LayoutDirection = LayoutDirection.Ltr - private var size: IntSize = IntSize.Zero - - private val cacheScope = CanvasDrawScope() - - /** - * Draw the contents of the lambda with receiver scope into an [ImageBitmap] with the provided - * size. If the same size is provided across calls, the same [ImageBitmap] instance is - * re-used and the contents are cleared out before drawing content in it again - */ - fun drawCachedImage( - size: IntSize, - density: Density, - layoutDirection: LayoutDirection, - block: DrawScope.() -> Unit - ) { - this.scopeDensity = density - this.layoutDirection = layoutDirection - var targetImage = mCachedImage - var targetCanvas = cachedCanvas - if (targetImage == null || - targetCanvas == null || - size.width > targetImage.width || - size.height > targetImage.height - ) { - targetImage = ImageBitmap(size.width, size.height) - targetCanvas = Canvas(targetImage) - - mCachedImage = targetImage - cachedCanvas = targetCanvas - } - this.size = size - cacheScope.draw(density, layoutDirection, targetCanvas, size.toSize()) { - clear() - block() - } - targetImage.prepareToDraw() - } - - /** - * Draw the cached content into the provided [DrawScope] instance - */ - fun drawInto( - target: DrawScope, - alpha: Float = 1.0f, - colorFilter: ColorFilter? = null - ) { - val targetImage = mCachedImage - check(targetImage != null) { - "drawCachedImage must be invoked first before attempting to draw the result " + - "into another destination" - } - target.drawImage(targetImage, srcSize = size, alpha = alpha, colorFilter = colorFilter) - } - - /** - * Helper method to clear contents of the draw environment from the given bounds of the - * DrawScope - */ - private fun DrawScope.clear() { - drawRect(color = Color.Black, blendMode = BlendMode.Clear) - } -} \ No newline at end of file From db0ed7edb631a79e73ea6b047320e857f7595a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D0=B8=D1=80=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Mon, 4 Mar 2024 01:32:27 +0300 Subject: [PATCH 12/13] fix incorrect barcode encoder --- .../kotlin/io/github/alexzhirkevich/qrose/oned/BarcodeType.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodeType.kt b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodeType.kt index 610d3a7..fdeb381 100644 --- a/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodeType.kt +++ b/qrose-oned/src/commonMain/kotlin/io/github/alexzhirkevich/qrose/oned/BarcodeType.kt @@ -4,7 +4,7 @@ enum class BarcodeType(val encoder: BarcodeEncoder) { Codabar(CodabarEncoder), Code39(Code39Encoder), - Code93(Code39Encoder), + Code93(Code93Encoder), Code128(Code128Encoder), EAN8(CodeEAN8Encoder), EAN13(CodeEAN13Encoder), From 7dca5a15ba9b9eaf507a78b9e0acb3a742022679 Mon Sep 17 00:00:00 2001 From: Alexander Zhirkevich Date: Wed, 12 Jun 2024 20:47:32 +0300 Subject: [PATCH 13/13] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 92b2501..3e2a49b 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ Why QRose? dependencies { // For QR codes - implementation("io.github.alexzhirkevich:qrose:1.0.0") + implementation("io.github.alexzhirkevich:qrose:") // For single-dimension barcodes (UPC,EAN, Code128, ...) - implementation("io.github.alexzhirkevich:qrose-oned:1.0.0") + implementation("io.github.alexzhirkevich:qrose-oned:") } ```