Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 34 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,29 @@
> Mostly notable changes from version to version. Some stuff might go undocumented. If you find something that you think
> should be documented, please open an [issue](https://github.com/g0dkar/qrcode-kotlin/issues) :)

# 4.5.0 - Latest
# 4.6.0 - Latest

## ✨ New

- Added a `resizeCanvas()` function to `QRCode` to make it possible to resize only the Canvas.
- You can now have canvas sizes that are NOT squares (e.g.: a `1024x768` canvas)
- By default, they'll be squares. But you can `resizeCanvas()` (or most likely `fitIntoArea()`) with different
`width` and `height` parameters.
- A new example (Example 09) showing this new behaviour.

## ♻️ Changed

- After resizing the canvas (either via `resizeCanvas()` or `fitIntoArea()`) the QRCode will be realigned inside the new
canvas. **Default is to stay centered.**

## 🔧 Fixed

- _DEVELOPMENT STILL IN PROGRESS_

# 4.5.0

## ✨ New

- Added (experimental) support for WASM targets (requested via Issues #140 and #167)
- Please do let us know if you run into any issues with it <3

Expand All @@ -17,15 +37,21 @@
> I'm trying to keep a better CHANGELOG from now on ^^

## 🔧 Fixed
- **Fixed an issue with rendering the Timing Pattern.** I have known it for a while, but now I finally figured what was the issue and fixed it.

- **Fixed an issue with rendering the Timing Pattern.** I have known it for a while, but now I finally figured what was
the issue and fixed it.

## ♻️ Changed

- Changed default ECL from `VERY_HIGH` to `LOW` as to stay closer to what other tools seems to use as a default
- Computing the `informationDensity` value now always goes for the **least possible value** _(down from a minimum of 6 set by `QRCodeBuilder`)_
- Computing the `informationDensity` value now always goes for the **least possible value** _(down from a minimum of 6
set by `QRCodeBuilder`)_
- Better documentation of methods - this is an ongoing initiative!

## ✨ New
- New `InsufficientInformationDensityException`: instead of an `IllegalArgumentException`, this new exception is thrown with a more helpful message

- New `InsufficientInformationDensityException`: instead of an `IllegalArgumentException`, this new exception is thrown
with a more helpful message
- Added `drawQRCode()` extension function to a Android Compose `DrawScope` to draw QRCodes into modern Android.
- Idea/request from Issue #141 by @dgmltn (Thanks!)
- Added examples demonstrating what the ECL does (same data, different ECLs)
Expand All @@ -34,10 +60,13 @@
- Moved example QRCode files to a folder within each language examples, just to reduce clutter :)

## 🚫 Removed
- `forceInformationDensity` was removed. Now the **QRCodeBuilder** class uses `infoDensity = 0` (default value) as a trigger to compute it automatically since it needs to be `>= 1`

- `forceInformationDensity` was removed. Now the **QRCodeBuilder** class uses `infoDensity = 0` (default value) as a
trigger to compute it automatically since it needs to be `>= 1`
- Default value calling `QRCode()` directly is still 6 as to keep a bit of backwards compatibility 😅

## 👀 Internal

- Renamed "typeNum" to "informationDensity"
- Updated dokka and KMP
- Fixed dokka always triggering building the whole `docs/dokka/` folder (that is only for GH Pages)
Expand Down
45 changes: 23 additions & 22 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -79,21 +79,21 @@ kotlin {
}
}

wasmJs {
browser {
commonWebpackConfig {
mode = PRODUCTION
sourceMaps = true
}

testTask {
enabled = false
}

binaries.library()
generateTypeScriptDefinitions()
}
}
// wasmJs {
// browser {
// commonWebpackConfig {
// mode = PRODUCTION
// sourceMaps = true
// }
//
// testTask {
// enabled = false
// }
//
// binaries.library()
// generateTypeScriptDefinitions()
// }
// }

// This is in place just because my main development machine is NOT a macOS :)
// iOS Family of targets... since you can't just "ios()" anymore.
Expand Down Expand Up @@ -131,17 +131,18 @@ kotlin {
}
}

androidTarget {
androidMain {
dependencies {
compileOnly(libs.androidx.compose.ui)
api(libs.androidx.compose.ui)
}
}

wasmJsMain {
dependencies {
implementation(libs.kotlinx.browser)
}
}
// wasmJsMain {
// dependencies {
// implementation(libs.kotlinx.browser)
// api(libs.jetbrains.compose.ui)
// }
// }
}
}

