@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
@ -0,0 +1,175 @@
|
||||
# Created by https://www.gitignore.io/api/android,androidstudio
|
||||
#gradle.properties
|
||||
#gradlew
|
||||
#gradlew.bat
|
||||
|
||||
### Android ###
|
||||
# Built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# Intellij
|
||||
*.iml
|
||||
.idea/
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/gradle.xml
|
||||
.idea/dictionaries
|
||||
.idea/libraries
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
google-services.json
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
### Android Patch ###
|
||||
gen-external-apklibs
|
||||
|
||||
### AndroidStudio ###
|
||||
# Covers files to be ignored for android development using Android Studio.
|
||||
|
||||
# Built application files
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
|
||||
# Java class files
|
||||
|
||||
# Generated files
|
||||
|
||||
# Gradle files
|
||||
.gradle
|
||||
|
||||
# Signing files
|
||||
.signing/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
|
||||
# Log Files
|
||||
|
||||
# Android Studio
|
||||
/*/build/
|
||||
/*/local.properties
|
||||
/*/out
|
||||
/*/*/build
|
||||
/*/*/production
|
||||
*.ipr
|
||||
*~
|
||||
*.swp
|
||||
|
||||
# Android Patch
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
|
||||
# NDK
|
||||
obj/
|
||||
|
||||
# IntelliJ IDEA
|
||||
*.iws
|
||||
/out/
|
||||
|
||||
# User-specific configurations
|
||||
.idea/libraries/
|
||||
.idea/.name
|
||||
.idea/compiler.xml
|
||||
.idea/copyright/profiles_settings.xml
|
||||
.idea/encodings.xml
|
||||
.idea/misc.xml
|
||||
.idea/modules.xml
|
||||
.idea/scopes/scope_settings.xml
|
||||
.idea/vcs.xml
|
||||
.idea/jsLibraryMappings.xml
|
||||
.idea/datasources.xml
|
||||
.idea/dataSources.ids
|
||||
.idea/sqlDataSources.xml
|
||||
.idea/dynamic.xml
|
||||
.idea/uiDesigner.xml
|
||||
|
||||
# Keystore files
|
||||
|
||||
# OS-specific files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Legacy Eclipse project files
|
||||
.classpath
|
||||
.project
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
|
||||
# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
|
||||
hs_err_pid*
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/mongoSettings.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
|
||||
# End of https://www.gitignore.io/api/android,androidstudio
|
||||
|
||||
#gradle/*
|
||||
publishing-scripts/*
|
@ -0,0 +1,37 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 31
|
||||
|
||||
defaultConfig {
|
||||
applicationId "ru.ra66it.qrcodegeneratorexample"
|
||||
minSdk 19
|
||||
targetSdk 31
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
implementation project(path: ':qrcodegen')
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
@ -0,0 +1,26 @@
|
||||
package ru.ra66it.qrcodegeneratorexample;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("ru.ra66it.qrcodegeneratorexample", appContext.getPackageName());
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="ru.ra66it.qrcodegeneratorexample">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.QRCodeGeneratorExample">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,27 @@
|
||||
package ru.ra66it.qrcodegeneratorexample;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
ImageView imageView = findViewById(R.id.imageView);
|
||||
TextView textView = findViewById(R.id.textView);
|
||||
|
||||
QRCodeGenerator qrCodeGenerator = new QRCodeGenerator();
|
||||
String content = "Hello World!";
|
||||
Bitmap bitmap = qrCodeGenerator.generate(content);
|
||||
|
||||
imageView.setImageBitmap(bitmap);
|
||||
textView.setText(content);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package ru.ra66it.qrcodegeneratorexample;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
|
||||
import ru.ra66it.qrcodegenerator.QrCode;
|
||||
|
||||
public class QRCodeGenerator {
|
||||
|
||||
public Bitmap generate(String content) {
|
||||
QrCode qr = QrCode.encodeText(content, QrCode.Ecc.HIGH);
|
||||
int scale = 8;
|
||||
|
||||
int size = qr.size * scale;
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
||||
|
||||
for (int x = 0; x < size; x++) {
|
||||
for (int y = 0; y < size; y++) {
|
||||
boolean isDark = qr.getModule(x / scale, y / scale);
|
||||
int color;
|
||||
if (isDark) {
|
||||
color = Color.BLACK;
|
||||
} else {
|
||||
color = Color.WHITE;
|
||||
}
|
||||
|
||||
bitmap.setPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/imageView" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 982 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 7.6 KiB |
@ -0,0 +1,16 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.QRCodeGeneratorExample" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">QRCodeGeneratorExample</string>
|
||||
</resources>
|
@ -0,0 +1,16 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.QRCodeGeneratorExample" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
@ -0,0 +1,17 @@
|
||||
package ru.ra66it.qrcodegeneratorexample;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '7.1.3' apply false
|
||||
id 'com.android.library' version '7.1.3' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
@ -0,0 +1,6 @@
|
||||
#Mon Apr 11 15:41:32 MSK 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
@ -0,0 +1,28 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 31
|
||||
|
||||
defaultConfig {
|
||||
minSdk 19
|
||||
targetSdk 31
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
@ -0,0 +1,26 @@
|
||||
package ru.ra66it.qrcodegenerator;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("ru.ra66it.qrcodegenerator.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="ru.ra66it.qrcodegenerator" />
|
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* QR Code generator library (Java)
|
||||
*
|
||||
* Copyright (c) Project Nayuki. (MIT License)
|
||||
* https://www.nayuki.io/page/qr-code-generator-library
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
* - The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* - The Software is provided "as is", without warranty of any kind, express or
|
||||
* implied, including but not limited to the warranties of merchantability,
|
||||
* fitness for a particular purpose and noninfringement. In no event shall the
|
||||
* authors or copyright holders be liable for any claim, damages or other
|
||||
* liability, whether in an action of contract, tort or otherwise, arising from,
|
||||
* out of or in connection with the Software or the use or other dealings in the
|
||||
* Software.
|
||||
*/
|
||||
|
||||
package ru.ra66it.qrcodegenerator;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* An appendable sequence of bits (0s and 1s). Mainly used by {@link QrSegment}.
|
||||
*/
|
||||
public final class BitBuffer implements Cloneable {
|
||||
|
||||
/*---- Fields ----*/
|
||||
|
||||
private BitSet data;
|
||||
|
||||
private int bitLength; // Non-negative
|
||||
|
||||
|
||||
|
||||
/*---- Constructor ----*/
|
||||
|
||||
/**
|
||||
* Constructs an empty bit buffer (length 0).
|
||||
*/
|
||||
public BitBuffer() {
|
||||
data = new BitSet();
|
||||
bitLength = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---- Methods ----*/
|
||||
|
||||
/**
|
||||
* Returns the length of this sequence, which is a non-negative value.
|
||||
*
|
||||
* @return the length of this sequence
|
||||
*/
|
||||
public int bitLength() {
|
||||
assert bitLength >= 0;
|
||||
return bitLength;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the bit at the specified index, yielding 0 or 1.
|
||||
*
|
||||
* @param index the index to get the bit at
|
||||
* @return the bit at the specified index
|
||||
* @throws IndexOutOfBoundsException if index < 0 or index ≥ bitLength
|
||||
*/
|
||||
public int getBit(int index) {
|
||||
if (index < 0 || index >= bitLength)
|
||||
throw new IndexOutOfBoundsException();
|
||||
return data.get(index) ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends the specified number of low-order bits of the specified value to this
|
||||
* buffer. Requires 0 ≤ len ≤ 31 and 0 ≤ val < 2<sup>len</sup>.
|
||||
*
|
||||
* @param val the value to append
|
||||
* @param len the number of low-order bits in the value to take
|
||||
* @throws IllegalArgumentException if the value or number of bits is out of range
|
||||
* @throws IllegalStateException if appending the data
|
||||
* would make bitLength exceed Integer.MAX_VALUE
|
||||
*/
|
||||
public void appendBits(int val, int len) {
|
||||
if (len < 0 || len > 31 || val >>> len != 0)
|
||||
throw new IllegalArgumentException("Value out of range");
|
||||
if (Integer.MAX_VALUE - bitLength < len)
|
||||
throw new IllegalStateException("Maximum length reached");
|
||||
for (int i = len - 1; i >= 0; i--, bitLength++) // Append bit by bit
|
||||
data.set(bitLength, QrCode.getBit(val, i));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends the content of the specified bit buffer to this buffer.
|
||||
*
|
||||
* @param bb the bit buffer whose data to append (not {@code null})
|
||||
* @throws NullPointerException if the bit buffer is {@code null}
|
||||
* @throws IllegalStateException if appending the data
|
||||
* would make bitLength exceed Integer.MAX_VALUE
|
||||
*/
|
||||
public void appendData(BitBuffer bb) {
|
||||
Objects.requireNonNull(bb);
|
||||
if (Integer.MAX_VALUE - bitLength < bb.bitLength)
|
||||
throw new IllegalStateException("Maximum length reached");
|
||||
for (int i = 0; i < bb.bitLength; i++, bitLength++) // Append bit by bit
|
||||
data.set(bitLength, bb.data.get(i));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new copy of this buffer.
|
||||
*
|
||||
* @return a new copy of this buffer (not {@code null})
|
||||
*/
|
||||
public BitBuffer clone() {
|
||||
try {
|
||||
BitBuffer result = (BitBuffer) super.clone();
|
||||
result.data = (BitSet) result.data.clone();
|
||||
return result;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* QR Code generator library (Java)
|
||||
*
|
||||
* Copyright (c) Project Nayuki. (MIT License)
|
||||
* https://www.nayuki.io/page/qr-code-generator-library
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
* - The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* - The Software is provided "as is", without warranty of any kind, express or
|
||||
* implied, including but not limited to the warranties of merchantability,
|
||||
* fitness for a particular purpose and noninfringement. In no event shall the
|
||||
* authors or copyright holders be liable for any claim, damages or other
|
||||
* liability, whether in an action of contract, tort or otherwise, arising from,
|
||||
* out of or in connection with the Software or the use or other dealings in the
|
||||
* Software.
|
||||
*/
|
||||
|
||||
package ru.ra66it.qrcodegenerator;
|
||||
|
||||
|
||||
/**
|
||||
* Thrown when the supplied data does not fit any QR Code version. Ways to handle this exception include:
|
||||
* <ul>
|
||||
* <li><p>Decrease the error correction level if it was greater than {@code Ecc.LOW}.</p></li>
|
||||
* <li><p>If the advanced {@code encodeSegments()} function with 6 arguments or the
|
||||
* {@code makeSegmentsOptimally()} function was called, then increase the maxVersion argument
|
||||
* if it was less than {@link QrCode#MAX_VERSION}. (This advice does not apply to the other
|
||||
* factory functions because they search all versions up to {@code QrCode.MAX_VERSION}.)</p></li>
|
||||
* <li><p>Split the text data into better or optimal segments in order to reduce the number of
|
||||
* bits required. (See {@link QrSegmentAdvanced#makeSegmentsOptimally(String, QrCode.Ecc, int, int)
|
||||
* QrSegmentAdvanced.makeSegmentsOptimally()}.)</p></li>
|
||||
* <li><p>Change the text or binary data to be shorter.</p></li>
|
||||
* <li><p>Change the text to fit the character set of a particular segment mode (e.g. alphanumeric).</p></li>
|
||||
* <li><p>Propagate the error upward to the caller/user.</p></li>
|
||||
* </ul>
|
||||
*
|
||||
* @see QrCode#encodeText(CharSequence, QrCode.Ecc)
|
||||
* @see QrCode#encodeBinary(byte[], QrCode.Ecc)
|
||||
* @see QrCode#encodeSegments(java.util.List, QrCode.Ecc)
|
||||
* @see QrCode#encodeSegments(java.util.List, QrCode.Ecc, int, int, int, boolean)
|
||||
* @see QrSegmentAdvanced#makeSegmentsOptimally(String, QrCode.Ecc, int, int)
|
||||
*/
|
||||
public class DataTooLongException extends IllegalArgumentException {
|
||||
|
||||
public DataTooLongException() {
|
||||
}
|
||||
|
||||
|
||||
public DataTooLongException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,868 @@
|
||||
/*
|
||||
* QR Code generator library (Java)
|
||||
*
|
||||
* Copyright (c) Project Nayuki. (MIT License)
|
||||
* https://www.nayuki.io/page/qr-code-generator-library
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
* - The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* - The Software is provided "as is", without warranty of any kind, express or
|
||||
* implied, including but not limited to the warranties of merchantability,
|
||||
* fitness for a particular purpose and noninfringement. In no event shall the
|
||||
* authors or copyright holders be liable for any claim, damages or other
|
||||
* liability, whether in an action of contract, tort or otherwise, arising from,
|
||||
* out of or in connection with the Software or the use or other dealings in the
|
||||
* Software.
|
||||
*/
|
||||
|
||||
package ru.ra66it.qrcodegenerator;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* A QR Code symbol, which is a type of two-dimension barcode.
|
||||
* Invented by Denso Wave and described in the ISO/IEC 18004 standard.
|
||||
* <p>Instances of this class represent an immutable square grid of dark and light cells.
|
||||
* The class provides static factory functions to create a QR Code from text or binary data.
|
||||
* The class covers the QR Code Model 2 specification, supporting all versions (sizes)
|
||||
* from 1 to 40, all 4 error correction levels, and 4 character encoding modes.</p>
|
||||
* <p>Ways to create a QR Code object:</p>
|
||||
* <ul>
|
||||
* <li><p>High level: Take the payload data and call {@link QrCode#encodeText(CharSequence, Ecc)}
|
||||
* or {@link QrCode#encodeBinary(byte[], Ecc)}.</p></li>
|
||||
* <li><p>Mid level: Custom-make the list of {@link QrSegment segments}
|
||||
* and call {@link QrCode#encodeSegments(List, Ecc)} or
|
||||
* {@link QrCode#encodeSegments(List, Ecc, int, int, int, boolean)}</p></li>
|
||||
* <li><p>Low level: Custom-make the array of data codeword bytes (including segment headers and
|
||||
* final padding, excluding error correction codewords), supply the appropriate version number,
|
||||
* and call the {@link QrCode#QrCode(int, Ecc, byte[], int) constructor}.</p></li>
|
||||
* </ul>
|
||||
* <p>(Note that all ways require supplying the desired error correction level.)</p>
|
||||
*
|
||||
* @see QrSegment
|
||||
*/
|
||||
public final class QrCode {
|
||||
|
||||
/*---- Static factory functions (high level) ----*/
|
||||
|
||||
/**
|
||||
* Returns a QR Code representing the specified Unicode text string at the specified error correction level.
|
||||
* As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer
|
||||
* Unicode code points (not UTF-16 code units) if the low error correction level is used. The smallest possible
|
||||
* QR Code version is automatically chosen for the output. The ECC level of the result may be higher than the
|
||||
* ecl argument if it can be done without increasing the version.
|
||||
*
|
||||
* @param text the text to be encoded (not {@code null}), which can be any Unicode string
|
||||
* @param ecl the error correction level to use (not {@code null}) (boostable)
|
||||
* @return a QR Code (not {@code null}) representing the text
|
||||
* @throws NullPointerException if the text or error correction level is {@code null}
|
||||
* @throws DataTooLongException if the text fails to fit in the
|
||||
* largest version QR Code at the ECL, which means it is too long
|
||||
*/
|
||||
public static QrCode encodeText(CharSequence text, Ecc ecl) {
|
||||
Objects.requireNonNull(text);
|
||||
Objects.requireNonNull(ecl);
|
||||
List<QrSegment> segs = QrSegment.makeSegments(text);
|
||||
return encodeSegments(segs, ecl);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a QR Code representing the specified binary data at the specified error correction level.
|
||||
* This function always encodes using the binary segment mode, not any text mode. The maximum number of
|
||||
* bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.
|
||||
* The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version.
|
||||
*
|
||||
* @param data the binary data to encode (not {@code null})
|
||||
* @param ecl the error correction level to use (not {@code null}) (boostable)
|
||||
* @return a QR Code (not {@code null}) representing the data
|
||||
* @throws NullPointerException if the data or error correction level is {@code null}
|
||||
* @throws DataTooLongException if the data fails to fit in the
|
||||
* largest version QR Code at the ECL, which means it is too long
|
||||
*/
|
||||
public static QrCode encodeBinary(byte[] data, Ecc ecl) {
|
||||
Objects.requireNonNull(data);
|
||||
Objects.requireNonNull(ecl);
|
||||
QrSegment seg = QrSegment.makeBytes(data);
|
||||
return encodeSegments(Collections.singletonList(seg), ecl);
|
||||
}
|
||||
|
||||
|
||||
/*---- Static factory functions (mid level) ----*/
|
||||
|
||||
/**
|
||||
* Returns a QR Code representing the specified segments at the specified error correction
|
||||
* level. The smallest possible QR Code version is automatically chosen for the output. The ECC level
|
||||
* of the result may be higher than the ecl argument if it can be done without increasing the version.
|
||||
* <p>This function allows the user to create a custom sequence of segments that switches
|
||||
* between modes (such as alphanumeric and byte) to encode text in less space.
|
||||
* This is a mid-level API; the high-level API is {@link #encodeText(CharSequence, Ecc)}
|
||||
* and {@link #encodeBinary(byte[], Ecc)}.</p>
|
||||
*
|
||||
* @param segs the segments to encode
|
||||
* @param ecl the error correction level to use (not {@code null}) (boostable)
|
||||
* @return a QR Code (not {@code null}) representing the segments
|
||||
* @throws NullPointerException if the list of segments, any segment, or the error correction level is {@code null}
|
||||
* @throws DataTooLongException if the segments fail to fit in the
|
||||
* largest version QR Code at the ECL, which means they are too long
|
||||
*/
|
||||
public static QrCode encodeSegments(List<QrSegment> segs, Ecc ecl) {
|
||||
return encodeSegments(segs, ecl, MIN_VERSION, MAX_VERSION, -1, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a QR Code representing the specified segments with the specified encoding parameters.
|
||||
* The smallest possible QR Code version within the specified range is automatically
|
||||
* chosen for the output. Iff boostEcl is {@code true}, then the ECC level of the
|
||||
* result may be higher than the ecl argument if it can be done without increasing
|
||||
* the version. The mask number is either between 0 to 7 (inclusive) to force that
|
||||
* mask, or −1 to automatically choose an appropriate mask (which may be slow).
|
||||
* <p>This function allows the user to create a custom sequence of segments that switches
|
||||
* between modes (such as alphanumeric and byte) to encode text in less space.
|
||||
* This is a mid-level API; the high-level API is {@link #encodeText(CharSequence, Ecc)}
|
||||
* and {@link #encodeBinary(byte[], Ecc)}.</p>
|
||||
*
|
||||
* @param segs the segments to encode
|
||||
* @param ecl the error correction level to use (not {@code null}) (boostable)
|
||||
* @param minVersion the minimum allowed version of the QR Code (at least 1)
|
||||
* @param maxVersion the maximum allowed version of the QR Code (at most 40)
|
||||
* @param mask the mask number to use (between 0 and 7 (inclusive)), or −1 for automatic mask
|
||||
* @param boostEcl increases the ECC level as long as it doesn't increase the version number
|
||||
* @return a QR Code (not {@code null}) representing the segments
|
||||
* @throws NullPointerException if the list of segments, any segment, or the error correction level is {@code null}
|
||||
* @throws IllegalArgumentException if 1 ≤ minVersion ≤ maxVersion ≤ 40
|
||||
* or −1 ≤ mask ≤ 7 is violated
|
||||
* @throws DataTooLongException if the segments fail to fit in
|
||||
* the maxVersion QR Code at the ECL, which means they are too long
|
||||
*/
|
||||
public static QrCode encodeSegments(List<QrSegment> segs, Ecc ecl, int minVersion, int maxVersion, int mask, boolean boostEcl) {
|
||||
Objects.requireNonNull(segs);
|
||||
Objects.requireNonNull(ecl);
|
||||
if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7)
|
||||
throw new IllegalArgumentException("Invalid value");
|
||||
|
||||
// Find the minimal version number to use
|
||||
int version, dataUsedBits;
|
||||
for (version = minVersion; ; version++) {
|
||||
int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available
|
||||
dataUsedBits = QrSegment.getTotalBits(segs, version);
|
||||
if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits)
|
||||
break; // This version number is found to be suitable
|
||||
if (version >= maxVersion) { // All versions in the range could not fit the given data
|
||||
String msg = "Segment too long";
|
||||
if (dataUsedBits != -1)
|
||||
msg = String.format("Data length = %d bits, Max capacity = %d bits", dataUsedBits, dataCapacityBits);
|
||||
throw new DataTooLongException(msg);
|
||||
}
|
||||
}
|
||||
assert dataUsedBits != -1;
|
||||
|
||||
// Increase the error correction level while the data still fits in the current version number
|
||||
for (Ecc newEcl : Ecc.values()) { // From low to high
|
||||
if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8)
|
||||
ecl = newEcl;
|
||||
}
|
||||
|
||||
// Concatenate all segments to create the data bit string
|
||||
BitBuffer bb = new BitBuffer();
|
||||
for (QrSegment seg : segs) {
|
||||
bb.appendBits(seg.mode.modeBits, 4);
|
||||
bb.appendBits(seg.numChars, seg.mode.numCharCountBits(version));
|
||||
bb.appendData(seg.data);
|
||||
}
|
||||
assert bb.bitLength() == dataUsedBits;
|
||||
|
||||
// Add terminator and pad up to a byte if applicable
|
||||
int dataCapacityBits = getNumDataCodewords(version, ecl) * 8;
|
||||
assert bb.bitLength() <= dataCapacityBits;
|
||||
bb.appendBits(0, Math.min(4, dataCapacityBits - bb.bitLength()));
|
||||
bb.appendBits(0, (8 - bb.bitLength() % 8) % 8);
|
||||
assert bb.bitLength() % 8 == 0;
|
||||
|
||||
// Pad with alternating bytes until data capacity is reached
|
||||
for (int padByte = 0xEC; bb.bitLength() < dataCapacityBits; padByte ^= 0xEC ^ 0x11)
|
||||
bb.appendBits(padByte, 8);
|
||||
|
||||
// Pack bits into bytes in big endian
|
||||
byte[] dataCodewords = new byte[bb.bitLength() / 8];
|
||||
for (int i = 0; i < bb.bitLength(); i++)
|
||||
dataCodewords[i >>> 3] |= bb.getBit(i) << (7 - (i & 7));
|
||||
|
||||
// Create the QR Code object
|
||||
return new QrCode(version, ecl, dataCodewords, mask);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---- Instance fields ----*/
|
||||
|
||||
// Public immutable scalar parameters:
|
||||
|
||||
/**
|
||||
* The version number of this QR Code, which is between 1 and 40 (inclusive).
|
||||
* This determines the size of this barcode.
|
||||
*/
|
||||
public final int version;
|
||||
|
||||
/**
|
||||
* The width and height of this QR Code, measured in modules, between
|
||||
* 21 and 177 (inclusive). This is equal to version × 4 + 17.
|
||||
*/
|
||||
public final int size;
|
||||
|
||||
/**
|
||||
* The error correction level used in this QR Code, which is not {@code null}.
|
||||
*/
|
||||
public final Ecc errorCorrectionLevel;
|
||||
|
||||
/**
|
||||
* The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive).
|
||||
* <p>Even if a QR Code is created with automatic masking requested (mask =
|
||||
* −1), the resulting object still has a mask value between 0 and 7.
|
||||
*/
|
||||
public final int mask;
|
||||
|
||||
// Private grids of modules/pixels, with dimensions of size*size:
|
||||
|
||||
// The modules of this QR Code (false = light, true = dark).
|
||||
// Immutable after constructor finishes. Accessed through getModule().
|
||||
private final boolean[][] modules;
|
||||
|
||||
// Indicates function modules that are not subjected to masking. Discarded when constructor finishes.
|
||||
private boolean[][] isFunction;
|
||||
|
||||
|
||||
|
||||
/*---- Constructor (low level) ----*/
|
||||
|
||||
/**
|
||||
* Constructs a QR Code with the specified version number,
|
||||
* error correction level, data codeword bytes, and mask number.
|
||||
* <p>This is a low-level API that most users should not use directly. A mid-level
|
||||
* API is the {@link #encodeSegments(List, Ecc, int, int, int, boolean)} function.</p>
|
||||
*
|
||||
* @param ver the version number to use, which must be in the range 1 to 40 (inclusive)
|
||||
* @param ecl the error correction level to use
|
||||
* @param dataCodewords the bytes representing segments to encode (without ECC)
|
||||
* @param msk the mask pattern to use, which is either −1 for automatic choice or from 0 to 7 for fixed choice
|
||||
* @throws NullPointerException if the byte array or error correction level is {@code null}
|
||||
* @throws IllegalArgumentException if the version or mask value is out of range,
|
||||
* or if the data is the wrong length for the specified version and error correction level
|
||||
*/
|
||||
public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int msk) {
|
||||
// Check arguments and initialize fields
|
||||
if (ver < MIN_VERSION || ver > MAX_VERSION)
|
||||
throw new IllegalArgumentException("Version value out of range");
|
||||
if (msk < -1 || msk > 7)
|
||||
throw new IllegalArgumentException("Mask value out of range");
|
||||
version = ver;
|
||||
size = ver * 4 + 17;
|
||||
errorCorrectionLevel = Objects.requireNonNull(ecl);
|
||||
Objects.requireNonNull(dataCodewords);
|
||||
modules = new boolean[size][size]; // Initially all light
|
||||
isFunction = new boolean[size][size];
|
||||
|
||||
// Compute ECC, draw modules, do masking
|
||||
drawFunctionPatterns();
|
||||
byte[] allCodewords = addEccAndInterleave(dataCodewords);
|
||||
drawCodewords(allCodewords);
|
||||
|
||||
// Do masking
|
||||
if (msk == -1) { // Automatically choose best mask
|
||||
int minPenalty = Integer.MAX_VALUE;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
applyMask(i);
|
||||
drawFormatBits(i);
|
||||
int penalty = getPenaltyScore();
|
||||
if (penalty < minPenalty) {
|
||||
msk = i;
|
||||
minPenalty = penalty;
|
||||
}
|
||||
applyMask(i); // Undoes the mask due to XOR
|
||||
}
|
||||
}
|
||||
assert 0 <= msk && msk <= 7;
|
||||
mask = msk;
|
||||
applyMask(msk); // Apply the final choice of mask
|
||||
drawFormatBits(msk); // Overwrite old format bits
|
||||
|
||||
isFunction = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---- Public instance methods ----*/
|
||||
|
||||
/**
|
||||
* Returns the color of the module (pixel) at the specified coordinates, which is {@code false}
|
||||
* for light or {@code true} for dark. The top left corner has the coordinates (x=0, y=0).
|
||||
* If the specified coordinates are out of bounds, then {@code false} (light) is returned.
|
||||
*
|
||||
* @param x the x coordinate, where 0 is the left edge and size−1 is the right edge
|
||||
* @param y the y coordinate, where 0 is the top edge and size−1 is the bottom edge
|
||||
* @return {@code true} if the coordinates are in bounds and the module
|
||||
* at that location is dark, or {@code false} (light) otherwise
|
||||
*/
|
||||
public boolean getModule(int x, int y) {
|
||||
return 0 <= x && x < size && 0 <= y && y < size && modules[y][x];
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---- Private helper methods for constructor: Drawing function modules ----*/
|
||||
|
||||
// Reads this object's version field, and draws and marks all function modules.
|
||||
private void drawFunctionPatterns() {
|
||||
// Draw horizontal and vertical timing patterns
|
||||
for (int i = 0; i < size; i++) {
|
||||
setFunctionModule(6, i, i % 2 == 0);
|
||||
setFunctionModule(i, 6, i % 2 == 0);
|
||||
}
|
||||
|
||||
// Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
|
||||
drawFinderPattern(3, 3);
|
||||
drawFinderPattern(size - 4, 3);
|
||||
drawFinderPattern(3, size - 4);
|
||||
|
||||
// Draw numerous alignment patterns
|
||||
int[] alignPatPos = getAlignmentPatternPositions();
|
||||
int numAlign = alignPatPos.length;
|
||||
for (int i = 0; i < numAlign; i++) {
|
||||
for (int j = 0; j < numAlign; j++) {
|
||||
// Don't draw on the three finder corners
|
||||
if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0))
|
||||
drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw configuration data
|
||||
drawFormatBits(0); // Dummy mask value; overwritten later in the constructor
|
||||
drawVersion();
|
||||
}
|
||||
|
||||
|
||||
// Draws two copies of the format bits (with its own error correction code)
|
||||
// based on the given mask and this object's error correction level field.
|
||||
private void drawFormatBits(int msk) {
|
||||
// Calculate error correction code and pack bits
|
||||
int data = errorCorrectionLevel.formatBits << 3 | msk; // errCorrLvl is uint2, mask is uint3
|
||||
int rem = data;
|
||||
for (int i = 0; i < 10; i++)
|
||||
rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
|
||||
int bits = (data << 10 | rem) ^ 0x5412; // uint15
|
||||
assert bits >>> 15 == 0;
|
||||
|
||||
// Draw first copy
|
||||
for (int i = 0; i <= 5; i++)
|
||||
setFunctionModule(8, i, getBit(bits, i));
|
||||
setFunctionModule(8, 7, getBit(bits, 6));
|
||||
setFunctionModule(8, 8, getBit(bits, 7));
|
||||
setFunctionModule(7, 8, getBit(bits, 8));
|
||||
for (int i = 9; i < 15; i++)
|
||||
setFunctionModule(14 - i, 8, getBit(bits, i));
|
||||
|
||||
// Draw second copy
|
||||
for (int i = 0; i < 8; i++)
|
||||
setFunctionModule(size - 1 - i, 8, getBit(bits, i));
|
||||
for (int i = 8; i < 15; i++)
|
||||
setFunctionModule(8, size - 15 + i, getBit(bits, i));
|
||||
setFunctionModule(8, size - 8, true); // Always dark
|
||||
}
|
||||
|
||||
|
||||
// Draws two copies of the version bits (with its own error correction code),
|
||||
// based on this object's version field, iff 7 <= version <= 40.
|
||||
private void drawVersion() {
|
||||
if (version < 7)
|
||||
return;
|
||||
|
||||
// Calculate error correction code and pack bits
|
||||
int rem = version; // version is uint6, in the range [7, 40]
|
||||
for (int i = 0; i < 12; i++)
|
||||
rem = (rem << 1) ^ ((rem >>> 11) * 0x1F25);
|
||||
int bits = version << 12 | rem; // uint18
|
||||
assert bits >>> 18 == 0;
|
||||
|
||||
// Draw two copies
|
||||
for (int i = 0; i < 18; i++) {
|
||||
boolean bit = getBit(bits, i);
|
||||
int a = size - 11 + i % 3;
|
||||
int b = i / 3;
|
||||
setFunctionModule(a, b, bit);
|
||||
setFunctionModule(b, a, bit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draws a 9*9 finder pattern including the border separator,
|
||||
// with the center module at (x, y). Modules can be out of bounds.
|
||||
private void drawFinderPattern(int x, int y) {
|
||||
for (int dy = -4; dy <= 4; dy++) {
|
||||
for (int dx = -4; dx <= 4; dx++) {
|
||||
int dist = Math.max(Math.abs(dx), Math.abs(dy)); // Chebyshev/infinity norm
|
||||
int xx = x + dx, yy = y + dy;
|
||||
if (0 <= xx && xx < size && 0 <= yy && yy < size)
|
||||
setFunctionModule(xx, yy, dist != 2 && dist != 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draws a 5*5 alignment pattern, with the center module
|
||||
// at (x, y). All modules must be in bounds.
|
||||
private void drawAlignmentPattern(int x, int y) {
|
||||
for (int dy = -2; dy <= 2; dy++) {
|
||||
for (int dx = -2; dx <= 2; dx++)
|
||||
setFunctionModule(x + dx, y + dy, Math.max(Math.abs(dx), Math.abs(dy)) != 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Sets the color of a module and marks it as a function module.
|
||||
// Only used by the constructor. Coordinates must be in bounds.
|
||||
private void setFunctionModule(int x, int y, boolean isDark) {
|
||||
modules[y][x] = isDark;
|
||||
isFunction[y][x] = true;
|
||||
}
|
||||
|
||||
|
||||
/*---- Private helper methods for constructor: Codewords and masking ----*/
|
||||
|
||||
// Returns a new byte string representing the given data with the appropriate error correction
|
||||
// codewords appended to it, based on this object's version and error correction level.
|
||||
private byte[] addEccAndInterleave(byte[] data) {
|
||||
Objects.requireNonNull(data);
|
||||
if (data.length != getNumDataCodewords(version, errorCorrectionLevel))
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
// Calculate parameter numbers
|
||||
int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[errorCorrectionLevel.ordinal()][version];
|
||||
int blockEccLen = ECC_CODEWORDS_PER_BLOCK[errorCorrectionLevel.ordinal()][version];
|
||||
int rawCodewords = getNumRawDataModules(version) / 8;
|
||||
int numShortBlocks = numBlocks - rawCodewords % numBlocks;
|
||||
int shortBlockLen = rawCodewords / numBlocks;
|
||||
|
||||
// Split data into blocks and append ECC to each block
|
||||
byte[][] blocks = new byte[numBlocks][];
|
||||
byte[] rsDiv = reedSolomonComputeDivisor(blockEccLen);
|
||||
for (int i = 0, k = 0; i < numBlocks; i++) {
|
||||
byte[] dat = Arrays.copyOfRange(data, k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1));
|
||||
k += dat.length;
|
||||
byte[] block = Arrays.copyOf(dat, shortBlockLen + 1);
|
||||
byte[] ecc = reedSolomonComputeRemainder(dat, rsDiv);
|
||||
System.arraycopy(ecc, 0, block, block.length - blockEccLen, ecc.length);
|
||||
blocks[i] = block;
|
||||
}
|
||||
|
||||
// Interleave (not concatenate) the bytes from every block into a single sequence
|
||||
byte[] result = new byte[rawCodewords];
|
||||
for (int i = 0, k = 0; i < blocks[0].length; i++) {
|
||||
for (int j = 0; j < blocks.length; j++) {
|
||||
// Skip the padding byte in short blocks
|
||||
if (i != shortBlockLen - blockEccLen || j >= numShortBlocks) {
|
||||
result[k] = blocks[j][i];
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
|
||||
// data area of this QR Code. Function modules need to be marked off before this is called.
|
||||
private void drawCodewords(byte[] data) {
|
||||
Objects.requireNonNull(data);
|
||||
if (data.length != getNumRawDataModules(version) / 8)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
int i = 0; // Bit index into the data
|
||||
// Do the funny zigzag scan
|
||||
for (int right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair
|
||||
if (right == 6)
|
||||
right = 5;
|
||||
for (int vert = 0; vert < size; vert++) { // Vertical counter
|
||||
for (int j = 0; j < 2; j++) {
|
||||
int x = right - j; // Actual x coordinate
|
||||
boolean upward = ((right + 1) & 2) == 0;
|
||||
int y = upward ? size - 1 - vert : vert; // Actual y coordinate
|
||||
if (!isFunction[y][x] && i < data.length * 8) {
|
||||
modules[y][x] = getBit(data[i >>> 3], 7 - (i & 7));
|
||||
i++;
|
||||
}
|
||||
// If this QR Code has any remainder bits (0 to 7), they were assigned as
|
||||
// 0/false/light by the constructor and are left unchanged by this method
|
||||
}
|
||||
}
|
||||
}
|
||||
assert i == data.length * 8;
|
||||
}
|
||||
|
||||
|
||||
// XORs the codeword modules in this QR Code with the given mask pattern.
|
||||
// The function modules must be marked and the codeword bits must be drawn
|
||||
// before masking. Due to the arithmetic of XOR, calling applyMask() with
|
||||
// the same mask value a second time will undo the mask. A final well-formed
|
||||
// QR Code needs exactly one (not zero, two, etc.) mask applied.
|
||||
private void applyMask(int msk) {
|
||||
if (msk < 0 || msk > 7)
|
||||
throw new IllegalArgumentException("Mask value out of range");
|
||||
for (int y = 0; y < size; y++) {
|
||||
for (int x = 0; x < size; x++) {
|
||||
boolean invert;
|
||||
switch (msk) {
|
||||
case 0:
|
||||
invert = (x + y) % 2 == 0;
|
||||
break;
|
||||
case 1:
|
||||
invert = y % 2 == 0;
|
||||
break;
|
||||
case 2:
|
||||
invert = x % 3 == 0;
|
||||
break;
|
||||
case 3:
|
||||
invert = (x + y) % 3 == 0;
|
||||
break;
|
||||
case 4:
|
||||
invert = (x / 3 + y / 2) % 2 == 0;
|
||||
break;
|
||||
case 5:
|
||||
invert = x * y % 2 + x * y % 3 == 0;
|
||||
break;
|
||||
case 6:
|
||||
invert = (x * y % 2 + x * y % 3) % 2 == 0;
|
||||
break;
|
||||
case 7:
|
||||
invert = ((x + y) % 2 + x * y % 3) % 2 == 0;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
modules[y][x] ^= invert & !isFunction[y][x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Calculates and returns the penalty score based on state of this QR Code's current modules.
|
||||
// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
|
||||
private int getPenaltyScore() {
|
||||
int result = 0;
|
||||
|
||||
// Adjacent modules in row having same color, and finder-like patterns
|
||||
for (int y = 0; y < size; y++) {
|
||||
boolean runColor = false;
|
||||
int runX = 0;
|
||||
int[] runHistory = new int[7];
|
||||
for (int x = 0; x < size; x++) {
|
||||
if (modules[y][x] == runColor) {
|
||||
runX++;
|
||||
if (runX == 5)
|
||||
result += PENALTY_N1;
|
||||
else if (runX > 5)
|
||||
result++;
|
||||
} else {
|
||||
finderPenaltyAddHistory(runX, runHistory);
|
||||
if (!runColor)
|
||||
result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3;
|
||||
runColor = modules[y][x];
|
||||
runX = 1;
|
||||
}
|
||||
}
|
||||
result += finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3;
|
||||
}
|
||||
// Adjacent modules in column having same color, and finder-like patterns
|
||||
for (int x = 0; x < size; x++) {
|
||||
boolean runColor = false;
|
||||
int runY = 0;
|
||||
int[] runHistory = new int[7];
|
||||
for (int y = 0; y < size; y++) {
|
||||
if (modules[y][x] == runColor) {
|
||||
runY++;
|
||||
if (runY == 5)
|
||||
result += PENALTY_N1;
|
||||
else if (runY > 5)
|
||||
result++;
|
||||
} else {
|
||||
finderPenaltyAddHistory(runY, runHistory);
|
||||
if (!runColor)
|
||||
result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3;
|
||||
runColor = modules[y][x];
|
||||
runY = 1;
|
||||
}
|
||||
}
|
||||
result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3;
|
||||
}
|
||||
|
||||
// 2*2 blocks of modules having same color
|
||||
for (int y = 0; y < size - 1; y++) {
|
||||
for (int x = 0; x < size - 1; x++) {
|
||||
boolean color = modules[y][x];
|
||||
if (color == modules[y][x + 1] &&
|
||||
color == modules[y + 1][x] &&
|
||||
color == modules[y + 1][x + 1])
|
||||
result += PENALTY_N2;
|
||||
}
|
||||
}
|
||||
|
||||
// Balance of dark and light modules
|
||||
int dark = 0;
|
||||
for (boolean[] row : modules) {
|
||||
for (boolean color : row) {
|
||||
if (color)
|
||||
dark++;
|
||||
}
|
||||
}
|
||||
int total = size * size; // Note that size is odd, so dark/total != 1/2
|
||||
// Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)%
|
||||
int k = (Math.abs(dark * 20 - total * 10) + total - 1) / total - 1;
|
||||
assert 0 <= k && k <= 9;
|
||||
result += k * PENALTY_N4;
|
||||
assert 0 <= result && result <= 2568888; // Non-tight upper bound based on default values of PENALTY_N1, ..., N4
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---- Private helper functions ----*/
|
||||
|
||||
// Returns an ascending list of positions of alignment patterns for this version number.
|
||||
// Each position is in the range [0,177), and are used on both the x and y axes.
|
||||
// This could be implemented as lookup table of 40 variable-length lists of unsigned bytes.
|
||||
private int[] getAlignmentPatternPositions() {
|
||||
if (version == 1)
|
||||
return new int[]{};
|
||||
else {
|
||||
int numAlign = version / 7 + 2;
|
||||
int step;
|
||||
if (version == 32) // Special snowflake
|
||||
step = 26;
|
||||
else // step = ceil[(size - 13) / (numAlign * 2 - 2)] * 2
|
||||
step = (version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2;
|
||||
int[] result = new int[numAlign];
|
||||
result[0] = 6;
|
||||
for (int i = result.length - 1, pos = size - 7; i >= 1; i--, pos -= step)
|
||||
result[i] = pos;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Returns the number of data bits that can be stored in a QR Code of the given version number, after
|
||||
// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
|
||||
// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.
|
||||
private static int getNumRawDataModules(int ver) {
|
||||
if (ver < MIN_VERSION || ver > MAX_VERSION)
|
||||
throw new IllegalArgumentException("Version number out of range");
|
||||
|
||||
int size = ver * 4 + 17;
|
||||
int result = size * size; // Number of modules in the whole QR Code square
|
||||
result -= 8 * 8 * 3; // Subtract the three finders with separators
|
||||
result -= 15 * 2 + 1; // Subtract the format information and dark module
|
||||
result -= (size - 16) * 2; // Subtract the timing patterns (excluding finders)
|
||||
// The five lines above are equivalent to: int result = (16 * ver + 128) * ver + 64;
|
||||
if (ver >= 2) {
|
||||
int numAlign = ver / 7 + 2;
|
||||
result -= (numAlign - 1) * (numAlign - 1) * 25; // Subtract alignment patterns not overlapping with timing patterns
|
||||
result -= (numAlign - 2) * 2 * 20; // Subtract alignment patterns that overlap with timing patterns
|
||||
// The two lines above are equivalent to: result -= (25 * numAlign - 10) * numAlign - 55;
|
||||
if (ver >= 7)
|
||||
result -= 6 * 3 * 2; // Subtract version information
|
||||
}
|
||||
assert 208 <= result && result <= 29648;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be
|
||||
// implemented as a lookup table over all possible parameter values, instead of as an algorithm.
|
||||
private static byte[] reedSolomonComputeDivisor(int degree) {
|
||||
if (degree < 1 || degree > 255)
|
||||
throw new IllegalArgumentException("Degree out of range");
|
||||
// Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1.
|
||||
// For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}.
|
||||
byte[] result = new byte[degree];
|
||||
result[degree - 1] = 1; // Start off with the monomial x^0
|
||||
|
||||
// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
|
||||
// and drop the highest monomial term which is always 1x^degree.
|
||||
// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
|
||||
int root = 1;
|
||||
for (int i = 0; i < degree; i++) {
|
||||
// Multiply the current product by (x - r^i)
|
||||
for (int j = 0; j < result.length; j++) {
|
||||
result[j] = (byte) reedSolomonMultiply(result[j] & 0xFF, root);
|
||||
if (j + 1 < result.length)
|
||||
result[j] ^= result[j + 1];
|
||||
}
|
||||
root = reedSolomonMultiply(root, 0x02);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials.
|
||||
private static byte[] reedSolomonComputeRemainder(byte[] data, byte[] divisor) {
|
||||
Objects.requireNonNull(data);
|
||||
Objects.requireNonNull(divisor);
|
||||
byte[] result = new byte[divisor.length];
|
||||
for (byte b : data) { // Polynomial division
|
||||
int factor = (b ^ result[0]) & 0xFF;
|
||||
System.arraycopy(result, 1, result, 0, result.length - 1);
|
||||
result[result.length - 1] = 0;
|
||||
for (int i = 0; i < result.length; i++)
|
||||
result[i] ^= reedSolomonMultiply(divisor[i] & 0xFF, factor);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result
|
||||
// are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8.
|
||||
private static int reedSolomonMultiply(int x, int y) {
|
||||
assert x >> 8 == 0 && y >> 8 == 0;
|
||||
// Russian peasant multiplication
|
||||
int z = 0;
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
z = (z << 1) ^ ((z >>> 7) * 0x11D);
|
||||
z ^= ((y >>> i) & 1) * x;
|
||||
}
|
||||
assert z >>> 8 == 0;
|
||||
return z;
|
||||
}
|
||||
|
||||
|
||||
// Returns the number of 8-bit data (i.e. not error correction) codewords contained in any
|
||||
// QR Code of the given version number and error correction level, with remainder bits discarded.
|
||||
// This stateless pure function could be implemented as a (40*4)-cell lookup table.
|
||||
static int getNumDataCodewords(int ver, Ecc ecl) {
|
||||
return getNumRawDataModules(ver) / 8
|
||||
- ECC_CODEWORDS_PER_BLOCK[ecl.ordinal()][ver]
|
||||
* NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal()][ver];
|
||||
}
|
||||
|
||||
|
||||
// Can only be called immediately after a light run is added, and
|
||||
// returns either 0, 1, or 2. A helper function for getPenaltyScore().
|
||||
private int finderPenaltyCountPatterns(int[] runHistory) {
|
||||
int n = runHistory[1];
|
||||
assert n <= size * 3;
|
||||
boolean core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n;
|
||||
return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0)
|
||||
+ (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0);
|
||||
}
|
||||
|
||||
|
||||
// Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore().
|
||||
private int finderPenaltyTerminateAndCount(boolean currentRunColor, int currentRunLength, int[] runHistory) {
|
||||
if (currentRunColor) { // Terminate dark run
|
||||
finderPenaltyAddHistory(currentRunLength, runHistory);
|
||||
currentRunLength = 0;
|
||||
}
|
||||
currentRunLength += size; // Add light border to final run
|
||||
finderPenaltyAddHistory(currentRunLength, runHistory);
|
||||
return finderPenaltyCountPatterns(runHistory);
|
||||
}
|
||||
|
||||
|
||||
// Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore().
|
||||
private void finderPenaltyAddHistory(int currentRunLength, int[] runHistory) {
|
||||
if (runHistory[0] == 0)
|
||||
currentRunLength += size; // Add light border to initial run
|
||||
System.arraycopy(runHistory, 0, runHistory, 1, runHistory.length - 1);
|
||||
runHistory[0] = currentRunLength;
|
||||
}
|
||||
|
||||
|
||||
// Returns true iff the i'th bit of x is set to 1.
|
||||
static boolean getBit(int x, int i) {
|
||||
return ((x >>> i) & 1) != 0;
|
||||
}
|
||||
|
||||
|
||||
/*---- Constants and tables ----*/
|
||||
|
||||
/**
|
||||
* The minimum version number (1) supported in the QR Code Model 2 standard.
|
||||
*/
|
||||
public static final int MIN_VERSION = 1;
|
||||
|
||||
/**
|
||||
* The maximum version number (40) supported in the QR Code Model 2 standard.
|
||||
*/
|
||||
public static final int MAX_VERSION = 40;
|
||||
|
||||
|
||||
// For use in getPenaltyScore(), when evaluating which mask is best.
|
||||
private static final int PENALTY_N1 = 3;
|
||||
private static final int PENALTY_N2 = 3;
|
||||
private static final int PENALTY_N3 = 40;
|
||||
private static final int PENALTY_N4 = 10;
|
||||
|
||||
|
||||
private static final byte[][] ECC_CODEWORDS_PER_BLOCK = {
|
||||
// Version: (note that index 0 is for padding, and is set to an illegal value)
|
||||
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
||||
{-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low
|
||||
{-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium
|
||||
{-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile
|
||||
{-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High
|
||||
};
|
||||
|
||||
private static final byte[][] NUM_ERROR_CORRECTION_BLOCKS = {
|
||||
// Version: (note that index 0 is for padding, and is set to an illegal value)
|
||||
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
||||
{-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low
|
||||
{-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium
|
||||
{-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile
|
||||
{-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*---- Public helper enumeration ----*/
|
||||
|
||||
/**
|
||||
* The error correction level in a QR Code symbol.
|
||||
*/
|
||||
public enum Ecc {
|
||||
// Must be declared in ascending order of error protection
|
||||
// so that the implicit ordinal() and values() work properly
|
||||
/**
|
||||
* The QR Code can tolerate about 7% erroneous codewords.
|
||||
*/
|
||||
LOW(1),
|
||||
/**
|
||||
* The QR Code can tolerate about 15% erroneous codewords.
|
||||
*/
|
||||
MEDIUM(0),
|
||||
/**
|
||||
* The QR Code can tolerate about 25% erroneous codewords.
|
||||
*/
|
||||
QUARTILE(3),
|
||||
/**
|
||||
* The QR Code can tolerate about 30% erroneous codewords.
|
||||
*/
|
||||
HIGH(2);
|
||||
|
||||
// In the range 0 to 3 (unsigned 2-bit integer).
|
||||
final int formatBits;
|
||||
|
||||
// Constructor.
|
||||
private Ecc(int fb) {
|
||||
formatBits = fb;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,330 @@
|
||||
/*
|
||||
* QR Code generator library (Java)
|
||||
*
|
||||
* Copyright (c) Project Nayuki. (MIT License)
|
||||
* https://www.nayuki.io/page/qr-code-generator-library
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
* - The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* - The Software is provided "as is", without warranty of any kind, express or
|
||||
* implied, including but not limited to the warranties of merchantability,
|
||||
* fitness for a particular purpose and noninfringement. In no event shall the
|
||||
* authors or copyright holders be liable for any claim, damages or other
|
||||
* liability, whether in an action of contract, tort or otherwise, arising from,
|
||||
* out of or in connection with the Software or the use or other dealings in the
|
||||
* Software.
|
||||
*/
|
||||
|
||||
package ru.ra66it.qrcodegenerator;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* A segment of character/binary/control data in a QR Code symbol.
|
||||
* Instances of this class are immutable.
|
||||
* <p>The mid-level way to create a segment is to take the payload data and call a
|
||||
* static factory function such as {@link QrSegment#makeNumeric(CharSequence)}. The low-level
|
||||
* way to create a segment is to custom-make the bit buffer and call the {@link
|
||||
* QrSegment#QrSegment(Mode, int, BitBuffer) constructor} with appropriate values.</p>
|
||||
* <p>This segment class imposes no length restrictions, but QR Codes have restrictions.
|
||||
* Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
|
||||
* Any segment longer than this is meaningless for the purpose of generating QR Codes.
|
||||
* This class can represent kanji mode segments, but provides no help in encoding them
|
||||
* - see {@link QrSegmentAdvanced} for full kanji support.</p>
|
||||
*/
|
||||
public final class QrSegment {
|
||||
|
||||
/*---- Static factory functions (mid level) ----*/
|
||||
|
||||
/**
|
||||
* Returns a segment representing the specified binary data
|
||||
* encoded in byte mode. All input byte arrays are acceptable.
|
||||
* <p>Any text string can be converted to UTF-8 bytes ({@code
|
||||
* s.getBytes(StandardCharsets.UTF_8)}) and encoded as a byte mode segment.</p>
|
||||
*
|
||||
* @param data the binary data (not {@code null})
|
||||
* @return a segment (not {@code null}) containing the data
|
||||
* @throws NullPointerException if the array is {@code null}
|
||||
*/
|
||||
public static QrSegment makeBytes(byte[] data) {
|
||||
Objects.requireNonNull(data);
|
||||
BitBuffer bb = new BitBuffer();
|
||||
for (byte b : data)
|
||||
bb.appendBits(b & 0xFF, 8);
|
||||
return new QrSegment(Mode.BYTE, data.length, bb);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a segment representing the specified string of decimal digits encoded in numeric mode.
|
||||
*
|
||||
* @param digits the text (not {@code null}), with only digits from 0 to 9 allowed
|
||||
* @return a segment (not {@code null}) containing the text
|
||||
* @throws NullPointerException if the string is {@code null}
|
||||
* @throws IllegalArgumentException if the string contains non-digit characters
|
||||
*/
|
||||
public static QrSegment makeNumeric(CharSequence digits) {
|
||||
Objects.requireNonNull(digits);
|
||||
if (!isNumeric(digits))
|
||||
throw new IllegalArgumentException("String contains non-numeric characters");
|
||||
|
||||
BitBuffer bb = new BitBuffer();
|
||||
for (int i = 0; i < digits.length(); ) { // Consume up to 3 digits per iteration
|
||||
int n = Math.min(digits.length() - i, 3);
|
||||
bb.appendBits(Integer.parseInt(digits.subSequence(i, i + n).toString()), n * 3 + 1);
|
||||
i += n;
|
||||
}
|
||||
return new QrSegment(Mode.NUMERIC, digits.length(), bb);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a segment representing the specified text string encoded in alphanumeric mode.
|
||||
* The characters allowed are: 0 to 9, A to Z (uppercase only), space,
|
||||
* dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
*
|
||||
* @param text the text (not {@code null}), with only certain characters allowed
|
||||
* @return a segment (not {@code null}) containing the text
|
||||
* @throws NullPointerException if the string is {@code null}
|
||||
* @throws IllegalArgumentException if the string contains non-encodable characters
|
||||
*/
|
||||
public static QrSegment makeAlphanumeric(CharSequence text) {
|
||||
Objects.requireNonNull(text);
|
||||
if (!isAlphanumeric(text))
|
||||
throw new IllegalArgumentException("String contains unencodable characters in alphanumeric mode");
|
||||
|
||||
BitBuffer bb = new BitBuffer();
|
||||
int i;
|
||||
for (i = 0; i <= text.length() - 2; i += 2) { // Process groups of 2
|
||||
int temp = ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45;
|
||||
temp += ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1));
|
||||
bb.appendBits(temp, 11);
|
||||
}
|
||||
if (i < text.length()) // 1 character remaining
|
||||
bb.appendBits(ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6);
|
||||
return new QrSegment(Mode.ALPHANUMERIC, text.length(), bb);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of zero or more segments to represent the specified Unicode text string.
|
||||
* The result may use various segment modes and switch modes to optimize the length of the bit stream.
|
||||
*
|
||||
* @param text the text to be encoded, which can be any Unicode string
|
||||
* @return a new mutable list (not {@code null}) of segments (not {@code null}) containing the text
|
||||
* @throws NullPointerException if the text is {@code null}
|
||||
*/
|
||||
public static List<QrSegment> makeSegments(CharSequence text) {
|
||||
Objects.requireNonNull(text);
|
||||
|
||||
// Select the most efficient segment encoding automatically
|
||||
List<QrSegment> result = new ArrayList<>();
|
||||
if (text.equals("")) ; // Leave result empty
|
||||
else if (isNumeric(text))
|
||||
result.add(makeNumeric(text));
|
||||
else if (isAlphanumeric(text))
|
||||
result.add(makeAlphanumeric(text));
|
||||
else
|
||||
result.add(makeBytes(text.toString().getBytes(StandardCharsets.UTF_8)));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a segment representing an Extended Channel Interpretation
|
||||
* (ECI) designator with the specified assignment value.
|
||||
*
|
||||
* @param assignVal the ECI assignment number (see the AIM ECI specification)
|
||||
* @return a segment (not {@code null}) containing the data
|
||||
* @throws IllegalArgumentException if the value is outside the range [0, 10<sup>6</sup>)
|
||||
*/
|
||||
public static QrSegment makeEci(int assignVal) {
|
||||
BitBuffer bb = new BitBuffer();
|
||||
if (assignVal < 0)
|
||||
throw new IllegalArgumentException("ECI assignment value out of range");
|
||||
else if (assignVal < (1 << 7))
|
||||
bb.appendBits(assignVal, 8);
|
||||
else if (assignVal < (1 << 14)) {
|
||||
bb.appendBits(0b10, 2);
|
||||
bb.appendBits(assignVal, 14);
|
||||
} else if (assignVal < 1_000_000) {
|
||||
bb.appendBits(0b110, 3);
|
||||
bb.appendBits(assignVal, 21);
|
||||
} else
|
||||
throw new IllegalArgumentException("ECI assignment value out of range");
|
||||
return new QrSegment(Mode.ECI, 0, bb);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether the specified string can be encoded as a segment in numeric mode.
|
||||
* A string is encodable iff each character is in the range 0 to 9.
|
||||
*
|
||||
* @param text the string to test for encodability (not {@code null})
|
||||
* @return {@code true} iff each character is in the range 0 to 9.
|
||||
* @throws NullPointerException if the string is {@code null}
|
||||
* @see #makeNumeric(CharSequence)
|
||||
*/
|
||||
public static boolean isNumeric(CharSequence text) {
|
||||
return NUMERIC_REGEX.matcher(text).matches();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether the specified string can be encoded as a segment in alphanumeric mode.
|
||||
* A string is encodable iff each character is in the following set: 0 to 9, A to Z
|
||||
* (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
*
|
||||
* @param text the string to test for encodability (not {@code null})
|
||||
* @return {@code true} iff each character is in the alphanumeric mode character set
|
||||
* @throws NullPointerException if the string is {@code null}
|
||||
* @see #makeAlphanumeric(CharSequence)
|
||||
*/
|
||||
public static boolean isAlphanumeric(CharSequence text) {
|
||||
return ALPHANUMERIC_REGEX.matcher(text).matches();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---- Instance fields ----*/
|
||||
|
||||
/**
|
||||
* The mode indicator of this segment. Not {@code null}.
|
||||
*/
|
||||
public final Mode mode;
|
||||
|
||||
/**
|
||||
* The length of this segment's unencoded data. Measured in characters for
|
||||
* numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
|
||||
* Always zero or positive. Not the same as the data's bit length.
|
||||
*/
|
||||
public final int numChars;
|
||||
|
||||
// The data bits of this segment. Not null. Accessed through getData().
|
||||
final BitBuffer data;
|
||||
|
||||
|
||||
/*---- Constructor (low level) ----*/
|
||||
|
||||
/**
|
||||
* Constructs a QR Code segment with the specified attributes and data.
|
||||
* The character count (numCh) must agree with the mode and the bit buffer length,
|
||||
* but the constraint isn't checked. The specified bit buffer is cloned and stored.
|
||||
*
|
||||
* @param md the mode (not {@code null})
|
||||
* @param numCh the data length in characters or bytes, which is non-negative
|
||||
* @param data the data bits (not {@code null})
|
||||
* @throws NullPointerException if the mode or data is {@code null}
|
||||
* @throws IllegalArgumentException if the character count is negative
|
||||
*/
|
||||
public QrSegment(Mode md, int numCh, BitBuffer data) {
|
||||
mode = Objects.requireNonNull(md);
|
||||
Objects.requireNonNull(data);
|
||||
if (numCh < 0)
|
||||
throw new IllegalArgumentException("Invalid value");
|
||||
numChars = numCh;
|
||||
this.data = data.clone(); // Make defensive copy
|
||||
}
|
||||
|
||||
|
||||
/*---- Methods ----*/
|
||||
|
||||
/**
|
||||
* Returns the data bits of this segment.
|
||||
*
|
||||
* @return a new copy of the data bits (not {@code null})
|
||||
*/
|
||||
public BitBuffer getData() {
|
||||
return data.clone(); // Make defensive copy
|
||||
}
|
||||
|
||||
|
||||
// Calculates the number of bits needed to encode the given segments at the given version.
|
||||
// Returns a non-negative number if successful. Otherwise returns -1 if a segment has too
|
||||
// many characters to fit its length field, or the total bits exceeds Integer.MAX_VALUE.
|
||||
static int getTotalBits(List<QrSegment> segs, int version) {
|
||||
Objects.requireNonNull(segs);
|
||||
long result = 0;
|
||||
for (QrSegment seg : segs) {
|
||||
Objects.requireNonNull(seg);
|
||||
int ccbits = seg.mode.numCharCountBits(version);
|
||||
if (seg.numChars >= (1 << ccbits))
|
||||
return -1; // The segment's length doesn't fit the field's bit width
|
||||
result += 4L + ccbits + seg.data.bitLength();
|
||||
if (result > Integer.MAX_VALUE)
|
||||
return -1; // The sum will overflow an int type
|
||||
}
|
||||
return (int) result;
|
||||
}
|
||||
|
||||
|
||||
/*---- Constants ----*/
|
||||
|
||||
// Describes precisely all strings that are encodable in numeric mode.
|
||||
private static final Pattern NUMERIC_REGEX = Pattern.compile("[0-9]*");
|
||||
|
||||
// Describes precisely all strings that are encodable in alphanumeric mode.
|
||||
private static final Pattern ALPHANUMERIC_REGEX = Pattern.compile("[A-Z0-9 $%*+./:-]*");
|
||||
|
||||
// The set of all legal characters in alphanumeric mode, where
|
||||
// each character value maps to the index in the string.
|
||||
static final String ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
|
||||
|
||||
|
||||
|
||||
/*---- Public helper enumeration ----*/
|
||||
|
||||
/**
|
||||
* Describes how a segment's data bits are interpreted.
|
||||
*/
|
||||
public enum Mode {
|
||||
|
||||
/*-- Constants --*/
|
||||
|
||||
NUMERIC(0x1, 10, 12, 14),
|
||||
ALPHANUMERIC(0x2, 9, 11, 13),
|
||||
BYTE(0x4, 8, 16, 16),
|
||||
KANJI(0x8, 8, 10, 12),
|
||||
ECI(0x7, 0, 0, 0);
|
||||
|
||||
|
||||
/*-- Fields --*/
|
||||
|
||||
// The mode indicator bits, which is a uint4 value (range 0 to 15).
|
||||
final int modeBits;
|
||||
|
||||
// Number of character count bits for three different version ranges.
|
||||
private final int[] numBitsCharCount;
|
||||
|
||||
|
||||
/*-- Constructor --*/
|
||||
|
||||
private Mode(int mode, int... ccbits) {
|
||||
modeBits = mode;
|
||||
numBitsCharCount = ccbits;
|
||||
}
|
||||
|
||||
|
||||
/*-- Method --*/
|
||||
|
||||
// Returns the bit width of the character count field for a segment in this mode
|
||||
// in a QR Code at the given version number. The result is in the range [0, 16].
|
||||
int numCharCountBits(int ver) {
|
||||
assert QrCode.MIN_VERSION <= ver && ver <= QrCode.MAX_VERSION;
|
||||
return numBitsCharCount[(ver + 7) / 17];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,460 @@
|
||||
/*
|
||||
* QR Code generator library - Optional advanced logic (Java)
|
||||
*
|
||||
* Copyright (c) Project Nayuki. (MIT License)
|
||||
* https://www.nayuki.io/page/qr-code-generator-library
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
* - The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* - The Software is provided "as is", without warranty of any kind, express or
|
||||
* implied, including but not limited to the warranties of merchantability,
|
||||
* fitness for a particular purpose and noninfringement. In no event shall the
|
||||
* authors or copyright holders be liable for any claim, damages or other
|
||||
* liability, whether in an action of contract, tort or otherwise, arising from,
|
||||
* out of or in connection with the Software or the use or other dealings in the
|
||||
* Software.
|
||||
*/
|
||||
|
||||
package ru.ra66it.qrcodegenerator;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import ru.ra66it.qrcodegenerator.QrSegment.Mode;
|
||||
|
||||
|
||||
/**
|
||||
* Splits text into optimal segments and encodes kanji segments.
|
||||
* Provides static functions only; not instantiable.
|
||||
*
|
||||
* @see QrSegment
|
||||
* @see QrCode
|
||||
*/
|
||||
public final class QrSegmentAdvanced {
|
||||
|
||||
/*---- Optimal list of segments encoder ----*/
|
||||
|
||||
/**
|
||||
* Returns a list of zero or more segments to represent the specified Unicode text string.
|
||||
* The resulting list optimally minimizes the total encoded bit length, subjected to the constraints
|
||||
* in the specified {error correction level, minimum version number, maximum version number}.
|
||||
* <p>This function can utilize all four text encoding modes: numeric, alphanumeric, byte (UTF-8),
|
||||
* and kanji. This can be considered as a sophisticated but slower replacement for {@link
|
||||
* QrSegment#makeSegments(CharSequence)}. This requires more input parameters because it searches a
|
||||
* range of versions, like {@link QrCode#encodeSegments(List, QrCode.Ecc, int, int, int, boolean)}.</p>
|
||||
*
|
||||
* @param text the text to be encoded (not {@code null}), which can be any Unicode string
|
||||
* @param ecl the error correction level to use (not {@code null})
|
||||
* @param minVersion the minimum allowed version of the QR Code (at least 1)
|
||||
* @param maxVersion the maximum allowed version of the QR Code (at most 40)
|
||||
* @return a new mutable list (not {@code null}) of segments (not {@code null})
|
||||
* containing the text, minimizing the bit length with respect to the constraints
|
||||
* @throws NullPointerException if the text or error correction level is {@code null}
|
||||
* @throws IllegalArgumentException if 1 ≤ minVersion ≤ maxVersion ≤ 40 is violated
|
||||
* @throws DataTooLongException if the text fails to fit in the maxVersion QR Code at the ECL
|
||||
*/
|
||||
public static List<QrSegment> makeSegmentsOptimally(String text, QrCode.Ecc ecl, int minVersion, int maxVersion) {
|
||||
// Check arguments
|
||||
Objects.requireNonNull(text);
|
||||
Objects.requireNonNull(ecl);
|
||||
if (!(QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= QrCode.MAX_VERSION))
|
||||
throw new IllegalArgumentException("Invalid value");
|
||||
|
||||
// Iterate through version numbers, and make tentative segments
|
||||
List<QrSegment> segs = null;
|
||||
int[] codePoints = toCodePoints(text);
|
||||
for (int version = minVersion; ; version++) {
|
||||
if (version == minVersion || version == 10 || version == 27)
|
||||
segs = makeSegmentsOptimally(codePoints, version);
|
||||
assert segs != null;
|
||||
|
||||
// Check if the segments fit
|
||||
int dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8; // Number of data bits available
|
||||
int dataUsedBits = QrSegment.getTotalBits(segs, version);
|
||||
if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits)
|
||||
return segs; // This version number is found to be suitable
|
||||
if (version >= maxVersion) { // All versions in the range could not fit the given text
|
||||
String msg = "Segment too long";
|
||||
if (dataUsedBits != -1)
|
||||
msg = String.format("Data length = %d bits, Max capacity = %d bits", dataUsedBits, dataCapacityBits);
|
||||
throw new DataTooLongException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Returns a new list of segments that is optimal for the given text at the given version number.
|
||||
private static List<QrSegment> makeSegmentsOptimally(int[] codePoints, int version) {
|
||||
if (codePoints.length == 0)
|
||||
return new ArrayList<>();
|
||||
Mode[] charModes = computeCharacterModes(codePoints, version);
|
||||
return splitIntoSegments(codePoints, charModes);
|
||||
}
|
||||
|
||||
|
||||
// Returns a new array representing the optimal mode per code point based on the given text and version.
|
||||
private static Mode[] computeCharacterModes(int[] codePoints, int version) {
|
||||
if (codePoints.length == 0)
|
||||
throw new IllegalArgumentException();
|
||||
if (codePoints.length > 7089) // Upper bound is the number of characters that fit in QR Code version 40, low error correction, numeric mode
|
||||
throw new DataTooLongException("String too long");
|
||||
final Mode[] modeTypes = {Mode.BYTE, Mode.ALPHANUMERIC, Mode.NUMERIC, Mode.KANJI}; // Do not modify
|
||||
final int numModes = modeTypes.length;
|
||||
|
||||
// Segment header sizes, measured in 1/6 bits
|
||||
final int[] headCosts = new int[numModes];
|
||||
for (int i = 0; i < numModes; i++) {
|
||||
headCosts[i] = (4 + modeTypes[i].numCharCountBits(version)) * 6;
|
||||
assert 0 <= headCosts[i] && headCosts[i] <= (4 + 16) * 6;
|
||||
}
|
||||
|
||||
// charModes[i][j] represents the mode to encode the code point at
|
||||
// index i such that the final segment ends in modeTypes[j] and the
|
||||
// total number of bits is minimized over all possible choices
|
||||
Mode[][] charModes = new Mode[codePoints.length][numModes];
|
||||
|
||||
// At the beginning of each iteration of the loop below,
|
||||
// prevCosts[j] is the exact minimum number of 1/6 bits needed to
|
||||
// encode the entire string prefix of length i, and end in modeTypes[j]
|
||||
int[] prevCosts = headCosts.clone();
|
||||
|
||||
// Calculate costs using dynamic programming
|
||||
for (int i = 0; i < codePoints.length; i++) {
|
||||
int c = codePoints[i];
|
||||
int[] curCosts = new int[numModes];
|
||||
{ // Always extend a byte mode segment
|
||||
curCosts[0] = prevCosts[0] + countUtf8Bytes(c) * 8 * 6;
|
||||
charModes[i][0] = modeTypes[0];
|
||||
}
|
||||
// Extend a segment if possible
|
||||
if (QrSegment.ALPHANUMERIC_CHARSET.indexOf(c) != -1) { // Is alphanumeric
|
||||
curCosts[1] = prevCosts[1] + 33; // 5.5 bits per alphanumeric char
|
||||
charModes[i][1] = modeTypes[1];
|
||||
}
|
||||
if ('0' <= c && c <= '9') { // Is numeric
|
||||
curCosts[2] = prevCosts[2] + 20; // 3.33 bits per digit
|
||||
charModes[i][2] = modeTypes[2];
|
||||
}
|
||||
if (isKanji(c)) {
|
||||
curCosts[3] = prevCosts[3] + 78; // 13 bits per Shift JIS char
|
||||
charModes[i][3] = modeTypes[3];
|
||||
}
|
||||
|
||||
// Start new segment at the end to switch modes
|
||||
for (int j = 0; j < numModes; j++) { // To mode
|
||||
for (int k = 0; k < numModes; k++) { // From mode
|
||||
int newCost = (curCosts[k] + 5) / 6 * 6 + headCosts[j];
|
||||
if (charModes[i][k] != null && (charModes[i][j] == null || newCost < curCosts[j])) {
|
||||
curCosts[j] = newCost;
|
||||
charModes[i][j] = modeTypes[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A non-tight upper bound is when each of 7089 characters switches to
|
||||
// byte mode (4-bit header + 16-bit count) and requires 4 bytes in UTF-8
|
||||
for (int cost : curCosts)
|
||||
assert 0 <= cost && cost <= (4 + 16 + 32) * 6 * 7089;
|
||||
prevCosts = curCosts;
|
||||
}
|
||||
|
||||
// Find optimal ending mode
|
||||
Mode curMode = null;
|
||||
for (int i = 0, minCost = 0; i < numModes; i++) {
|
||||
if (curMode == null || prevCosts[i] < minCost) {
|
||||
minCost = prevCosts[i];
|
||||
curMode = modeTypes[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Get optimal mode for each code point by tracing backwards
|
||||
Mode[] result = new Mode[charModes.length];
|
||||
for (int i = result.length - 1; i >= 0; i--) {
|
||||
for (int j = 0; j < numModes; j++) {
|
||||
if (modeTypes[j] == curMode) {
|
||||
curMode = charModes[i][j];
|
||||
result[i] = curMode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Returns a new list of segments based on the given text and modes, such that
|
||||
// consecutive code points in the same mode are put into the same segment.
|
||||
private static List<QrSegment> splitIntoSegments(int[] codePoints, Mode[] charModes) {
|
||||
if (codePoints.length == 0)
|
||||
throw new IllegalArgumentException();
|
||||
List<QrSegment> result = new ArrayList<>();
|
||||
|
||||
// Accumulate run of modes
|
||||
Mode curMode = charModes[0];
|
||||
int start = 0;
|
||||
for (int i = 1; ; i++) {
|
||||
if (i < codePoints.length && charModes[i] == curMode)
|
||||
continue;
|
||||
String s = new String(codePoints, start, i - start);
|
||||
if (curMode == Mode.BYTE)
|
||||
result.add(QrSegment.makeBytes(s.getBytes(StandardCharsets.UTF_8)));
|
||||
else if (curMode == Mode.NUMERIC)
|
||||
result.add(QrSegment.makeNumeric(s));
|
||||
else if (curMode == Mode.ALPHANUMERIC)
|
||||
result.add(QrSegment.makeAlphanumeric(s));
|
||||
else if (curMode == Mode.KANJI)
|
||||
result.add(makeKanji(s));
|
||||
else
|
||||
throw new AssertionError();
|
||||
if (i >= codePoints.length)
|
||||
return result;
|
||||
curMode = charModes[i];
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Returns a new array of Unicode code points (effectively
|
||||
// UTF-32 / UCS-4) representing the given UTF-16 string.
|
||||
private static int[] toCodePoints(String s) {
|
||||
int[] result = new int[s.length()];
|
||||
|
||||
final int length = s.length();
|
||||
for (int offset = 0; offset < length; ) {
|
||||
final int codepoint = s.codePointAt(offset);
|
||||
|
||||
// do something with the codepoint
|
||||
|
||||
offset += Character.charCount(codepoint);
|
||||
}
|
||||
for (int c : result) {
|
||||
if (Character.isSurrogate((char) c))
|
||||
throw new IllegalArgumentException("Invalid UTF-16 string");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Returns the number of UTF-8 bytes needed to encode the given Unicode code point.
|
||||
private static int countUtf8Bytes(int cp) {
|
||||
if (cp < 0) throw new IllegalArgumentException("Invalid code point");
|
||||
else if (cp < 0x80) return 1;
|
||||
else if (cp < 0x800) return 2;
|
||||
else if (cp < 0x10000) return 3;
|
||||
else if (cp < 0x110000) return 4;
|
||||
else throw new IllegalArgumentException("Invalid code point");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---- Kanji mode segment encoder ----*/
|
||||
|
||||
/**
|
||||
* Returns a segment representing the specified text string encoded in kanji mode.
|
||||
* Broadly speaking, the set of encodable characters are {kanji used in Japan,
|
||||
* hiragana, katakana, East Asian punctuation, full-width ASCII, Greek, Cyrillic}.
|
||||
* Examples of non-encodable characters include {ordinary ASCII, half-width katakana,
|
||||
* more extensive Chinese hanzi}.
|
||||
*
|
||||
* @param text the text (not {@code null}), with only certain characters allowed
|
||||
* @return a segment (not {@code null}) containing the text
|
||||
* @throws NullPointerException if the string is {@code null}
|
||||
* @throws IllegalArgumentException if the string contains non-encodable characters
|
||||
* @see #isEncodableAsKanji(String)
|
||||
*/
|
||||
public static QrSegment makeKanji(String text) {
|
||||
Objects.requireNonNull(text);
|
||||
BitBuffer bb = new BitBuffer();
|
||||
|
||||
char[] chars = text.toString().toCharArray();
|
||||
|
||||
for (char c : chars) {
|
||||
int val = UNICODE_TO_QR_KANJI[c];
|
||||
if (val == -1)
|
||||
throw new IllegalArgumentException("String contains non-kanji-mode characters");
|
||||
bb.appendBits(val, 13);
|
||||
}
|
||||
|
||||
return new QrSegment(Mode.KANJI, text.length(), bb);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether the specified string can be encoded as a segment in kanji mode.
|
||||
* Broadly speaking, the set of encodable characters are {kanji used in Japan,
|
||||
* hiragana, katakana, East Asian punctuation, full-width ASCII, Greek, Cyrillic}.
|
||||
* Examples of non-encodable characters include {ordinary ASCII, half-width katakana,
|
||||
* more extensive Chinese hanzi}.
|
||||
*
|
||||
* @param text the string to test for encodability (not {@code null})
|
||||
* @return {@code true} iff each character is in the kanji mode character set
|
||||
* @throws NullPointerException if the string is {@code null}
|
||||
* @see #makeKanji(String)
|
||||
*/
|
||||
public static boolean isEncodableAsKanji(String text) {
|
||||
Objects.requireNonNull(text);
|
||||
|
||||
boolean isEncodable = true;
|
||||
for (char c : text.toCharArray()) {
|
||||
if (!isKanji(c)) {
|
||||
isEncodable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return isEncodable;
|
||||
}
|
||||
|
||||
|
||||
private static boolean isKanji(int c) {
|
||||
return c < UNICODE_TO_QR_KANJI.length && UNICODE_TO_QR_KANJI[c] != -1;
|
||||
}
|
||||
|
||||
|
||||
// Data derived from ftp://ftp.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/SHIFTJIS.TXT
|
||||
private static final String PACKED_QR_KANJI_TO_UNICODE =
|
||||
"MAAwATAC/wz/DjD7/xr/G/8f/wEwmzCcALT/QACo/z7/4/8/MP0w/jCdMJ4wA07dMAUwBjAHMPwgFSAQ/w8AXDAcIBb/XCAmICUgGCAZIBwgHf8I/wkwFDAV/zv/Pf9b/10wCDAJMAowCzAMMA0wDjAPMBAwEf8LIhIAsQDX//8A9/8dImD/HP8eImYiZyIeIjQmQiZA" +
|
||||
"ALAgMiAzIQP/5f8EAKIAo/8F/wP/Bv8K/yAApyYGJgUlyyXPJc4lxyXGJaEloCWzJbIlvSW8IDswEiGSIZAhkSGTMBP/////////////////////////////IggiCyKGIocigiKDIioiKf////////////////////8iJyIoAKwh0iHUIgAiA///////////////////" +
|
||||
"//////////8iICKlIxIiAiIHImEiUiJqImsiGiI9Ih0iNSIrIiz//////////////////yErIDAmbyZtJmogICAhALb//////////yXv/////////////////////////////////////////////////xD/Ef8S/xP/FP8V/xb/F/8Y/xn///////////////////8h" +
|
||||
"/yL/I/8k/yX/Jv8n/yj/Kf8q/yv/LP8t/y7/L/8w/zH/Mv8z/zT/Nf82/zf/OP85/zr///////////////////9B/0L/Q/9E/0X/Rv9H/0j/Sf9K/0v/TP9N/07/T/9Q/1H/Uv9T/1T/Vf9W/1f/WP9Z/1r//////////zBBMEIwQzBEMEUwRjBHMEgwSTBKMEswTDBN" +
|
||||
"ME4wTzBQMFEwUjBTMFQwVTBWMFcwWDBZMFowWzBcMF0wXjBfMGAwYTBiMGMwZDBlMGYwZzBoMGkwajBrMGwwbTBuMG8wcDBxMHIwczB0MHUwdjB3MHgweTB6MHswfDB9MH4wfzCAMIEwgjCDMIQwhTCGMIcwiDCJMIowizCMMI0wjjCPMJAwkTCSMJP/////////////" +
|
||||
"////////////////////////MKEwojCjMKQwpTCmMKcwqDCpMKowqzCsMK0wrjCvMLAwsTCyMLMwtDC1MLYwtzC4MLkwujC7MLwwvTC+ML8wwDDBMMIwwzDEMMUwxjDHMMgwyTDKMMswzDDNMM4wzzDQMNEw0jDTMNQw1TDWMNcw2DDZMNow2zDcMN0w3jDf//8w4DDh" +
|
||||
"MOIw4zDkMOUw5jDnMOgw6TDqMOsw7DDtMO4w7zDwMPEw8jDzMPQw9TD2/////////////////////wORA5IDkwOUA5UDlgOXA5gDmQOaA5sDnAOdA54DnwOgA6EDowOkA6UDpgOnA6gDqf////////////////////8DsQOyA7MDtAO1A7YDtwO4A7kDugO7A7wDvQO+" +
|
||||
"A78DwAPBA8MDxAPFA8YDxwPIA8n/////////////////////////////////////////////////////////////////////////////////////////////////////////////BBAEEQQSBBMEFAQVBAEEFgQXBBgEGQQaBBsEHAQdBB4EHwQgBCEEIgQjBCQEJQQm" +
|
||||
"BCcEKAQpBCoEKwQsBC0ELgQv////////////////////////////////////////BDAEMQQyBDMENAQ1BFEENgQ3BDgEOQQ6BDsEPAQ9//8EPgQ/BEAEQQRCBEMERARFBEYERwRIBEkESgRLBEwETQROBE///////////////////////////////////yUAJQIlDCUQ" +
|
||||
"JRglFCUcJSwlJCU0JTwlASUDJQ8lEyUbJRclIyUzJSslOyVLJSAlLyUoJTclPyUdJTAlJSU4JUL/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
|
||||
"////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
|
||||
"////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
|
||||
"////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
|
||||
"////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
|
||||
"////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
|
||||
"////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
|
||||
"////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
|
||||
"////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
|
||||
"////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
|
||||
"/////////////////////////////////////06cVRZaA5Y/VMBhG2MoWfaQIoR1gxx6UGCqY+FuJWXthGaCppv1aJNXJ2WhYnFbm1nQhnuY9H1ifb6bjmIWfJ+It1uJXrVjCWaXaEiVx5eNZ09O5U8KT01PnVBJVvJZN1nUWgFcCWDfYQ9hcGYTaQVwunVPdXB5+32t" +
|
||||
"fe+Aw4QOiGOLApBVkHpTO06VTqVX34CykMF4704AWPFuopA4ejKDKIKLnC9RQVNwVL1U4VbgWftfFZjybeuA5IUt////////lmKWcJagl/tUC1PzW4dwz3+9j8KW6FNvnVx6uk4ReJOB/G4mVhhVBGsdhRqcO1nlU6ltZnTclY9WQk6RkEuW8oNPmQxT4VW2WzBfcWYg" +
|
||||
"ZvNoBGw4bPNtKXRbdsh6Tpg0gvGIW4pgku1tsnWrdsqZxWCmiwGNipWyaY5TrVGG//9XElgwWURbtF72YChjqWP0bL9vFHCOcRRxWXHVcz9+AYJ2gtGFl5BgkludG1hpZbxsWnUlUflZLlllX4Bf3GK8ZfpqKmsna7Rzi3/BiVadLJ0OnsRcoWyWg3tRBFxLYbaBxmh2" +
|
||||
"cmFOWU/6U3hgaW4pek+X804LUxZO7k9VTz1PoU9zUqBT71YJWQ9awVu2W+F50WaHZ5xntmtMbLNwa3PCeY15vno8e4eCsYLbgwSDd4Pvg9OHZoqyVimMqI/mkE6XHoaKT8Rc6GIRcll1O4Hlgr2G/ozAlsWZE5nVTstPGonjVt5YSljKXvtf62AqYJRgYmHQYhJi0GU5" +
|
||||
"////////m0FmZmiwbXdwcHVMdoZ9dYKlh/mVi5aOjJ1R8VK+WRZUs1uzXRZhaGmCba94jYTLiFeKcpOnmrhtbJmohtlXo2f/hs6SDlKDVodUBF7TYuFkuWg8aDhru3NyeLp6a4maidKNa48DkO2Vo5aUl2lbZlyzaX2YTZhOY5t7IGor//9qf2i2nA1vX1JyVZ1gcGLs" +
|
||||
"bTtuB27RhFuJEI9EThScOVP2aRtqOpeEaCpRXHrDhLKR3JOMVludKGgigwWEMXylUgiCxXTmTn5Pg1GgW9JSClLYUudd+1WaWCpZ5luMW5hb215yXnlgo2EfYWNhvmPbZWJn0WhTaPprPmtTbFdvIm+Xb0V0sHUYduN3C3r/e6F8IX3pfzZ/8ICdgmaDnomzisyMq5CE" +
|
||||
"lFGVk5WRlaKWZZfTmSiCGE44VCtcuF3Mc6l2THc8XKl/640LlsGYEZhUmFhPAU8OU3FVnFZoV/pZR1sJW8RckF4MXn5fzGPuZzpl12XiZx9oy2jE////////al9eMGvFbBdsfXV/eUhbY3oAfQBfvYmPihiMtI13jsyPHZjimg6bPE6AUH1RAFmTW5xiL2KAZOxrOnKg" +
|
||||
"dZF5R3+ph/uKvItwY6yDypegVAlUA1WraFRqWIpweCdndZ7NU3RbooEahlCQBk4YTkVOx08RU8pUOFuuXxNgJWVR//9nPWxCbHJs43B4dAN6dnquewh9Gnz+fWZl53JbU7tcRV3oYtJi4GMZbiCGWooxjd2S+G8BeaabWk6oTqtOrE+bT6BQ0VFHevZRcVH2U1RTIVN/" +
|
||||
"U+tVrFiDXOFfN19KYC9gUGBtYx9lWWpLbMFywnLtd++A+IEFggiFTpD3k+GX/5lXmlpO8FHdXC1mgWltXEBm8ml1c4loUHyBUMVS5FdHXf6TJmWkayNrPXQ0eYF5vXtLfcqCuYPMiH+JX4s5j9GR0VQfkoBOXVA2U+VTOnLXc5Z36YLmjq+ZxpnImdJRd2Eahl5VsHp6" +
|
||||
"UHZb05BHloVOMmrbkedcUVxI////////Y5h6n2yTl3SPYXqqcYqWiHyCaBd+cGhRk2xS8lQbhauKE3+kjs2Q4VNmiIh5QU/CUL5SEVFEVVNXLXPqV4tZUV9iX4RgdWF2YWdhqWOyZDplbGZvaEJuE3Vmej18+31MfZl+S39rgw6DSobNigiKY4tmjv2YGp2PgriPzpvo" +
|
||||
"//9Sh2IfZINvwJaZaEFQkWsgbHpvVHp0fVCIQIojZwhO9lA5UCZQZVF8UjhSY1WnVw9YBVrMXvphsmH4YvNjcmkcailyfXKscy54FHhvfXl3DICpiYuLGYzijtKQY5N1lnqYVZoTnnhRQ1OfU7Nee18mbhtukHOEc/59Q4I3igCK+pZQTk5QC1PkVHxW+lnRW2Rd8V6r" +
|
||||
"XydiOGVFZ69uVnLQfMqItIChgOGD8IZOioeN6JI3lseYZ58TTpROkk8NU0hUSVQ+Wi9fjF+hYJ9op2qOdFp4gYqeiqSLd5GQTl6byU6kT3xPr1AZUBZRSVFsUp9SuVL+U5pT41QR////////VA5ViVdRV6JZfVtUW11bj13lXedd9154XoNeml63XxhgUmFMYpdi2GOn" +
|
||||
"ZTtmAmZDZvRnbWghaJdpy2xfbSptaW4vbp11MnaHeGx6P3zgfQV9GH1efbGAFYADgK+AsYFUgY+CKoNSiEyIYYsbjKKM/JDKkXWScXg/kvyVpJZN//+YBZmZmtidO1JbUqtT91QIWNVi92/gjGqPX565UUtSO1RKVv16QJF3nWCe0nNEbwmBcHURX/1g2pqoctuPvGtk" +
|
||||
"mANOylbwV2RYvlpaYGhhx2YPZgZoOWixbfd11X06gm6bQk6bT1BTyVUGXW9d5l3uZ/tsmXRzeAKKUJOWiN9XUF6nYytQtVCsUY1nAFTJWF5Zu1uwX2liTWOhaD1rc24IcH2Rx3KAeBV4JnltZY59MIPciMGPCZabUmRXKGdQf2qMoVG0V0KWKlg6aYqAtFSyXQ5X/HiV" +
|
||||
"nfpPXFJKVItkPmYoZxRn9XqEe1Z9IpMvaFybrXs5UxlRilI3////////W99i9mSuZOZnLWu6hamW0XaQm9ZjTJMGm6t2v2ZSTglQmFPCXHFg6GSSZWNoX3Hmc8p1I3uXfoKGlYuDjNuReJkQZaxmq2uLTtVO1E86T39SOlP4U/JV41bbWOtZy1nJWf9bUFxNXgJeK1/X" +
|
||||
"YB1jB2UvW1xlr2W9ZehnnWti//9re2wPc0V5SXnBfPh9GX0rgKKBAoHziZaKXoppimaKjIrujMeM3JbMmPxrb06LTzxPjVFQW1db+mFIYwFmQmshbstsu3I+dL111HjBeTqADIAzgeqElI+ebFCef18Pi1idK3r6jvhbjZbrTgNT8Vf3WTFayVukYIluf28Gdb6M6luf" +
|
||||
"hQB74FByZ/SCnVxhhUp+HoIOUZlcBGNojWZlnHFueT59F4AFix2OypBuhseQqlAfUvpcOmdTcHxyNZFMkciTK4LlW8JfMWD5TjtT1luIYktnMWuKculz4HougWuNo5FSmZZRElPXVGpb/2OIajl9rJcAVtpTzlRo////////W5dcMV3eT+5hAWL+bTJ5wHnLfUJ+TX/S" +
|
||||
"ge2CH4SQiEaJcouQjnSPL5AxkUuRbJbGkZxOwE9PUUVTQV+TYg5n1GxBbgtzY34mkc2Sg1PUWRlbv23ReV1+LnybWH5xn1H6iFOP8E/KXPtmJXeseuOCHJn/UcZfqmXsaW9riW3z//9ulm9kdv59FF3hkHWRh5gGUeZSHWJAZpFm2W4aXrZ90n9yZviFr4X3ivhSqVPZ" +
|
||||
"WXNej1+QYFWS5JZkULdRH1LdUyBTR1PsVOhVRlUxVhdZaFm+WjxbtVwGXA9cEVwaXoReil7gX3Bif2KEYttjjGN3ZgdmDGYtZnZnfmiiah9qNWy8bYhuCW5YcTxxJnFndcd3AXhdeQF5ZXnweuB7EXynfTmAloPWhIuFSYhdiPOKH4o8ilSKc4xhjN6RpJJmk36UGJac" +
|
||||
"l5hOCk4ITh5OV1GXUnBXzlg0WMxbIl44YMVk/mdhZ1ZtRHK2dXN6Y4S4i3KRuJMgVjFX9Jj+////////Yu1pDWuWce1+VIB3gnKJ5pjfh1WPsVw7TzhP4U+1VQdaIFvdW+lfw2FOYy9lsGZLaO5pm214bfF1M3W5dx95XnnmfTOB44KvhaqJqoo6jquPm5Aykd2XB066" +
|
||||
"TsFSA1h1WOxcC3UaXD2BTooKj8WWY5dteyWKz5gIkWJW81Oo//+QF1Q5V4JeJWOobDRwindhfIt/4IhwkEKRVJMQkxiWj3RemsRdB11pZXBnoo2olttjbmdJaRmDxZgXlsCI/m+EZHpb+E4WcCx1XWYvUcRSNlLiWdNfgWAnYhBlP2V0Zh9mdGjyaBZrY24FcnJ1H3bb" +
|
||||
"fL6AVljwiP2Jf4qgipOKy5AdkZKXUpdZZYl6DoEGlrteLWDcYhplpWYUZ5B383pNfE1+PoEKjKyNZI3hjl94qVIHYtljpWRCYpiKLXqDe8CKrJbqfXaCDIdJTtlRSFNDU2Bbo1wCXBZd3WImYkdksGgTaDRsyW1FbRdn029ccU5xfWXLen97rX3a////////fkp/qIF6" +
|
||||
"ghuCOYWmim6Mzo31kHiQd5KtkpGVg5uuUk1VhG84cTZRaHmFflWBs3zOVkxYUVyoY6pm/mb9aVpy2XWPdY55DnlWed98l30gfUSGB4o0ljuQYZ8gUOdSdVPMU+JQCVWqWO5ZT3I9W4tcZFMdYONg82NcY4NjP2O7//9kzWXpZvld42nNaf1vFXHlTol16Xb4epN8333P" +
|
||||
"fZyAYYNJg1iEbIS8hfuIxY1wkAGQbZOXlxyaElDPWJdhjoHThTWNCJAgT8NQdFJHU3Ngb2NJZ19uLI2zkB9P11xejMplz32aU1KIllF2Y8NbWFtrXApkDWdRkFxO1lkaWSpscIpRVT5YFVmlYPBiU2fBgjVpVZZAmcSaKE9TWAZb/oAQXLFeL1+FYCBhS2I0Zv9s8G7e" +
|
||||
"gM6Bf4LUiIuMuJAAkC6Wip7bm9tO41PwWSd7LJGNmEyd+W7dcCdTU1VEW4ViWGKeYtNsom/vdCKKF5Q4b8GK/oM4UeeG+FPq////////U+lPRpBUj7BZaoExXf166o+/aNqMN3L4nEhqPYqwTjlTWFYGV2ZixWOiZeZrTm3hbltwrXfteu97qn27gD2AxobLipWTW1bj" +
|
||||
"WMdfPmWtZpZqgGu1dTeKx1Akd+VXMF8bYGVmemxgdfR6Gn9ugfSHGJBFmbN7yXVcevl7UYTE//+QEHnpepKDNlrhd0BOLU7yW5lf4GK9Zjxn8WzohmuId4o7kU6S85nQahdwJnMqgueEV4yvTgFRRlHLVYtb9V4WXjNegV8UXzVfa1+0YfJjEWaiZx1vbnJSdTp3OoB0" +
|
||||
"gTmBeId2ir+K3I2FjfOSmpV3mAKc5VLFY1d29GcVbIhzzYzDk66Wc20lWJxpDmnMj/2TmnXbkBpYWmgCY7Rp+09Dbyxn2I+7hSZ9tJNUaT9vcFdqWPdbLH0scipUCpHjnbROrU9OUFxQdVJDjJ5USFgkW5peHV6VXq1e918fYIxitWM6Y9Bor2xAeId5jnoLfeCCR4oC" +
|
||||
"iuaORJAT////////kLiRLZHYnw5s5WRYZOJldW70doR7G5Bpk9FuulTyX7lkpI9Nj+2SRFF4WGtZKVxVXpdt+36PdRyMvI7imFtwuU8da79vsXUwlvtRTlQQWDVYV1msXGBfkmWXZ1xuIXZ7g9+M7ZAUkP2TTXgleDpSql6mVx9ZdGASUBJRWlGs//9RzVIAVRBYVFhY" +
|
||||
"WVdblVz2XYtgvGKVZC1ncWhDaLxo33bXbdhub22bcG9xyF9Tddh5d3tJe1R7UnzWfXFSMIRjhWmF5IoOiwSMRo4PkAOQD5QZlnaYLZowldhQzVLVVAxYAlwOYadknm0ed7N65YD0hASQU5KFXOCdB1M/X5dfs22ccnl3Y3m/e+Rr0nLsiq1oA2phUfh6gWk0XEqc9oLr" +
|
||||
"W8WRSXAeVnhcb2DHZWZsjIxakEGYE1RRZseSDVlIkKNRhU5NUeqFmYsOcFhjepNLaWKZtH4EdXdTV2lgjt+W42xdToxcPF8Qj+lTAozRgImGeV7/ZeVOc1Fl////////WYJcP5fuTvtZil/Nio1v4XmweWJb54RxcytxsV50X/Vje2SaccN8mE5DXvxOS1fcVqJgqW/D" +
|
||||
"fQ2A/YEzgb+PsomXhqRd9GKKZK2Jh2d3bOJtPnQ2eDRaRn91gq2ZrE/zXsNi3WOSZVdnb3bDckyAzIC6jymRTVANV/lakmiF//9pc3Fkcv2Mt1jyjOCWapAZh3955HfnhClPL1JlU1pizWfPbMp2fXuUfJWCNoWEj+tm3W8gcgZ+G4OrmcGeplH9e7F4cnu4gId7SGro" +
|
||||
"XmGAjHVRdWBRa5Jibox2epGXmupPEH9wYpx7T5WlnOlWelhZhuSWvE80UiRTSlPNU9teBmQsZZFnf2w+bE5ySHKvc+11VH5BgiyF6Yype8SRxnFpmBKY72M9Zml1anbkeNCFQ4buUypTUVQmWYNeh198YLJiSWJ5YqtlkGvUbMx1snaueJF52H3Lf3eApYirirmMu5B/" +
|
||||
"l16Y22oLfDhQmVw+X65nh2vYdDV3CX+O////////nztnynoXUzl1i5rtX2aBnYPxgJhfPF/FdWJ7RpA8aGdZ61qbfRB2fossT/VfamoZbDdvAnTieWiIaIpVjHle32PPdcV50oLXkyiS8oSchu2cLVTBX2xljG1ccBWMp4zTmDtlT3T2Tg1O2FfgWStaZlvMUaheA16c" +
|
||||
"YBZidmV3//9lp2ZubW5yNnsmgVCBmoKZi1yMoIzmjXSWHJZET65kq2tmgh6EYYVqkOhcAWlTmKiEeoVXTw9Sb1+pXkVnDXmPgXmJB4mGbfVfF2JVbLhOz3Jpm5JSBlQ7VnRYs2GkYm5xGllufIl83n0blvBlh4BeThlPdVF1WEBeY15zXwpnxE4mhT2ViZZbfHOYAVD7" +
|
||||
"WMF2VninUiV3pYURe4ZQT1kJckd7x33oj7qP1JBNT79SyVopXwGXrU/dgheS6lcDY1VraXUriNyPFHpCUt9Yk2FVYgpmrmvNfD+D6VAjT/hTBVRGWDFZSVudXPBc710pXpZisWNnZT5luWcL////////bNVs4XD5eDJ+K4DegrOEDITshwKJEooqjEqQppLSmP2c851s" +
|
||||
"Tk9OoVCNUlZXSlmoXj1f2F/ZYj9mtGcbZ9Bo0lGSfSGAqoGoiwCMjIy/kn6WMlQgmCxTF1DVU1xYqGSyZzRyZ3dmekaR5lLDbKFrhlgAXkxZVGcsf/tR4XbG//9kaXjom1Seu1fLWblmJ2eaa85U6WnZXlWBnGeVm6pn/pxSaF1Opk/jU8hiuWcrbKuPxE+tfm2ev04H" +
|
||||
"YWJugG8rhRNUc2cqm0Vd83uVXKxbxoccbkqE0XoUgQhZmXyNbBF3IFLZWSJxIXJfd9uXJ51haQtaf1oYUaVUDVR9Zg5234/3kpic9Fnqcl1uxVFNaMl9v33sl2KeumR4aiGDAlmEW19r23MbdvJ9soAXhJlRMmcontl27mdiUv+ZBVwkYjt8foywVU9gtn0LlYBTAU5f" +
|
||||
"UbZZHHI6gDaRzl8ld+JThF95fQSFrIozjo2XVmfzha6UU2EJYQhsuXZS////////iu2POFUvT1FRKlLHU8tbpV59YKBhgmPWZwln2m5nbYxzNnM3dTF5UIjVipiQSpCRkPWWxIeNWRVOiE9ZTg6KiY8/mBBQrV58WZZbuV64Y9pj+mTBZtxpSmnYbQtutnGUdSh6r3+K" +
|
||||
"gACESYTJiYGLIY4KkGWWfZkKYX5ikWsy//9sg210f8x//G3Af4WHuoj4Z2WDsZg8lvdtG31hhD2Rak5xU3VdUGsEb+uFzYYtiadSKVQPXGVnTmiodAZ0g3XiiM+I4ZHMluKWeF+Lc4d6y4ROY6B1ZVKJbUFunHQJdVl4a3ySloZ63J+NT7ZhbmXFhlxOhk6uUNpOIVHM" +
|
||||
"W+5lmWiBbbxzH3ZCd616HHzngm+K0pB8kc+WdZgYUpt90VArU5hnl23LcdB0M4HojyqWo5xXnp90YFhBbZl9L5heTuRPNk+LUbdSsV26YBxzsnk8gtOSNJa3lvaXCp6Xn2Jmpmt0UhdSo3DIiMJeyWBLYZBvI3FJfD599IBv////////hO6QI5MsVEKbb2rTcImMwo3v" +
|
||||
"lzJStFpBXspfBGcXaXxplG1qbw9yYnL8e+2AAYB+h0uQzlFtnpN5hICLkzKK1lAtVIyKcWtqjMSBB2DRZ6Cd8k6ZTpicEIprhcGFaGkAbn54l4FV////////////////////////////////////////////////////////////////////////////////////////" +
|
||||
"/////////////////////////////18MThBOFU4qTjFONk48Tj9OQk5WTlhOgk6FjGtOioISXw1Ojk6eTp9OoE6iTrBOs062Ts5OzU7ETsZOwk7XTt5O7U7fTvdPCU9aTzBPW09dT1dPR092T4hPj0+YT3tPaU9wT5FPb0+GT5ZRGE/UT99Pzk/YT9tP0U/aT9BP5E/l" +
|
||||
"UBpQKFAUUCpQJVAFTxxP9lAhUClQLE/+T+9QEVAGUENQR2cDUFVQUFBIUFpQVlBsUHhQgFCaUIVQtFCy////////UMlQylCzUMJQ1lDeUOVQ7VDjUO5Q+VD1UQlRAVECURZRFVEUURpRIVE6UTdRPFE7UT9RQFFSUUxRVFFievhRaVFqUW5RgFGCVthRjFGJUY9RkVGT" +
|
||||
"UZVRllGkUaZRolGpUapRq1GzUbFRslGwUbVRvVHFUclR21HghlVR6VHt//9R8FH1Uf5SBFILUhRSDlInUipSLlIzUjlST1JEUktSTFJeUlRSalJ0UmlSc1J/Un1SjVKUUpJScVKIUpGPqI+nUqxSrVK8UrVSwVLNUtdS3lLjUuaY7VLgUvNS9VL4UvlTBlMIdThTDVMQ" +
|
||||
"Uw9TFVMaUyNTL1MxUzNTOFNAU0ZTRU4XU0lTTVHWU15TaVNuWRhTe1N3U4JTllOgU6ZTpVOuU7BTtlPDfBKW2VPfZvxx7lPuU+hT7VP6VAFUPVRAVCxULVQ8VC5UNlQpVB1UTlSPVHVUjlRfVHFUd1RwVJJUe1SAVHZUhFSQVIZUx1SiVLhUpVSsVMRUyFSo////////" +
|
||||
"VKtUwlSkVL5UvFTYVOVU5lUPVRRU/VTuVO1U+lTiVTlVQFVjVUxVLlVcVUVVVlVXVThVM1VdVZlVgFSvVYpVn1V7VX5VmFWeVa5VfFWDValVh1WoVdpVxVXfVcRV3FXkVdRWFFX3VhZV/lX9VhtV+VZOVlBx31Y0VjZWMlY4//9Wa1ZkVi9WbFZqVoZWgFaKVqBWlFaP" +
|
||||
"VqVWrla2VrRWwla8VsFWw1bAVshWzlbRVtNW11buVvlXAFb/VwRXCVcIVwtXDVcTVxhXFlXHVxxXJlc3VzhXTlc7V0BXT1dpV8BXiFdhV39XiVeTV6BXs1ekV6pXsFfDV8ZX1FfSV9NYClfWV+NYC1gZWB1YclghWGJYS1hwa8BYUlg9WHlYhVi5WJ9Yq1i6WN5Yu1i4" +
|
||||
"WK5YxVjTWNFY11jZWNhY5VjcWORY31jvWPpY+Vj7WPxY/VkCWQpZEFkbaKZZJVksWS1ZMlk4WT560llVWVBZTllaWVhZYllgWWdZbFlp////////WXhZgVmdT15Pq1mjWbJZxlnoWdxZjVnZWdpaJVofWhFaHFoJWhpaQFpsWklaNVo2WmJaalqaWrxavlrLWsJavVrj" +
|
||||
"Wtda5lrpWtZa+lr7WwxbC1sWWzJa0FsqWzZbPltDW0VbQFtRW1VbWltbW2VbaVtwW3NbdVt4ZYhbeluA//9bg1umW7hbw1vHW8lb1FvQW+Rb5lviW95b5VvrW/Bb9lvzXAVcB1wIXA1cE1wgXCJcKFw4XDlcQVxGXE5cU1xQXE9bcVxsXG5OYlx2XHlcjFyRXJRZm1yr" +
|
||||
"XLtctly8XLdcxVy+XMdc2VzpXP1c+lztXYxc6l0LXRVdF11cXR9dG10RXRRdIl0aXRldGF1MXVJdTl1LXWxdc112XYddhF2CXaJdnV2sXa5dvV2QXbddvF3JXc1d013SXdZd213rXfJd9V4LXhpeGV4RXhteNl43XkReQ15AXk5eV15UXl9eYl5kXkdedV52XnqevF5/" +
|
||||
"XqBewV7CXshe0F7P////////XtZe417dXtpe217iXuFe6F7pXuxe8V7zXvBe9F74Xv5fA18JX11fXF8LXxFfFl8pXy1fOF9BX0hfTF9OXy9fUV9WX1dfWV9hX21fc193X4Nfgl9/X4pfiF+RX4dfnl+ZX5hfoF+oX61fvF/WX/tf5F/4X/Ff3WCzX/9gIWBg//9gGWAQ" +
|
||||
"YClgDmAxYBtgFWArYCZgD2A6YFpgQWBqYHdgX2BKYEZgTWBjYENgZGBCYGxga2BZYIFgjWDnYINgmmCEYJtglmCXYJJgp2CLYOFguGDgYNNgtF/wYL1gxmC1YNhhTWEVYQZg9mD3YQBg9GD6YQNhIWD7YPFhDWEOYUdhPmEoYSdhSmE/YTxhLGE0YT1hQmFEYXNhd2FY" +
|
||||
"YVlhWmFrYXRhb2FlYXFhX2FdYVNhdWGZYZZhh2GsYZRhmmGKYZFhq2GuYcxhymHJYfdhyGHDYcZhumHLf3lhzWHmYeNh9mH6YfRh/2H9Yfxh/mIAYghiCWINYgxiFGIb////////Yh5iIWIqYi5iMGIyYjNiQWJOYl5iY2JbYmBiaGJ8YoJiiWJ+YpJik2KWYtRig2KU" +
|
||||
"Ytdi0WK7Ys9i/2LGZNRiyGLcYsxiymLCYsdim2LJYwxi7mLxYydjAmMIYu9i9WNQYz5jTWQcY09jlmOOY4Bjq2N2Y6Njj2OJY59jtWNr//9jaWO+Y+ljwGPGY+NjyWPSY/ZjxGQWZDRkBmQTZCZkNmUdZBdkKGQPZGdkb2R2ZE5lKmSVZJNkpWSpZIhkvGTaZNJkxWTH" +
|
||||
"ZLtk2GTCZPFk54IJZOBk4WKsZONk72UsZPZk9GTyZPplAGT9ZRhlHGUFZSRlI2UrZTRlNWU3ZTZlOHVLZUhlVmVVZU1lWGVeZV1lcmV4ZYJlg4uKZZtln2WrZbdlw2XGZcFlxGXMZdJl22XZZeBl4WXxZ3JmCmYDZftnc2Y1ZjZmNGYcZk9mRGZJZkFmXmZdZmRmZ2Zo" +
|
||||
"Zl9mYmZwZoNmiGaOZolmhGaYZp1mwWa5Zslmvma8////////ZsRmuGbWZtpm4GY/ZuZm6WbwZvVm92cPZxZnHmcmZyeXOGcuZz9nNmdBZzhnN2dGZ15nYGdZZ2NnZGeJZ3BnqWd8Z2pnjGeLZ6ZnoWeFZ7dn72e0Z+xns2fpZ7hn5GfeZ91n4mfuZ7lnzmfGZ+dqnGge" +
|
||||
"aEZoKWhAaE1oMmhO//9os2graFloY2h3aH9on2iPaK1olGidaJtog2quaLlodGi1aKBoumkPaI1ofmkBaMppCGjYaSJpJmjhaQxozWjUaOdo1Wk2aRJpBGjXaONpJWj5aOBo72koaSppGmkjaSFoxml5aXdpXGl4aWtpVGl+aW5pOWl0aT1pWWkwaWFpXmldaYFpammy" +
|
||||
"aa5p0Gm/acFp02m+ac5b6GnKad1pu2nDaadqLmmRaaBpnGmVabRp3mnoagJqG2n/awpp+WnyaedqBWmxah5p7WoUaetqCmoSasFqI2oTakRqDGpyajZqeGpHamJqWWpmakhqOGoiapBqjWqgaoRqomqj////////apeGF2q7asNqwmq4arNqrGreatFq32qqatpq6mr7" +
|
||||
"awWGFmr6axJrFpsxax9rOGs3dtxrOZjua0drQ2tJa1BrWWtUa1trX2tha3hreWt/a4BrhGuDa41rmGuVa55rpGuqa6trr2uya7Frs2u3a7xrxmvLa9Nr32vsa+tr82vv//+evmwIbBNsFGwbbCRsI2xebFVsYmxqbIJsjWyabIFsm2x+bGhsc2ySbJBsxGzxbNNsvWzX" +
|
||||
"bMVs3WyubLFsvmy6bNts72zZbOptH4hNbTZtK209bThtGW01bTNtEm0MbWNtk21kbVpteW1ZbY5tlW/kbYVt+W4VbgpttW3HbeZtuG3Gbext3m3Mbeht0m3Fbfpt2W3kbdVt6m3ubi1ubm4ubhlucm5fbj5uI25rbitudm5Nbh9uQ246bk5uJG7/bh1uOG6CbqpumG7J" +
|
||||
"brdu0269bq9uxG6ybtRu1W6PbqVuwm6fb0FvEXBMbuxu+G7+bz9u8m8xbu9vMm7M////////bz5vE273b4Zvem94b4FvgG9vb1tv829tb4JvfG9Yb45vkW/Cb2Zvs2+jb6FvpG+5b8Zvqm/fb9Vv7G/Ub9hv8W/ub9twCXALb/pwEXABcA9v/nAbcBpvdHAdcBhwH3Aw" +
|
||||
"cD5wMnBRcGNwmXCScK9w8XCscLhws3CucN9wy3Dd//9w2XEJcP1xHHEZcWVxVXGIcWZxYnFMcVZxbHGPcftxhHGVcahxrHHXcblxvnHScclx1HHOceBx7HHncfVx/HH5cf9yDXIQchtyKHItcixyMHIycjtyPHI/ckByRnJLclhydHJ+coJygXKHcpJylnKicqdyuXKy" +
|
||||
"csNyxnLEcs5y0nLicuBy4XL5cvdQD3MXcwpzHHMWcx1zNHMvcylzJXM+c05zT57Yc1dzanNoc3BzeHN1c3tzenPIc7NzznO7c8Bz5XPuc950onQFdG90JXP4dDJ0OnRVdD90X3RZdEF0XHRpdHB0Y3RqdHZ0fnSLdJ50p3TKdM901HPx////////dOB043TndOl07nTy" +
|
||||
"dPB08XT4dPd1BHUDdQV1DHUOdQ11FXUTdR51JnUsdTx1RHVNdUp1SXVbdUZ1WnVpdWR1Z3VrdW11eHV2dYZ1h3V0dYp1iXWCdZR1mnWddaV1o3XCdbN1w3W1db11uHW8dbF1zXXKddJ12XXjdd51/nX///91/HYBdfB1+nXydfN2C3YNdgl2H3YndiB2IXYidiR2NHYw" +
|
||||
"djt2R3ZIdkZ2XHZYdmF2YnZodml2anZndmx2cHZydnZ2eHZ8doB2g3aIdot2jnaWdpN2mXaadrB2tHa4drl2unbCds121nbSdt524Xbldud26oYvdvt3CHcHdwR3KXckdx53JXcmdxt3N3c4d0d3Wndod2t3W3dld393fnd5d453i3eRd6B3nnewd7Z3uXe/d7x3vXe7" +
|
||||
"d8d3zXfXd9p33Hfjd+53/HgMeBJ5JnggeSp4RXiOeHR4hnh8eJp4jHijeLV4qniveNF4xnjLeNR4vni8eMV4ynjs////////eOd42nj9ePR5B3kSeRF5GXkseSt5QHlgeVd5X3laeVV5U3l6eX95inmdeaefS3mqea55s3m5ebp5yXnVeed57HnheeN6CHoNehh6GXog" +
|
||||
"eh95gHoxejt6Pno3ekN6V3pJemF6Ynppn516cHp5en16iHqXepV6mHqWeql6yHqw//96tnrFesR6v5CDesd6ynrNes961XrTetl62nrdeuF64nrmeu168HsCew97CnsGezN7GHsZex57NXsoezZ7UHt6ewR7TXsLe0x7RXt1e2V7dHtne3B7cXtse257nXuYe597jXuc" +
|
||||
"e5p7i3uSe497XXuZe8t7wXvMe897tHvGe9176XwRfBR75nvlfGB8AHwHfBN783v3fBd8DXv2fCN8J3wqfB98N3wrfD18THxDfFR8T3xAfFB8WHxffGR8VnxlfGx8dXyDfJB8pHytfKJ8q3yhfKh8s3yyfLF8rny5fL18wHzFfMJ82HzSfNx84ps7fO988nz0fPZ8+n0G" +
|
||||
"////////fQJ9HH0VfQp9RX1LfS59Mn0/fTV9Rn1zfVZ9Tn1yfWh9bn1PfWN9k32JfVt9j319fZt9un2ufaN9tX3Hfb19q349faJ9r33cfbh9n32wfdh93X3kfd59+33yfeF+BX4KfiN+IX4SfjF+H34Jfgt+In5GfmZ+O341fjl+Q343//9+Mn46fmd+XX5Wfl5+WX5a" +
|
||||
"fnl+an5pfnx+e36DfdV+fY+ufn9+iH6Jfox+kn6QfpN+lH6Wfo5+m36cfzh/On9Ff0x/TX9Of1B/UX9Vf1R/WH9ff2B/aH9pf2d/eH+Cf4Z/g3+If4d/jH+Uf55/nX+af6N/r3+yf7l/rn+2f7iLcX/Ff8Z/yn/Vf9R/4X/mf+l/83/5mNyABoAEgAuAEoAYgBmAHIAh" +
|
||||
"gCiAP4A7gEqARoBSgFiAWoBfgGKAaIBzgHKAcIB2gHmAfYB/gISAhoCFgJuAk4CagK1RkICsgNuA5YDZgN2AxIDagNaBCYDvgPGBG4EpgSOBL4FL////////louBRoE+gVOBUYD8gXGBboFlgWaBdIGDgYiBioGAgYKBoIGVgaSBo4FfgZOBqYGwgbWBvoG4gb2BwIHC" +
|
||||
"gbqByYHNgdGB2YHYgciB2oHfgeCB54H6gfuB/oIBggKCBYIHggqCDYIQghaCKYIrgjiCM4JAglmCWIJdglqCX4Jk//+CYoJogmqCa4IugnGCd4J4gn6CjYKSgquCn4K7gqyC4YLjgt+C0oL0gvOC+oOTgwOC+4L5gt6DBoLcgwmC2YM1gzSDFoMygzGDQIM5g1CDRYMv" +
|
||||
"gyuDF4MYg4WDmoOqg5+DooOWgyODjoOHg4qDfIO1g3ODdYOgg4mDqIP0hBOD64POg/2EA4PYhAuDwYP3hAeD4IPyhA2EIoQgg72EOIUGg/uEbYQqhDyFWoSEhHeEa4SthG6EgoRphEaELIRvhHmENYTKhGKEuYS/hJ+E2YTNhLuE2oTQhMGExoTWhKGFIYT/hPSFF4UY" +
|
||||
"hSyFH4UVhRSE/IVAhWOFWIVI////////hUGGAoVLhVWFgIWkhYiFkYWKhaiFbYWUhZuF6oWHhZyFd4V+hZCFyYW6hc+FuYXQhdWF3YXlhdyF+YYKhhOGC4X+hfqGBoYihhqGMIY/hk1OVYZUhl+GZ4ZxhpOGo4aphqqGi4aMhraGr4bEhsaGsIbJiCOGq4bUht6G6Ybs" +
|
||||
"//+G34bbhu+HEocGhwiHAIcDhvuHEYcJhw2G+YcKhzSHP4c3hzuHJYcphxqHYIdfh3iHTIdOh3SHV4doh26HWYdTh2OHaogFh6KHn4eCh6+Hy4e9h8CH0JbWh6uHxIezh8eHxoe7h++H8ofgiA+IDYf+h/aH94gOh9KIEYgWiBWIIoghiDGINog5iCeIO4hEiEKIUohZ" +
|
||||
"iF6IYohriIGIfoieiHWIfYi1iHKIgoiXiJKIroiZiKKIjYikiLCIv4ixiMOIxIjUiNiI2YjdiPmJAoj8iPSI6IjyiQSJDIkKiROJQ4keiSWJKokriUGJRIk7iTaJOIlMiR2JYIle////////iWaJZIltiWqJb4l0iXeJfomDiYiJiomTiZiJoYmpiaaJrImvibKJuom9" +
|
||||
"ib+JwInaidyJ3YnnifSJ+IoDihaKEIoMihuKHYolijaKQYpbilKKRopIinyKbYpsimKKhYqCioSKqIqhipGKpYqmipqKo4rEis2KworaiuuK84rn//+K5IrxixSK4IriiveK3orbiwyLB4saiuGLFosQixeLIIszl6uLJosriz6LKItBi0yLT4tOi0mLVotbi1qLa4tf" +
|
||||
"i2yLb4t0i32LgIuMi46LkouTi5aLmYuajDqMQYw/jEiMTIxOjFCMVYxijGyMeIx6jIKMiYyFjIqMjYyOjJSMfIyYYh2MrYyqjL2MsoyzjK6MtozIjMGM5IzjjNqM/Yz6jPuNBI0FjQqNB40PjQ2NEJ9OjROMzY0UjRaNZ41tjXGNc42BjZmNwo2+jbqNz43ajdaNzI3b" +
|
||||
"jcuN6o3rjd+N4438jgiOCY3/jh2OHo4Qjh+OQo41jjCONI5K////////jkeOSY5MjlCOSI5ZjmSOYI4qjmOOVY52jnKOfI6BjoeOhY6EjouOio6TjpGOlI6ZjqqOoY6sjrCOxo6xjr6OxY7IjsuO247jjvyO+47rjv6PCo8FjxWPEo8ZjxOPHI8fjxuPDI8mjzOPO485" +
|
||||
"j0WPQo8+j0yPSY9Gj06PV49c//+PYo9jj2SPnI+fj6OPrY+vj7eP2o/lj+KP6o/vkIeP9JAFj/mP+pARkBWQIZANkB6QFpALkCeQNpA1kDmP+JBPkFCQUZBSkA6QSZA+kFaQWJBekGiQb5B2lqiQcpCCkH2QgZCAkIqQiZCPkKiQr5CxkLWQ4pDkYkiQ25ECkRKRGZEy" +
|
||||
"kTCRSpFWkViRY5FlkWmRc5FykYuRiZGCkaKRq5GvkaqRtZG0kbqRwJHBkcmRy5HQkdaR35HhkduR/JH1kfaSHpH/khSSLJIVkhGSXpJXkkWSSZJkkkiSlZI/kkuSUJKckpaSk5KbklqSz5K5kreS6ZMPkvqTRJMu////////kxmTIpMakyOTOpM1kzuTXJNgk3yTbpNW" +
|
||||
"k7CTrJOtk5STuZPWk9eT6JPlk9iTw5Pdk9CTyJPklBqUFJQTlAOUB5QQlDaUK5Q1lCGUOpRBlFKURJRblGCUYpRelGqSKZRwlHWUd5R9lFqUfJR+lIGUf5WClYeVipWUlZaVmJWZ//+VoJWolaeVrZW8lbuVuZW+lcpv9pXDlc2VzJXVldSV1pXcleGV5ZXiliGWKJYu" +
|
||||
"li+WQpZMlk+WS5Z3llyWXpZdll+WZpZylmyWjZaYlpWWl5aqlqeWsZaylrCWtJa2lriWuZbOlsuWyZbNiU2W3JcNltWW+ZcElwaXCJcTlw6XEZcPlxaXGZcklyqXMJc5lz2XPpdEl0aXSJdCl0mXXJdgl2SXZpdoUtKXa5dxl3mXhZd8l4GXepeGl4uXj5eQl5yXqJem" +
|
||||
"l6OXs5e0l8OXxpfIl8uX3Jftn0+X8nrfl/aX9ZgPmAyYOJgkmCGYN5g9mEaYT5hLmGuYb5hw////////mHGYdJhzmKqYr5ixmLaYxJjDmMaY6ZjrmQOZCZkSmRSZGJkhmR2ZHpkkmSCZLJkumT2ZPplCmUmZRZlQmUuZUZlSmUyZVZmXmZiZpZmtma6ZvJnfmduZ3ZnY" +
|
||||
"mdGZ7ZnumfGZ8pn7mfiaAZoPmgWZ4poZmiuaN5pFmkKaQJpD//+aPppVmk2aW5pXml+aYpplmmSaaZprmmqarZqwmryawJrPmtGa05rUmt6a35rimuOa5prvmuua7pr0mvGa95r7mwabGJsamx+bIpsjmyWbJ5somymbKpsumy+bMptEm0ObT5tNm06bUZtYm3Sbk5uD" +
|
||||
"m5GblpuXm5+boJuom7SbwJvKm7mbxpvPm9Gb0pvjm+Kb5JvUm+GcOpvym/Gb8JwVnBScCZwTnAycBpwInBKcCpwEnC6cG5wlnCScIZwwnEecMpxGnD6cWpxgnGecdpx4nOec7JzwnQmdCJzrnQOdBp0qnSadr50jnR+dRJ0VnRKdQZ0/nT6dRp1I////////nV2dXp1k" +
|
||||
"nVGdUJ1ZnXKdiZ2Hnaudb516nZqdpJ2pnbKdxJ3BnbuduJ26ncadz53Cndmd0534nead7Z3vnf2eGp4bnh6edZ55nn2egZ6InouejJ6SnpWekZ6dnqWeqZ64nqqerZdhnsyezp7PntCe1J7cnt6e3Z7gnuWe6J7v//+e9J72nvee+Z77nvye/Z8Hnwh2t58VnyGfLJ8+" +
|
||||
"n0qfUp9Un2OfX59gn2GfZp9nn2yfap93n3Kfdp+Vn5yfoFgvaceQWXRkUdxxmf//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
|
||||
"////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
|
||||
"////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" +
|
||||
"/////////////////////////////////////////////w==";
|
||||
|
||||
|
||||
private static final short[] UNICODE_TO_QR_KANJI = new short[1 << 16];
|
||||
|
||||
static { // Unpack the Shift JIS table into a more computation-friendly form
|
||||
Arrays.fill(UNICODE_TO_QR_KANJI, (short) -1);
|
||||
byte[] bytes = Base64.decode(PACKED_QR_KANJI_TO_UNICODE, Base64.NO_WRAP);
|
||||
for (int i = 0; i < bytes.length; i += 2) {
|
||||
char c = (char) (((bytes[i] & 0xFF) << 8) | (bytes[i + 1] & 0xFF));
|
||||
if (c == 0xFFFF)
|
||||
continue;
|
||||
assert UNICODE_TO_QR_KANJI[c] == -1;
|
||||
UNICODE_TO_QR_KANJI[c] = (short) (i / 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---- Miscellaneous ----*/
|
||||
|
||||
private QrSegmentAdvanced() {
|
||||
} // Not instantiable
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package ru.ra66it.qrcodegenerator;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
rootProject.name = "QRCodeGeneratorExample"
|
||||
include ':app'
|
||||
include ':qrcodegen'
|