Expand Down
7 changes: 5 additions & 2 deletions examples/android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {

android {
namespace = "io.github.g0dkar.qrcode"
compileSdk = 35
compileSdk = 36

defaultConfig {
applicationId = "io.github.g0dkar.qrcodeKotlin"
Expand All @@ -29,7 +29,10 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
buildFeatures {
viewBinding = true
Expand Down
1 change: 0 additions & 1 deletion examples/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
android:name=".NewQRCodeActivity" />
<activity
android:exported="true"
android:label="@string/app_name"
android:name=".QRCodeListActivity"
android:theme="@style/Theme.QRCodeKotlinExampleApp.NoActionBar">
<intent-filter>
Expand Down
2 changes: 1 addition & 1 deletion examples/java/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ repositories {

dependencies {
implementation("io.github.g0dkar:qrcode-kotlin:4.5.0")
implementation("org.jfree:org.jfree.svg:5.0.5")
implementation("org.jfree:org.jfree.svg:5.0.7")
}
5 changes: 3 additions & 2 deletions examples/kotlin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ repositories {
}

dependencies {
implementation("io.github.g0dkar:qrcode-kotlin:4.5.0")
implementation("org.jfree:org.jfree.svg:5.0.5")
implementation(project(":"))
// implementation("io.github.g0dkar:qrcode-kotlin:4.6.0")
implementation("org.jfree:org.jfree.svg:5.0.7")
}
Binary file modified examples/kotlin/examples-results/example00-simple.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 79 additions & 0 deletions examples/kotlin/src/main/kotlin/Example08-TextCaption.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import qrcode.QRCode
import java.awt.Color
import java.awt.Font
import java.awt.Graphics2D
import java.awt.RenderingHints
import java.awt.geom.Rectangle2D
import java.awt.image.BufferedImage
import java.io.FileOutputStream
import javax.imageio.ImageIO
import kotlin.math.floor
import kotlin.math.max

fun main() {
val canvasCaption = createQRCodeWithCaption("QRCode with Caption")

FileOutputStream("examples/kotlin/examples-results/example08-caption.png")
.use { ImageIO.write(canvasCaption, "PNG", it) }

// ----------------------------------

val customFont = loadFont("MozillaHeadline-Regular.ttf")
val canvasCaptionWithCustomFont = createQRCodeWithCaption("Custom Font: Mozilla Headline", customFont)

FileOutputStream("examples/kotlin/examples-results/example08-caption-customFont.png")
.use { ImageIO.write(canvasCaptionWithCustomFont, "PNG", it) }
}

fun createQRCodeWithCaption(caption: String, customFont: Font? = null): BufferedImage {
val qrCode = QRCode.ofSquares().build(caption)
val qrCodeCanvas = qrCode.render().nativeImage() as BufferedImage
val qrCodeWidth = qrCodeCanvas.width
val qrCodeHeight = qrCodeCanvas.height
val captionTextGraphics2d = qrCodeCanvas.createGraphics()
.apply {
if (customFont != null) {
font = customFont
}
}
val (captionTextWidth, captionTextHeight) = fitText(qrCode.data, qrCodeWidth, captionTextGraphics2d)

// Generate the QRCode + Caption into a BufferedImage and return it
return BufferedImage(
max(qrCodeWidth, captionTextWidth),
qrCodeHeight + captionTextHeight * 2,
BufferedImage.TYPE_INT_ARGB,
).also {
it.createGraphics().apply {
drawImage(qrCodeCanvas, 0, 0, null)

font = captionTextGraphics2d.font
paint = Color.BLACK
background = Color.BLACK
setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
drawString(
caption,
qrCodeWidth / 2 - captionTextWidth / 2,
qrCodeHeight + captionTextHeight / 2 + fontMetrics.ascent,
)
}
}
}

fun loadFont(fontFile: String): Font =
ClassLoader.getSystemResourceAsStream(fontFile)
.use { Font.createFont(Font.TRUETYPE_FONT, it) }

fun fitText(text: String, width: Int, graphics: Graphics2D, fontSizeIncrease: Float = 1.0f): Pair<Int, Int> {
val targetWidth = floor(width * 0.9)
var textSize = graphics.font.size.toFloat()
var textBounds: Rectangle2D

do {
graphics.font = graphics.font.deriveFont(textSize)
textBounds = graphics.fontMetrics.getStringBounds(text, graphics)
textSize += fontSizeIncrease
} while (textBounds.width <= targetWidth)

return textBounds.width.toInt() to textBounds.height.toInt()
}
37 changes: 37 additions & 0 deletions examples/kotlin/src/main/kotlin/Example09-Resizing.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import qrcode.QRCode
import java.io.FileOutputStream

fun main() {
// Calling fitIntoArea()
val qrCode_2xW_2xH_Size = QRCode.ofSquares()
.build("Resizing to 2x the original size")
val qrCode_2xW_2xH_SizeCanvasSize = qrCode_2xW_2xH_Size.canvasSize
qrCode_2xW_2xH_Size.fitIntoArea(qrCode_2xW_2xH_SizeCanvasSize * 2, qrCode_2xW_2xH_SizeCanvasSize * 2)
val qrCode_2xW_2xH_SizePngData = qrCode_2xW_2xH_Size.renderToBytes()

val qrCode_3xW_2xH_Size = QRCode.ofSquares()
.build("Resizing to 3x Width, 2x Height the original size")
val qrCode_3xW_2xH_SizeCanvasSize = qrCode_3xW_2xH_Size.canvasSize
qrCode_3xW_2xH_Size.fitIntoArea(qrCode_3xW_2xH_SizeCanvasSize * 3, qrCode_3xW_2xH_SizeCanvasSize * 2)
val qrCode_3xW_2xH_SizePngData = qrCode_3xW_2xH_Size.renderToBytes()

val qrCode_2xW_3xH_Size = QRCode.ofSquares()
.build("Resizing to 2x Width, 3x Height the original size")
val qrCode_2xW_3xH_SizeCanvasSize = qrCode_2xW_3xH_Size.canvasSize
qrCode_2xW_3xH_Size.fitIntoArea(qrCode_2xW_3xH_SizeCanvasSize * 2, qrCode_2xW_3xH_SizeCanvasSize * 3)
val qrCode_2xW_3xH_SizePngData = qrCode_2xW_3xH_Size.renderToBytes()

FileOutputStream("examples/kotlin/examples-results/example09-2xW-2xH-fitIntoArea.png").use { it.write(qrCode_2xW_2xH_SizePngData) }
FileOutputStream("examples/kotlin/examples-results/example09-3xW-2xH-fitIntoArea.png").use { it.write(qrCode_3xW_2xH_SizePngData) }
FileOutputStream("examples/kotlin/examples-results/example09-2xW-3xH-fitIntoArea.png").use { it.write(qrCode_2xW_3xH_SizePngData) }

// ------------------------------------------
// Calling resize()
val qrCode2xResize = QRCode.ofSquares()
.build("Resizing to 2x the original size")
val qrCode2xResizeCanvasSize = qrCode2xResize.canvasSize
qrCode2xResize.resizeCanvas(qrCode2xResizeCanvasSize * 2)
val qrCode2xResizePngData = qrCode2xResize.renderToBytes()

FileOutputStream("examples/kotlin/examples-results/example09-2xW-2xH-resizeCanvas.png").use { it.write(qrCode2xResizePngData) }
}
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version=4.5.0
version=4.6.0
kotlin.code.style=official

# JS
Expand Down
32 changes: 17 additions & 15 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
[versions]
annotation = "1.9.1"
kotlin = "2.1.21"
dokka = "2.0.0"
kotest = "6.0.0.M3"
lifecycleLivedataKtx = "2.9.1"
lifecycleViewmodelKtx = "2.9.1"
kotlin = "2.2.21"
dokka = "2.1.0"
kotest = "6.0.4"
kotestPlugin = "6.0.0.M3"
lifecycleLivedataKtx = "2.9.4"
lifecycleViewmodelKtx = "2.9.4"
mavenNexus = "2.0.0"
androidPlugin = "8.10.1"
androidPlugin = "8.12.0"
npmPublish = "3.5.3"
core-ktx = "1.16.0"
appcompat = "1.7.0"
material = "1.12.0"
core-ktx = "1.17.0"
appcompat = "1.7.1"
material = "1.13.0"
constraintlayout = "2.2.1"
navigation-fragment-ktx = "2.9.0"
navigation-ui-ktx = "2.9.0"
androidx-compose = "1.8.2"
kotlinx-browser = "0.3"
navigation-fragment-ktx = "2.9.6"
navigation-ui-ktx = "2.9.6"
jetbrains-compose = "1.9.4"
kotlinx-browser = "0.5.0"

[libraries]
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
Expand All @@ -32,13 +33,14 @@ material = { group = "com.google.android.material", name = "material", version.r
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigation-fragment-ktx" }
navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigation-ui-ktx" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "androidx-compose" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "jetbrains-compose" }
jetbrains-compose-ui = { group = "org.jetbrains.compose.ui", name = "ui", version.ref = "jetbrains-compose" }
kotlinx-browser = { module = "org.jetbrains.kotlinx:kotlinx-browser", version.ref = "kotlinx-browser" }

[plugins]
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotest-multiplatform = { id = "io.kotest.multiplatform", version.ref = "kotest" }
kotest-multiplatform = { id = "io.kotest.multiplatform", version.ref = "kotestPlugin" }
android-library = { id = "com.android.library", version.ref = "androidPlugin" }
nexus = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "mavenNexus" }
npmPublish = { id = "dev.petuska.npm.publish", version.ref = "npmPublish" }
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
13 changes: 9 additions & 4 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
dependencyResolutionManagement {
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
google()
gradlePluginPortal()
mavenLocal()
}
}

pluginManagement {
dependencyResolutionManagement {
repositories {
mavenCentral()
google()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ actual open class QRCodeGraphics actual constructor(
actual open fun availableFormats(): Array<String> = AVAILABLE_FORMATS

/** Returns the [Bitmap] or [DrawScope] (if Jetpack Compose is available) object being worked upon. */
actual open fun nativeImage(): Any = drawingInterface?.nativeImage() ?: throw NotImplementedError("Native image not supported")
actual open fun nativeImage(): Any =
drawingInterface?.nativeImage() ?: throw NotImplementedError("Native image not supported")

/** Draw a straight line from point `(x1,y1)` to `(x2,y2)`. */
actual open fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Int, thickness: Double) {
Expand Down
Loading
Loading