chatgui: invite link dialogs, zxing-cpp QR code, crypto transport, channel improvements; msg_transport: chat protocol support
No files matched your search
@@ -0,0 +1,43 @@
|
||||
---
|
||||
Language: Cpp
|
||||
Standard: c++17
|
||||
BasedOnStyle: LLVM
|
||||
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
UseTab: ForContinuationAndIndentation # ForIndentation
|
||||
|
||||
AccessModifierOffset: -4
|
||||
ColumnLimit: 135
|
||||
|
||||
#AlignConsecutiveAssignments: true
|
||||
AlignConsecutiveBitFields: true
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlignTrailingComments: true
|
||||
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
#AllowShortLambdasOnASingleLine: Inline
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterEnum: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
AfterFunction: true
|
||||
SplitEmptyFunction: false
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeTernaryOperators: true
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
FixNamespaceComments: true
|
||||
IncludeBlocks: Regroup
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
PointerAlignment: Left
|
||||
ReflowComments: true
|
||||
SortIncludes: true
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
Checks: >
|
||||
-*,
|
||||
bugprone-*,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-bugprone-exception-escape,
|
||||
-bugprone-implicit-widening-of-multiplication-result,
|
||||
-bugprone-integer-division,
|
||||
-bugprone-macro-parentheses,
|
||||
-bugprone-narrowing-conversions,
|
||||
-bugprone-signed-char-misuse,
|
||||
-bugprone-use-after-move,
|
||||
-bugprone-assignment-in-if-condition,
|
||||
performance-*,
|
||||
-performance-enum-size,
|
||||
-performance-move-const-arg,
|
||||
portability-*,
|
||||
-portability-avoid-pragma-once,
|
||||
readability-avoid-const-params-in-decls,
|
||||
readability-const-return-type,
|
||||
readability-delete-null-pointer,
|
||||
readability-deleted-default,
|
||||
readability-inconsistent-declaration-parameter-name,
|
||||
readability-misleading-indentation,
|
||||
readability-misplaced-array-index,
|
||||
readability-non-const-parameter,
|
||||
readability-redundant-*,
|
||||
readability-simplify-*,
|
||||
-readability-simplify-boolean-expr,
|
||||
readability-static-*,
|
||||
-readability-static-definition-in-anonymous-namespace,
|
||||
readability-string-compare,
|
||||
readability-suspicious-call-argument,
|
||||
readability-uniqueptr-delete-release,
|
||||
readability-use-anyofallof,
|
||||
-readability-redundant-inline-specifier,
|
||||
-readability-redundant-member-init,
|
||||
modernize-deprecated-*,
|
||||
modernize-loop-convert,
|
||||
modernize-make-*,
|
||||
modernize-pass-by-value,
|
||||
modernize-raw-string-literal,
|
||||
modernize-redundant-void-arg,
|
||||
modernize-replace-*,
|
||||
modernize-return-braced-init-list,
|
||||
modernize-shrink-to-fit,
|
||||
modernize-unary-static-assert,
|
||||
modernize-use-*,
|
||||
-modernize-use-constraints,
|
||||
-modernize-use-designated-initializers,
|
||||
-modernize-use-nodiscard,
|
||||
-modernize-use-ranges,
|
||||
-modernize-use-trailing-return-type,
|
||||
-modernize-use-integer-sign-comparison,
|
||||
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: '^(?!.*zint\.h).*$'
|
||||
FormatStyle: file
|
||||
@@ -0,0 +1,19 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
|
||||
[*.{cpp,c,h,html,py,kt,cs,qlm}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
max_line_length = 135
|
||||
|
||||
[CMakeLists.txt]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
test/samples export-ignore
|
||||
zint export-ignore
|
||||
.git* export-ignore
|
||||
@@ -0,0 +1,2 @@
|
||||
github: axxel
|
||||
custom: 'www.paypal.com/donate/?hosted_button_id=8LDF4NNQF8Z3L'
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import tempfile
|
||||
import shutil
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def run_command(cmd, cwd=None):
|
||||
try:
|
||||
return subprocess.run(cmd, check=True, capture_output=True, text=True, cwd=cwd)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"✗ Error {cmd}: {e.stderr}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main(repo_root=None, output_dir=None):
|
||||
repo_root = Path.cwd() if repo_root is None else Path(repo_root).resolve()
|
||||
output_dir = repo_root if output_dir is None else Path(output_dir).resolve()
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
print(f"Working in repository: {repo_root}")
|
||||
|
||||
# Extract version and optional suffix from core/CMakeLists.txt
|
||||
cmake_txt = (repo_root / "core" / "CMakeLists.txt").read_text()
|
||||
version = re.search(r'ZXing VERSION "?([0-9]+\.[0-9]+\.[0-9]+)"?', cmake_txt).group(1)
|
||||
suffix = re.search(r'ZXING_VERSION_SUFFIX "?([^\)\s\"]*)"?', cmake_txt).group(1)
|
||||
ver_tag = f"{version}{suffix}"
|
||||
print(f"Project version from CMake: {ver_tag}")
|
||||
|
||||
# Create output filenames with git tag
|
||||
output_stem = f"zxing-cpp-{ver_tag}"
|
||||
output_tgz = output_dir / f"{output_stem}.tar.gz"
|
||||
output_zip = output_dir / f"{output_stem}.zip"
|
||||
|
||||
# Step 1: Create initial tar ball via git archive
|
||||
print("\n[1/4] Creating git archive...")
|
||||
run_command(["git", "archive", f"--prefix={output_stem}/", f"--output={output_tgz}", "HEAD", repo_root])
|
||||
print(f"✓ Created git archive: {output_tgz}")
|
||||
|
||||
# Step 2: Extract tar ball into temporary folder
|
||||
print("\n[2/4] Extracting archive to temporary folder...")
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
print(f"✓ Created temporary directory: {temp_dir}")
|
||||
|
||||
run_command(["tar", "-xf", str(output_tgz), "-C", temp_dir])
|
||||
print(f"✓ Extracted archive to temporary folder")
|
||||
|
||||
# Step 3: Replace files in core/src/libzint/ with files from zint/backend/
|
||||
print("\n[3/4] Replacing files in core/src/libzint/ with zint/backend/ files...")
|
||||
dst_dir = Path(temp_dir) / output_stem / "core" / "src" / "libzint"
|
||||
src_dir = repo_root / "zint" / "backend" # Use actual repo directory
|
||||
|
||||
# Find all files/symlinks currently in core/src/libzint/ (including subdirectories)
|
||||
libzint_files = list(dst_dir.rglob("*"))
|
||||
# Include both regular files and symlinks
|
||||
libzint_files = [f for f in libzint_files if f.is_file() or f.is_symlink()]
|
||||
|
||||
replaced_count = 0
|
||||
not_found_count = 0
|
||||
|
||||
for libzint_file in libzint_files:
|
||||
# Get relative path from core/src/libzint/
|
||||
rel_path = libzint_file.relative_to(dst_dir)
|
||||
|
||||
# Source path in zint/backend/
|
||||
source_file = src_dir / rel_path
|
||||
|
||||
# Only replace if the file exists in zint/backend/
|
||||
if source_file.exists() and source_file.is_file():
|
||||
# Remove symlink if it exists
|
||||
if libzint_file.is_symlink():
|
||||
libzint_file.unlink()
|
||||
# Copy the actual file
|
||||
shutil.copy2(source_file, libzint_file)
|
||||
replaced_count += 1
|
||||
else:
|
||||
not_found_count += 1
|
||||
print(f" - Skipped (not in zint/backend): {rel_path}")
|
||||
|
||||
print(f"✓ Replaced {replaced_count} files, skipped {not_found_count} files")
|
||||
|
||||
# Step 4: Create tar.gz and zip archives
|
||||
print("\n[4/4] Creating archives (tar.gz and zip)...")
|
||||
run_command(["tar", "-czf", str(output_tgz), "-C", temp_dir, output_stem])
|
||||
|
||||
if (output_zip.exists()):
|
||||
output_zip.unlink()
|
||||
|
||||
run_command(["zip", "-q", "-r", str(output_zip), output_stem], cwd=temp_dir)
|
||||
|
||||
# Cleanup initial tar file
|
||||
print(f"\n✅ Done! Source archives created:")
|
||||
print(f" - {output_tgz}")
|
||||
print(f" - {output_zip}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
repo_root = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
output_dir = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
main(repo_root=repo_root, output_dir=output_dir)
|
||||
@@ -0,0 +1,435 @@
|
||||
name: CI
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
# don't cancel all jobs just because one of them failed
|
||||
fail-fast: false
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Python 3
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Configure
|
||||
run: >
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_SHARED_LIBS=ON
|
||||
-DZXING_BLACKBOX_TESTS=ON -DZXING_TEST_DOTNET=ON -DZXING_TEST_GO=ON -DZXING_TEST_INSTALL=ON -DZXING_UNIT_TESTS=ON
|
||||
-DZXING_PYTHON_MODULE=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --parallel --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Set PATH for Tests
|
||||
shell: bash # to make the $GITHUB_PATH update work
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
echo "${GITHUB_WORKSPACE}/build/core/${BUILD_TYPE}" >> $GITHUB_PATH
|
||||
echo "${GITHUB_WORKSPACE}/build/bin/${BUILD_TYPE}" >> $GITHUB_PATH
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build -V -C ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
cmake -E make_directory install
|
||||
cmake --install build --config ${{env.BUILD_TYPE}} --prefix ${{github.workspace}}/install
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{matrix.os}}-artifacts
|
||||
path: install
|
||||
|
||||
build-exp:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest, ubuntu-24.04-arm, macos-15-intel, windows-11-arm]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Python 3
|
||||
id: setup-python
|
||||
if: matrix.os == 'windows-11-arm' # the default python on the arm runner is an x64 build
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
architecture: arm64
|
||||
|
||||
- name: Configure
|
||||
run: >
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_SHARED_LIBS=ON
|
||||
-DZXING_READERS=ON -DZXING_WRITERS=BOTH -DZXING_EXPERIMENTAL_API=ON
|
||||
-DZXING_BLACKBOX_TESTS=ON -DZXING_TEST_INSTALL=ON -DZXING_UNIT_TESTS=OFF -DZXING_PYTHON_MODULE=ON
|
||||
${{ steps.setup-python.outputs.python-path != '' && format('-DPython_EXECUTABLE={0}', steps.setup-python.outputs.python-path) || '' }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --parallel --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Set PATH for Tests
|
||||
shell: bash # to make the $GITHUB_PATH update work
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
echo "${GITHUB_WORKSPACE}/build/core/${BUILD_TYPE}" >> $GITHUB_PATH
|
||||
echo "${GITHUB_WORKSPACE}/build/bin/${BUILD_TYPE}" >> $GITHUB_PATH
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build -V -C ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
cmake -E make_directory install
|
||||
cmake --install build --config ${{env.BUILD_TYPE}} --prefix ${{github.workspace}}/install
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{matrix.os}}-exp-artifacts
|
||||
path: install
|
||||
|
||||
build-ubuntu-gcc-12: # keep homebrew happy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Configure
|
||||
run: >
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
-DZXING_WRITERS=BOTH
|
||||
-DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --parallel
|
||||
|
||||
- name: Test
|
||||
run: ctest -V --test-dir build
|
||||
|
||||
build-ubuntu-asan-ubsan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Configure
|
||||
run: >
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DZXING_WRITERS=BOTH
|
||||
-DZXING_BLACKBOX_TESTS=ON -DZXING_UNIT_TESTS=ON -DZXING_PYTHON_MODULE=OFF -DZXING_C_API=OFF
|
||||
-DCMAKE_CXX_COMPILER=clang++
|
||||
-DCMAKE_CXX_FLAGS="-march=native -fsanitize=address,undefined -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer"
|
||||
-DCMAKE_C_COMPILER=clang
|
||||
-DCMAKE_C_FLAGS="-march=native -fsanitize=address,undefined -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer"
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --parallel
|
||||
|
||||
- name: Test
|
||||
run: ctest -V --test-dir build
|
||||
|
||||
build-ubuntu-tsan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Configure
|
||||
run: >
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DZXING_WRITERS=OFF -DZXING_EXAMPLES=OFF
|
||||
-DZXING_BLACKBOX_TESTS=ON -DZXING_UNIT_TESTS=OFF -DZXING_PYTHON_MODULE=OFF -DZXING_C_API=OFF
|
||||
-DCMAKE_CXX_COMPILER=clang++
|
||||
-DCMAKE_CXX_FLAGS="-march=native -fsanitize=thread -fno-optimize-sibling-calls -fno-omit-frame-pointer -g"
|
||||
-DCMAKE_C_COMPILER=clang
|
||||
-DCMAKE_C_FLAGS="-march=native -fsanitize=thread -fno-optimize-sibling-calls -fno-omit-frame-pointer -g"
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --parallel
|
||||
|
||||
- name: Test
|
||||
run: ctest -V --test-dir build
|
||||
|
||||
build-old-writers:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Configure
|
||||
run: >
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
-DZXING_READERS=ON -DZXING_WRITERS=OLD -DZXING_PYTHON_MODULE=ON -DZXING_C_API=ON
|
||||
-DZXING_BLACKBOX_TESTS=ON -DZXING_TEST_INSTALL=ON -DZXING_UNIT_TESTS=ON -DZXING_PYTHON_MODULE=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --parallel
|
||||
|
||||
- name: Test
|
||||
run: ctest -V --test-dir build
|
||||
|
||||
build-ios:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Build the swift package
|
||||
run: swift build
|
||||
|
||||
- name: Build the demo app
|
||||
working-directory: wrappers/ios/demo
|
||||
run: xcodebuild build -scheme demo -sdk "iphonesimulator"
|
||||
|
||||
# - name: Validate the Pod
|
||||
# run: pod lib lint --allow-warnings
|
||||
|
||||
build-android:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- name: Build the lib/app
|
||||
working-directory: wrappers/android
|
||||
run: ./gradlew assembleRelease
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: android-artifacts
|
||||
path: "wrappers/android/zxingcpp/build/outputs/aar/zxingcpp-release.aar"
|
||||
|
||||
- name: Publish Library Snapshot
|
||||
if: github.repository == 'zxing-cpp/zxing-cpp' && github.event_name != 'pull_request'
|
||||
working-directory: wrappers/android
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_publishSnapshot: true
|
||||
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALUSERNAME }}
|
||||
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALPASSWORD }}
|
||||
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY }}
|
||||
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.GPG_KEY_ID }}
|
||||
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_KEY_PASSWORD }}
|
||||
run: ./gradlew publishToMavenCentral --no-configuration-cache
|
||||
|
||||
build-kn:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: wrappers/kn
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Prepare Zig toolchain
|
||||
uses: mlugg/setup-zig@v2
|
||||
with:
|
||||
version: 0.13.0
|
||||
|
||||
- name: Checkout toolchain initializer repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ISNing/kn-toolchain-initializer
|
||||
path: wrappers/kn/.kn-toolchain-initializer
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/actions/wrapper-validation@v5
|
||||
|
||||
- name: Initialize Kotlin/Native toolchain
|
||||
working-directory: wrappers/kn/.kn-toolchain-initializer
|
||||
run: ./gradlew build -DkotlinVersion=2.2.0
|
||||
|
||||
- name: Run test for linuxX64 target
|
||||
run: |
|
||||
echo -e "konan.dir=$HOME/.konan/kotlin-native-prebuilt-linux-x86_64-2.2.0" >> local.properties
|
||||
./gradlew linuxX64Test
|
||||
|
||||
build-python:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.12']
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install numpy pillow build
|
||||
|
||||
- name: Build module
|
||||
working-directory: wrappers/python
|
||||
run: python -m pip -v install -e .
|
||||
|
||||
- name: Test module
|
||||
working-directory: wrappers/python
|
||||
run: python -m unittest -v
|
||||
|
||||
- name: Build wheel
|
||||
working-directory: wrappers/python
|
||||
run: python -m build --wheel
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{matrix.os}}-python-artifacts
|
||||
path: wrappers/python/dist/*
|
||||
|
||||
build-rust:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: wrappers/rust
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Lint
|
||||
run: |
|
||||
cargo fmt --check
|
||||
cargo clippy -- -Dwarnings
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release --verbose --all-features --examples
|
||||
|
||||
- name: Test
|
||||
run: cargo test --release --all-features
|
||||
|
||||
- name: Package
|
||||
# --allow-dirty is required on the windows build (but not the ubuntu build?!)
|
||||
run: |
|
||||
cargo package --all-features
|
||||
cargo publish --dry-run
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: rust-artifacts
|
||||
path: wrappers/rust/target/package/*.crate
|
||||
|
||||
build-wasm:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- uses: emscripten-core/setup-emsdk@v15
|
||||
|
||||
- name: Configure
|
||||
run: emcmake cmake -Swrappers/wasm -Bbuild
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --parallel
|
||||
|
||||
# - name: Test
|
||||
# run: node build/EmGlueTests.js
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: wasm-artifacts
|
||||
path: |
|
||||
build/zxing*
|
||||
build/demo*
|
||||
|
||||
# disable WinRT build for now, as it does not compile with C++20 (see wrappers/winrt/CMakeLists.txt)
|
||||
# build-winrt:
|
||||
# runs-on: windows-latest
|
||||
|
||||
# steps:
|
||||
# - uses: actions/checkout@v6
|
||||
# with:
|
||||
# submodules: true
|
||||
|
||||
# - name: Configure
|
||||
# shell: cmd # powershell messes up the arguments containing a '.' ?!?
|
||||
# run: >
|
||||
# cmake -S wrappers/winrt -B build -A ARM64
|
||||
# -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_BUILD_TYPE=Release
|
||||
# -DBUILD_WINRT_LIB=ON -DZXING_EXAMPLES=OFF -DZXING_BLACKBOX_TESTS=OFF -DZXING_C_API=OFF
|
||||
# -DEXTENSION_SDK_OUTPUT=dist/UAP/v0.8.0.0/ExtensionSDKs/ZXingWinRT/1.0.0.0
|
||||
|
||||
# - name: Build
|
||||
# run: cmake --build build -j8 --config Release
|
||||
|
||||
# - uses: actions/upload-artifact@v6
|
||||
# with:
|
||||
# name: winrt-ARM64-artifacts
|
||||
# path: build/dist
|
||||
|
||||
build-tarball:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Build
|
||||
run: python .github/create_release_archive.py
|
||||
|
||||
- name: Find generated archives
|
||||
id: find
|
||||
run: |
|
||||
tgz=$(ls zxing-cpp-*.tar.gz 2>/dev/null | head -n1 || true)
|
||||
zip=$(ls zxing-cpp-*.zip 2>/dev/null | head -n1 || true)
|
||||
echo "tgz=$tgz" >> $GITHUB_OUTPUT
|
||||
echo "zip=$zip" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload source_artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: source_artifacts
|
||||
path: |
|
||||
${{ steps.find.outputs.tgz }}
|
||||
${{ steps.find.outputs.zip }}
|
||||
@@ -0,0 +1,59 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: CodeQL
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# push:
|
||||
# branches: [ master ]
|
||||
# pull_request:
|
||||
# # The branches below must be a subset of the branches above
|
||||
# branches: [ master ]
|
||||
# schedule:
|
||||
# - cron: '18 21 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'cpp' ] # , 'java-kotlin', 'swift' currently fail with autobuild, 'python' is not worth the extra build time
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: manual
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DZXING_EXPERIMENTAL_API=ON
|
||||
cmake --build build -j
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
@@ -0,0 +1,71 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
#
|
||||
# Find more information at:
|
||||
# https://github.com/microsoft/msvc-code-analysis-action
|
||||
|
||||
name: Microsoft C++ Code Analysis
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
# on:
|
||||
# push:
|
||||
# branches: [ "master" ]
|
||||
# pull_request:
|
||||
# branches: [ "master" ]
|
||||
# schedule:
|
||||
# - cron: '34 6 * * 1'
|
||||
|
||||
env:
|
||||
# Path to the CMake build directory.
|
||||
build: '${{ github.workspace }}/build'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
|
||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||
name: Analyze
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{ env.build }} -DZXING_EXPERIMENTAL_API=ON -DZXING_DISABLE_PCH=ON
|
||||
|
||||
# Build is not required unless generated source files are used
|
||||
# - name: Build CMake
|
||||
# run: cmake --build ${{ env.build }}
|
||||
|
||||
- name: Initialize MSVC Code Analysis
|
||||
uses: microsoft/msvc-code-analysis-action@v0.1.1
|
||||
# Provide a unique ID to access the sarif output path
|
||||
id: run-analysis
|
||||
with:
|
||||
cmakeBuildDirectory: ${{ env.build }}
|
||||
# Ruleset file that will determine what checks will be run
|
||||
ruleset: NativeRecommendedRules.ruleset
|
||||
|
||||
# Upload SARIF file to GitHub Code Scanning Alerts
|
||||
# this currently fails with the Error: The CodeQL Action does not support uploading multiple SARIF runs with the same category.
|
||||
# - name: Upload SARIF to GitHub
|
||||
# uses: github/codeql-action/upload-sarif@v4
|
||||
# with:
|
||||
# sarif_file: ${{ steps.run-analysis.outputs.sarif }}
|
||||
|
||||
# Upload SARIF file as an Artifact to download and view
|
||||
- name: Upload SARIF as an Artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: sarif-file
|
||||
path: ${{ steps.run-analysis.outputs.sarif }}
|
||||
@@ -0,0 +1,41 @@
|
||||
name: publish-android
|
||||
|
||||
on:
|
||||
# release:
|
||||
# types: [published]
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: 'Publish package (y/n)'
|
||||
default: 'n'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
# working-directory: wrappers/android
|
||||
# run: ./gradlew versionDisplay
|
||||
|
||||
- name: Build Library
|
||||
working-directory: wrappers/android
|
||||
run: ./gradlew assembleRelease
|
||||
|
||||
- name: Publish Library
|
||||
if: ${{ github.event.inputs.publish == 'y' }}
|
||||
working-directory: wrappers/android
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALUSERNAME }}
|
||||
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALPASSWORD }}
|
||||
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY }}
|
||||
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.GPG_KEY_ID }}
|
||||
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_KEY_PASSWORD }}
|
||||
run: ./gradlew publishToMavenCentral --no-configuration-cache
|
||||
|
||||
|
||||
# To actually publish the the blob, login to https://central.sonatype.com/publishing/deployments.
|
||||
@@ -0,0 +1,130 @@
|
||||
name: publish-dotnet
|
||||
|
||||
on:
|
||||
# release:
|
||||
# types: [published]
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: 'Publish package (y/n)'
|
||||
default: 'n'
|
||||
|
||||
jobs:
|
||||
build-native:
|
||||
name: build-native ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
lib_name: ZXing.dll
|
||||
rid: win-x64
|
||||
lib_path: build/core/Release/ZXing.dll
|
||||
- os: windows-11-arm
|
||||
lib_name: ZXing.dll
|
||||
rid: win-arm64
|
||||
lib_path: build/core/Release/ZXing.dll
|
||||
- os: ubuntu-latest
|
||||
lib_name: libZXing.so
|
||||
rid: linux-x64
|
||||
lib_path: build/core/libZXing.so
|
||||
- os: ubuntu-24.04-arm
|
||||
lib_name: libZXing.so
|
||||
rid: linux-arm64
|
||||
lib_path: build/core/libZXing.so
|
||||
- os: macos-15-intel
|
||||
lib_name: libZXing.dylib
|
||||
rid: osx-x64
|
||||
lib_path: build/core/libZXing.dylib
|
||||
- os: macos-latest
|
||||
lib_name: libZXing.dylib
|
||||
rid: osx-arm64
|
||||
lib_path: build/core/libZXing.dylib
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Configure CMake
|
||||
run: >
|
||||
cmake -S . -B build
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DBUILD_SHARED_LIBS=ON
|
||||
-DZXING_EXAMPLES=OFF
|
||||
-DZXING_TEST_DOTNET=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --config Release --parallel
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build -V -C Release -R DotNetTest
|
||||
|
||||
- name: Stage native library
|
||||
run: |
|
||||
cmake -E make_directory artifact/runtimes/${{ matrix.rid }}/native
|
||||
cmake -E copy_if_different ${{ matrix.lib_path }} artifact/runtimes/${{ matrix.rid }}/native/${{ matrix.lib_name }}
|
||||
|
||||
- name: Upload native library
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: native-${{ matrix.rid }}
|
||||
path: artifact
|
||||
|
||||
publish-nuget:
|
||||
needs: build-native
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Download native libraries
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: native-*
|
||||
path: wrappers/dotnet
|
||||
merge-multiple: true
|
||||
|
||||
- name: Validate runtime files
|
||||
shell: bash
|
||||
run: |
|
||||
test -f wrappers/dotnet/runtimes/win-x64/native/ZXing.dll
|
||||
test -f wrappers/dotnet/runtimes/win-arm64/native/ZXing.dll
|
||||
test -f wrappers/dotnet/runtimes/linux-x64/native/libZXing.so
|
||||
test -f wrappers/dotnet/runtimes/linux-arm64/native/libZXing.so
|
||||
test -f wrappers/dotnet/runtimes/osx-x64/native/libZXing.dylib
|
||||
test -f wrappers/dotnet/runtimes/osx-arm64/native/libZXing.dylib
|
||||
|
||||
- name: Pack NuGet package
|
||||
run: dotnet pack wrappers/dotnet/ZXingCpp/ZXingCpp.csproj --configuration Release --output wrappers/dotnet/artifacts
|
||||
|
||||
- name: Smoke test local NuGet package
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pkg_file="$(ls wrappers/dotnet/artifacts/ZXingCpp.*.nupkg | head -n 1)"
|
||||
pkg_version="$(basename "$pkg_file" | sed -E 's/^ZXingCpp\.//; s/\.nupkg$//')"
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
dotnet new console -n ZxingPkgTest -o "$tmp_dir/ZxingPkgTest"
|
||||
echo "Console.WriteLine(global::ZXingCpp.BarcodeFormat.QRCode.ToString());" > "$tmp_dir/ZxingPkgTest/Program.cs"
|
||||
|
||||
dotnet add "$tmp_dir/ZxingPkgTest/ZxingPkgTest.csproj" package ZXingCpp --version "$pkg_version" --no-restore
|
||||
dotnet restore "$tmp_dir/ZxingPkgTest/ZxingPkgTest.csproj" \
|
||||
--source "${{ github.workspace }}/wrappers/dotnet/artifacts" \
|
||||
--source https://api.nuget.org/v3/index.json
|
||||
dotnet run --project "$tmp_dir/ZxingPkgTest/ZxingPkgTest.csproj" --no-restore
|
||||
|
||||
- name: Upload NuGet package artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: dotnet-nuget-package
|
||||
path: wrappers/dotnet/artifacts/*.nupkg
|
||||
|
||||
- name: Push NuGet package
|
||||
if: ${{ github.event_name == 'release' || github.event.inputs.publish == 'y' }}
|
||||
run: dotnet nuget push wrappers/dotnet/artifacts/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate
|
||||
@@ -0,0 +1,131 @@
|
||||
name: publish-gh-pages
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
# push:
|
||||
# branches: ["master"]
|
||||
|
||||
# Runs on new releases
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-wasm:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup emsdk
|
||||
uses: emscripten-core/setup-emsdk@v15
|
||||
# with:
|
||||
# # Make sure to set a version number!
|
||||
# version: 1.38.40
|
||||
# # This is the name of the cache folder.
|
||||
# # The cache folder will be placed in the build directory,
|
||||
# # so make sure it doesn't conflict with anything!
|
||||
# actions-cache-folder: 'emsdk-cache'
|
||||
|
||||
- name: Configure
|
||||
run: emcmake cmake -Swrappers/wasm -Bbuild
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --parallel
|
||||
|
||||
- name: Prepare Archive
|
||||
shell: sh
|
||||
run: |
|
||||
mkdir -p public/demo
|
||||
mv build/zxing_* build/*.html public/demo/
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: 'public'
|
||||
name: 'artifact-wasm'
|
||||
|
||||
build-docs:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install doxygen
|
||||
|
||||
- name: Configure
|
||||
run: cmake -S . -B build
|
||||
|
||||
- name: Build docs
|
||||
run: cmake --build build --target docs
|
||||
|
||||
- name: Prepare deploy folder
|
||||
run: |
|
||||
mkdir -p public/docs
|
||||
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
mkdir -p public/docs/$VERSION
|
||||
cp -r build/docs/html/* public/docs/$VERSION/
|
||||
else
|
||||
mkdir -p public/docs/latest
|
||||
cp -r build/docs/html/* public/docs/latest/
|
||||
fi
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: public
|
||||
name: 'artifact-docs'
|
||||
|
||||
deploy:
|
||||
needs: [build-wasm, build-docs]
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Download WASM artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: artifact-wasm
|
||||
path: wasm
|
||||
|
||||
- name: Download Docs artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: artifact-docs
|
||||
path: docs
|
||||
|
||||
- name: Merge artifacts
|
||||
run: |
|
||||
mkdir public
|
||||
cp -r wasm/* public/
|
||||
cp -r docs/* public/
|
||||
|
||||
- name: Upload merged artifact
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: public
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v5
|
||||
@@ -0,0 +1,97 @@
|
||||
name: publish-kn
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: 'Publish package (y/n)'
|
||||
default: 'n'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Library Publish
|
||||
|
||||
runs-on: macos-latest # at least macos-13 is required to enable c++20 support
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./wrappers/kn
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Prepare Zig toolchain
|
||||
uses: mlugg/setup-zig@v2
|
||||
with:
|
||||
version: 0.13.0
|
||||
|
||||
- name: Checkout toolchain initializer repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ISNing/kn-toolchain-initializer
|
||||
path: wrappers/kn/.kn-toolchain-initializer
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/actions/wrapper-validation@v5
|
||||
|
||||
- name: Initialize Kotlin/Native toolchain
|
||||
working-directory: wrappers/kn/.kn-toolchain-initializer
|
||||
run: ./gradlew build -DkotlinVersion=2.2.0
|
||||
|
||||
- name: Export Toolchain properties
|
||||
run: |
|
||||
echo -e "konan.dir=$HOME/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.0" >> local.properties
|
||||
|
||||
- name: Build Library
|
||||
run: |
|
||||
export PATH="$PATH:$(brew --prefix llvm@15)/bin"
|
||||
./gradlew assemble
|
||||
|
||||
- name: Run All Library Tests
|
||||
run: |
|
||||
export PATH="$PATH:$(brew --prefix llvm@15)/bin"
|
||||
./gradlew allTests
|
||||
|
||||
- name: Upload Library Test Reports
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: allTests-reports
|
||||
path: wrappers/kn/build/reports/tests/allTests
|
||||
|
||||
- name: Upload Built LinuxArm64 shared library
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: linuxArm64-shared
|
||||
path: wrappers/kn/build/cmake/libZXing/linuxArm64
|
||||
|
||||
- name: Upload Built LinuxX64 shared library
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: linuxX64-shared
|
||||
path: wrappers/kn/build/cmake/libZXing/linuxX64
|
||||
|
||||
- name: Publish Library
|
||||
if: ${{ github.event.inputs.publish == 'y' }}
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALUSERNAME }}
|
||||
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.ORG_GRADLE_PROJECT_MAVENCENTRALPASSWORD }}
|
||||
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY }}
|
||||
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.GPG_KEY_ID }}
|
||||
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_KEY_PASSWORD }}
|
||||
run: |
|
||||
export PATH="$PATH:$(brew --prefix llvm@15)/bin"
|
||||
./gradlew publishToMavenCentral
|
||||
|
||||
# To actually publish the blob, login to https://central.sonatype.com/publishing/deployments.
|
||||
@@ -0,0 +1,100 @@
|
||||
name: publish-python
|
||||
|
||||
on:
|
||||
# release:
|
||||
# types: [published]
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: 'Publish package (y/n)'
|
||||
default: 'n'
|
||||
# push:
|
||||
# branches: [master]
|
||||
# tags: ["v*.*.*"]
|
||||
# pull_request:
|
||||
# branches: [master]
|
||||
|
||||
jobs:
|
||||
build-wheels:
|
||||
name: Build wheels on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest, macos-15-intel, ubuntu-24.04-arm, windows-11-arm]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install cibuildwheel
|
||||
run: python3 -m pip install cibuildwheel==3.3.0
|
||||
|
||||
- name: Build wheels
|
||||
run: python3 -m cibuildwheel --output-dir wheelhouse wrappers/python
|
||||
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: cibw-wheels-${{ matrix.os }}
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
build-sdist:
|
||||
name: Build source distribution
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: wrappers/python
|
||||
run: python -m pip install --upgrade build
|
||||
|
||||
- name: Build sdist
|
||||
working-directory: wrappers/python
|
||||
run: python3 -m build --sdist
|
||||
|
||||
- name: Upload sdist
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: cibw-sdist
|
||||
path: wrappers/python/dist/*.tar.gz
|
||||
|
||||
upload-pypi:
|
||||
name: Upload to PyPI
|
||||
needs: [build-wheels, build-sdist]
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/zxing-cpp
|
||||
permissions:
|
||||
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
||||
# only run if the commit is tagged...
|
||||
# if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
||||
if: github.event_name == 'release' || github.event.inputs.publish == 'y'
|
||||
steps:
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: cibw-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
|
||||
- name: List wheels
|
||||
run: ls dist
|
||||
|
||||
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||
# with:
|
||||
# repository-url: https://test.pypi.org/legacy/
|
||||
@@ -0,0 +1,39 @@
|
||||
name: publish-source
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish-source:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Create source archives
|
||||
run: python .github/create_release_archive.py
|
||||
|
||||
- name: Create test samples tarball
|
||||
run: tar -czf test_samples.tar.gz test/samples
|
||||
|
||||
- name: Upload workflow artifact
|
||||
if: github.event_name != 'release'
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: source-archives
|
||||
path: |
|
||||
*.zip
|
||||
*.gz
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload to release
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
tag='${{ github.event.release.tag_name }}'
|
||||
version="${tag#v}"
|
||||
gh release upload --clobber "$tag" "$GITHUB_WORKSPACE/zxing-cpp-$version.tar.gz" "$GITHUB_WORKSPACE/zxing-cpp-$version.zip" "$GITHUB_WORKSPACE/test_samples.tar.gz"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,82 @@
|
||||
name: publish-winrt
|
||||
on:
|
||||
# release:
|
||||
# types: [published]
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: 'Publish package (y/n)'
|
||||
default: 'n'
|
||||
package_version:
|
||||
description: 'Package version (major.minor.patch)'
|
||||
default: '1.0.0'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [Win32, x64, ARM64]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Create build environment
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake -E make_directory ${{runner.workspace}}/build
|
||||
cmake -E copy_directory ${{github.workspace}}/wrappers/winrt/UAP ${{runner.workspace}}/build/dist/UAP
|
||||
cmake -E copy_directory ${{github.workspace}}/wrappers/winrt/nuget ${{runner.workspace}}/build/dist/nuget
|
||||
|
||||
- name: Configure CMake
|
||||
shell: cmd
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake ${{github.workspace}}/wrappers/winrt -A ${{matrix.target}} -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_BUILD_TYPE=Release -DBUILD_WINRT_LIB=ON -DZXING_EXAMPLES=OFF -DZXING_BLACKBOX_TESTS=OFF -DEXTENSION_SDK_OUTPUT=dist/UAP/v0.8.0.0/ExtensionSDKs/ZXingWinRT/1.0.0.0
|
||||
|
||||
- name: Build
|
||||
shell: cmd
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake --build . -j8 --config Release
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: winrt-${{matrix.target}}-artifacts
|
||||
path: ${{runner.workspace}}/build/dist
|
||||
|
||||
publish-package:
|
||||
needs: build
|
||||
runs-on: windows-latest
|
||||
if: ${{ github.event_name == 'release' || github.event.inputs.publish == 'y' }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: winrt-Win32-artifacts
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: winrt-x64-artifacts
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: winrt-ARM64-artifacts
|
||||
|
||||
- name: Create NuGet package
|
||||
if: ${{ github.event_name != 'release' }}
|
||||
shell: cmd
|
||||
run: nuget pack -Version ${{ github.event.inputs.package_version }} -OutputFileNamesWithoutVersion nuget/ZXingWinRT.nuspec
|
||||
|
||||
- name: Create NuGet package
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
shell: bash
|
||||
env:
|
||||
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
run: nuget pack -Version ${RELEASE_TAG_NAME:1} -OutputFileNamesWithoutVersion nuget/ZXingWinRT.nuspec
|
||||
|
||||
- name: Publish NuGet package
|
||||
shell: cmd
|
||||
run: nuget push huycn.zxingcpp.winrt.nupkg -ApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: nuget-package
|
||||
path: huycn.zxingcpp.winrt.nupkg
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
*.o
|
||||
*.so
|
||||
*.lib
|
||||
*.d
|
||||
*.a
|
||||
compile_commands.json
|
||||
/build
|
||||
|
||||
# QtCreator
|
||||
CMakeLists.txt.user
|
||||
|
||||
# Vim
|
||||
*.swp
|
||||
|
||||
# XCode/Swift
|
||||
.swiftpm
|
||||
.build
|
||||
|
||||
# JetBrain (AndroidStudio, clion)
|
||||
.idea
|
||||
|
||||
# Visual Studio
|
||||
/.vs
|
||||
/CMakeUserPresets.json
|
||||
|
||||
# Visual Studio Code
|
||||
/.vscode
|
||||
@@ -0,0 +1,4 @@
|
||||
[submodule "zint"]
|
||||
path = zint
|
||||
url = https://github.com/zint/zint.git
|
||||
shallow = true
|
||||
@@ -0,0 +1,121 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(ZXing)
|
||||
|
||||
option (ZXING_READERS "Build with reader support (decoders)" ON)
|
||||
set (ZXING_WRITERS "ON" CACHE STRING "Build with old and/or new writer (encoder) backend (OFF/ON/OLD/NEW/BOTH)")
|
||||
option (ZXING_C_API "Build the C-API" ON)
|
||||
option (ZXING_EXPERIMENTAL_API "Build with experimental API" OFF)
|
||||
option (ZXING_EXAMPLES "Build the example barcode reader/writer applications" ON)
|
||||
option (ZXING_EXAMPLES_QT "Build the Qt based example barcode reader/writer applications" OFF)
|
||||
option (ZXING_BLACKBOX_TESTS "Build the black box reader/writer tests" OFF)
|
||||
option (ZXING_UNIT_TESTS "Build the unit tests (don't enable for production builds)" OFF)
|
||||
option (ZXING_TEST_DOTNET "Test if the .NET wrapper works" OFF)
|
||||
option (ZXING_TEST_GO "Test if the Go wrapper works" OFF)
|
||||
option (ZXING_TEST_INSTALL "Test if the installed/public API works" OFF)
|
||||
option (ZXING_PYTHON_MODULE "Build the python module" OFF)
|
||||
set (ZXING_DEPENDENCIES "AUTO" CACHE STRING "Fetch from github or use locally installed (AUTO/GITHUB/LOCAL)")
|
||||
option (ZXING_CLANG_TIDY "Enable clang-tidy analysis" OFF)
|
||||
|
||||
if (WIN32)
|
||||
option (BUILD_SHARED_LIBS "Build and link as shared library" OFF)
|
||||
else()
|
||||
option (BUILD_SHARED_LIBS "Build and link as shared library" ON)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
add_definitions (-DUNICODE -D_UNICODE -D_CRT_SECURE_NO_WARNINGS)
|
||||
option (ZXING_LINK_CPP_STATICALLY "MSVC only, link standard library statically (/MT and /MTd)" OFF)
|
||||
if (LINK_CPP_STATICALLY OR ZXING_LINK_CPP_STATICALLY)
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
set (DEFAULT_BUILD_TYPE "Release")
|
||||
message (STATUS "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.")
|
||||
set (CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "Choose the type of build." FORCE)
|
||||
set_property (CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
|
||||
endif()
|
||||
|
||||
if (NOT (ZXING_READERS OR ZXING_WRITERS))
|
||||
message(FATAL_ERROR "At least one of ZXING_READERS/ZXING_WRITERS must be enabled.")
|
||||
endif()
|
||||
|
||||
set(ZXING_WRITERS_LIST OFF ON OLD NEW BOTH)
|
||||
set_property(CACHE ZXING_WRITERS PROPERTY STRINGS ${ZXING_WRITERS_LIST})
|
||||
if(NOT ZXING_WRITERS IN_LIST ZXING_WRITERS_LIST)
|
||||
message(FATAL_ERROR "ZXING_WRITERS must be one of ${ZXING_WRITERS_LIST}")
|
||||
endif()
|
||||
|
||||
set(ZXING_DEPENDENCIES_LIST AUTO GITHUB LOCAL)
|
||||
set_property(CACHE ZXING_DEPENDENCIES PROPERTY STRINGS ${ZXING_DEPENDENCIES_LIST})
|
||||
if(NOT ZXING_DEPENDENCIES IN_LIST ZXING_DEPENDENCIES_LIST)
|
||||
message(FATAL_ERROR "ZXING_DEPENDENCIES must be one of ${ZXING_DEPENDENCIES_LIST}")
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED CMAKE_CXX_STANDARD)
|
||||
set (CMAKE_CXX_STANDARD 20)
|
||||
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set (CMAKE_CXX_EXTENSIONS OFF)
|
||||
endif()
|
||||
|
||||
set(CMAKE_COLOR_DIAGNOSTICS ON)
|
||||
|
||||
if (ZXING_CLANG_TIDY)
|
||||
find_program(CMAKE_CXX_CLANG_TIDY NAMES "clang-tidy" DOC "Path to clang-tidy executable")
|
||||
if (NOT CMAKE_CXX_CLANG_TIDY)
|
||||
message(WARNING "clang-tidy not found.")
|
||||
else()
|
||||
message(STATUS "clang-tidy found: ${CMAKE_CXX_CLANG_TIDY}")
|
||||
set(ZXING_DISABLE_PCH ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# make installed binaries relocatable on macOS and Linux
|
||||
set(CMAKE_BUILD_RPATH_USE_ORIGIN ON) # improve build reproducibility
|
||||
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
|
||||
set(CMAKE_INSTALL_RPATH "$<$<PLATFORM_ID:Darwin>:@loader_path/../lib>$<$<PLATFORM_ID:Linux>:$ORIGIN/../lib>")
|
||||
|
||||
add_subdirectory (core)
|
||||
add_subdirectory (docs)
|
||||
|
||||
enable_testing()
|
||||
|
||||
include(zxing.cmake)
|
||||
|
||||
if (ZXING_EXAMPLES)
|
||||
add_subdirectory (example)
|
||||
endif()
|
||||
if (ZXING_EXAMPLES_QT)
|
||||
add_subdirectory (wrappers/qt)
|
||||
endif()
|
||||
if (ZXING_BLACKBOX_TESTS)
|
||||
add_subdirectory (test/blackbox)
|
||||
endif()
|
||||
if (ZXING_UNIT_TESTS)
|
||||
add_subdirectory (test/unit)
|
||||
endif()
|
||||
if (ZXING_TEST_DOTNET)
|
||||
add_subdirectory (wrappers/dotnet)
|
||||
endif()
|
||||
if (ZXING_TEST_GO)
|
||||
add_subdirectory (wrappers/go)
|
||||
endif()
|
||||
if (ZXING_TEST_INSTALL)
|
||||
add_test(
|
||||
NAME InstallTest
|
||||
COMMAND
|
||||
${CMAKE_COMMAND}
|
||||
-DPROJECT_BINARY_DIR=${CMAKE_BINARY_DIR}
|
||||
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}/test/install
|
||||
-DZXING_WRITERS=${ZXING_WRITERS}
|
||||
-P ${CMAKE_SOURCE_DIR}/test/install/run.cmake
|
||||
)
|
||||
endif()
|
||||
if (ZXING_PYTHON_MODULE)
|
||||
add_subdirectory (wrappers/python)
|
||||
endif()
|
||||
if (ZXING_C_API)
|
||||
add_subdirectory (wrappers/c)
|
||||
endif()
|
||||
@@ -0,0 +1,202 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// swift-tools-version:5.7.1
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "ZXingCpp",
|
||||
platforms: [
|
||||
.macOS(.v13), .iOS(.v12)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "ZXingCpp",
|
||||
targets: ["ZXingCpp"])
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "ZXingCppCore",
|
||||
path: "core/src",
|
||||
exclude: ["libzint", "ZXingC.cpp", "ZXingCpp.cpp"],
|
||||
publicHeadersPath: ".",
|
||||
cxxSettings: [
|
||||
.headerSearchPath("../../wrappers/ios/Sources/Wrapper"),
|
||||
.define("ZXING_INTERNAL")
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "ZXingCpp",
|
||||
dependencies: ["ZXingCppCore"],
|
||||
path: "wrappers/ios/Sources/Wrapper",
|
||||
publicHeadersPath: ".",
|
||||
linkerSettings: [
|
||||
.linkedFramework("CoreGraphics"),
|
||||
.linkedFramework("CoreImage"),
|
||||
.linkedFramework("CoreVideo")
|
||||
]
|
||||
)
|
||||
],
|
||||
cxxLanguageStandard: CXXLanguageStandard.cxx20
|
||||
)
|
||||
@@ -0,0 +1,154 @@
|
||||
[](https://github.com/zxing-cpp/zxing-cpp/actions?query=workflow%3ACI)
|
||||
|
||||
# ZXing-C++
|
||||
|
||||
ZXing-C++ ("zebra crossing") is an open-source, multi-format linear/matrix barcode image processing library implemented in C++.
|
||||
|
||||
It was originally ported from the Java [ZXing library](https://github.com/zxing/zxing) but has been developed further and now includes many improvements in terms of runtime and detection performance. It can both read and write barcodes in a number of formats. Since version 3.0 the default writing backend is provided by the [zint library](https://sourceforge.net/projects/zint/).
|
||||
|
||||
## Features
|
||||
|
||||
* Written in pure C++20 (public API is C++17 compatible), no third-party dependencies (for the library itself)
|
||||
* Thread safe
|
||||
* Wrappers/Bindings for:
|
||||
|
||||
| | | |
|
||||
|:-|:-|:-|
|
||||
| [Android](wrappers/android/README.md) | [C](wrappers/c/README.md) | [Go](wrappers/go/README.md) |
|
||||
| [iOS](wrappers/ios/README.md) | [Kotlin/Native](wrappers/kn/README.md) | [.NET](wrappers/dotnet/README.md) |
|
||||
| [Python](wrappers/python/README.md) | [Qt](wrappers/qt/README.md) | [Rust](wrappers/rust/README.md) |
|
||||
| [WebAssembly](wrappers/wasm/README.md) | [WinRT](wrappers/winrt/README.md) | |
|
||||
| External projects: |
|
||||
| [Flutter](https://pub.dev/packages/flutter_zxing) | [Julia](https://github.com/rakeshksr/ZXingCPP.jl) | |
|
||||
|
||||
|
||||
## Supported Formats
|
||||
|
||||
| Symbology | Variants |
|
||||
|:----------|:---------|
|
||||
| ***Retail:*** | *(Point-of-Sale, Coupons)*
|
||||
| EAN/UPC | EAN-13, EAN-8, EAN-5ᵂ, EAN-2ᵂ, UPC-A, UPC-E, ISBN
|
||||
| DataBar | Omnidirectional, Stacked, Limited, Expanded, Expanded Stacked
|
||||
| ***Industrial:*** | *(Logistics, Tracking, Pharma)*
|
||||
| Code39 | Standard, Extended, PZN, Code32, (VIN, LOGMARS)
|
||||
| Code93 |
|
||||
| Code128 |
|
||||
| ITF | ITF-14, (DHL Leitcode, DHL Identcode)
|
||||
| Telepen | Telepen Alpha, Telepen Numeric
|
||||
| ***Matrix:*** | *(Documents, Tickets, Logistics, IDs)*
|
||||
| Aztec Code | Aztec Code, Aztec Rune
|
||||
| Data Matrix | ECC200
|
||||
| MaxiCode | (partial read support)
|
||||
| PDF417 | PDF417, Compact PDF417, MicroPDF417
|
||||
| QR Code | Model 1ᴿ, Model 2, Micro QR Code, rMQR
|
||||
| ***Other:*** | *(Legacy, Niche)*
|
||||
| Codabar |
|
||||
| DXFilmEdge |
|
||||
|
||||
|
||||
[Note:]
|
||||
* ᵂ : write support only
|
||||
* ᴿ : read support only
|
||||
* DataBar used to be called RSS.
|
||||
* DataBar, DX Film Edge, MaxiCode, Micro QR Code and rMQR Code are not supported for writing if the library is configured with `ZXING_WRITERS=OLD`.
|
||||
|
||||
## Sponsors
|
||||
|
||||
You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/axxel).
|
||||
|
||||
| | Named Sponsors: |
|
||||
|:-:|:-|
|
||||
| [](https://www.alliedvision.com/) | [Allied Vision Konstanz GmbH](https://www.alliedvision.com/) |
|
||||
| [](https://github.com/jknlsn) | [Jake Nelson](https://github.com/jknlsn) |
|
||||
| [](https://github.com/kurzdigital) | [KURZ Digital Solutions GmbH & Co. KG](https://github.com/kurzdigital) |
|
||||
| [](https://github.com/moonshine-ai) | [Moonshine AI](https://github.com/moonshine-ai) |
|
||||
| [](https://www.sap.com/germany/about/company/innovation/open-source.html) | [SAP (Open Source Program Office)](https://www.sap.com/germany/about/company/innovation/open-source.html) |
|
||||
| [](https://github.com/somcosoftware) | [Somco Software](https://somcosoftware.com/en) |
|
||||
| [](https://synedra.com/) | [synedra information technologies GmbH](https://synedra.com/) |
|
||||
|
||||
Thanks a lot for your contribution!
|
||||
|
||||
## Getting Started
|
||||
|
||||
### To read barcodes:
|
||||
1. Load your image into memory (3rd-party library required).
|
||||
2. Call `ReadBarcodes()` from [`ReadBarcode.h`](core/src/ReadBarcode.h), the simplest API to get a list of `Barcode` objects.
|
||||
|
||||
A very simple example looks like this:
|
||||
```c++
|
||||
#include "ZXing/ZXingCpp.h"
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int width, height;
|
||||
unsigned char* data;
|
||||
// load your image data from somewhere. ImageFormat::Lum assumes grey scale image data.
|
||||
|
||||
auto image = ZXing::ImageView(data, width, height, ZXing::ImageFormat::Lum);
|
||||
auto options = ZXing::ReaderOptions().formats(ZXing::BarcodeFormat::QRCode);
|
||||
auto barcodes = ZXing::ReadBarcodes(image, options);
|
||||
|
||||
for (const auto& b : barcodes)
|
||||
std::cout << ZXing::ToString(b.format()) << ": " << b.text() << "\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
To see the full capability of the API, have a look at [`ZXingReader.cpp`](example/ZXingReader.cpp).
|
||||
|
||||
### To write barcodes:
|
||||
1. Create a `Barcode` object with `CreateBarcodeFrom...()` from [`CreateBarcode.h`](core/src/CreateBarcode.h).
|
||||
2. The `Barcode::symbol()` can be used to get access to the bit matrix (1 module == 1 pixel, no quiet zone)
|
||||
3. Alternatively the 3 `WriteBarcodeTo...()` functions from [`WriteBarcode.h`](core/src/WriteBarcode.h) can be used to create an `Image`, a SVG string or a UTF-8 string representation.
|
||||
|
||||
A very simple example looks like this:
|
||||
```c++
|
||||
#include "ZXing/ZXingCpp.h"
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
auto barcode = ZXing::CreateBarcodeFromText("some text", ZXing::BarcodeFormat::QRCode);
|
||||
auto svg = ZXing::WriteBarcodeToSVG(barcode);
|
||||
|
||||
// see also ZXing::WriteBarcodeToImage()
|
||||
|
||||
std::cout << svg << "\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
As an example for how to parameterize the process with `CreatorOptions` and `WriterOptions`, have a look at [`ZXingWriter.cpp`](example/ZXingWriter.cpp).
|
||||
|
||||
### Online API documentation
|
||||
|
||||
The latest API documentation can be found here: https://zxing-cpp.github.io/zxing-cpp/docs/latest
|
||||
|
||||
## Web Demos
|
||||
- [Read barcodes](https://zxing-cpp.github.io/zxing-cpp/demo/demo_reader.html)
|
||||
- [Write barcodes](https://zxing-cpp.github.io/zxing-cpp/demo/demo_writer.html)
|
||||
- [Read barcodes from camera](https://zxing-cpp.github.io/zxing-cpp/demo/demo_cam_reader.html)
|
||||
|
||||
[Note: those live demos are not necessarily fully up-to-date at all times.]
|
||||
|
||||
## Build Instructions
|
||||
These are the generic instructions to build the library on Windows/macOS/Linux. For details on how to build the individual wrappers, follow the links above.
|
||||
|
||||
1. Make sure [CMake](https://cmake.org) version 3.16 or newer is installed. The python module requires 3.18 or higher.
|
||||
2. Make sure a sufficiently C++20 compliant compiler is installed (minimum VS 2019 16.10? / gcc 11 / clang 12?).
|
||||
3. See the cmake `ZXING_...` options to enable the testing code, python wrapper, etc.
|
||||
|
||||
```
|
||||
git clone https://github.com/zxing-cpp/zxing-cpp.git --recursive --depth 1
|
||||
cmake -S zxing-cpp -B zxing-cpp/build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build zxing-cpp/build --parallel --config Release
|
||||
```
|
||||
|
||||
[Note: ready to use packages are available for/as
|
||||
[vcpkg](https://github.com/Microsoft/vcpkg/tree/master/ports/nu-book-zxing-cpp),
|
||||
[conan](https://github.com/conan-io/conan-center-index/tree/master/recipes/zxing-cpp),
|
||||
[mingw](https://github.com/msys2/MINGW-packages/tree/master/mingw-w64-zxing-cpp),
|
||||
[homebrew](https://formulae.brew.sh/formula/zxing-cpp) and a bunch of
|
||||
[linux distributions](https://repology.org/project/zxing-cpp/versions).]
|
||||
@@ -0,0 +1,29 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(ZXingDocs VERSION 3.1.0)
|
||||
|
||||
find_package(Doxygen)
|
||||
|
||||
if(DOXYGEN_FOUND)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
doxygen-awesome-css
|
||||
URL https://github.com/jothepro/doxygen-awesome-css/archive/refs/heads/main.zip
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(doxygen-awesome-css)
|
||||
|
||||
# Save the location the files were cloned into
|
||||
# This allows us to get the path to doxygen-awesome.css
|
||||
FetchContent_GetProperties(doxygen-awesome-css SOURCE_DIR AWESOME_CSS_DIR)
|
||||
|
||||
# Generate the Doxyfile
|
||||
set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in)
|
||||
set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
|
||||
configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY)
|
||||
|
||||
add_custom_target(docs
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
VERBATIM)
|
||||
endif()
|
||||
@@ -0,0 +1,58 @@
|
||||
zxing_add_package_stb()
|
||||
|
||||
option (ZXING_EXAMPLES_USE_WEBP "Add support for WebP image decoding" OFF)
|
||||
|
||||
if (ZXING_EXAMPLES_USE_WEBP)
|
||||
zxing_add_package(WebP WebP https://github.com/webmproject/libwebp.git v1.6.0)
|
||||
endif()
|
||||
|
||||
include (GNUInstallDirs)
|
||||
|
||||
if (ZXING_WRITERS AND ZXING_ENABLE_QRCODE)
|
||||
add_executable (ZXingWriter ZXingWriter.cpp)
|
||||
|
||||
target_link_libraries (ZXingWriter ZXing::ZXing stb::stb)
|
||||
|
||||
add_test(NAME ZXingWriterTest COMMAND ZXingWriter qrcode "I have the best words." test.png)
|
||||
|
||||
install(TARGETS ZXingWriter DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
if (ZXING_READERS)
|
||||
add_executable (ZXingReader ZXingReader.cpp)
|
||||
|
||||
target_link_libraries (ZXingReader ZXing::ZXing stb::stb)
|
||||
if (ZXING_EXAMPLES_USE_WEBP)
|
||||
target_link_libraries (ZXingReader ZXing::ZXing stb::stb WebP::webp)
|
||||
target_compile_definitions(ZXingReader PRIVATE ZXING_USE_WEBP)
|
||||
endif()
|
||||
|
||||
add_test(NAME ZXingReaderTest COMMAND ZXingReader -fast -format qrcode test.png) # see above
|
||||
set_tests_properties(ZXingReaderTest PROPERTIES DEPENDS ZXingWriterTest)
|
||||
|
||||
if (ZXING_EXAMPLES_USE_WEBP AND WIN32)
|
||||
add_custom_command(TARGET ZXingReader POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
$<TARGET_RUNTIME_DLLS:ZXingReader>
|
||||
$<TARGET_FILE_DIR:ZXingReader>
|
||||
COMMAND_EXPAND_LISTS
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
install(TARGETS ZXingReader DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
if (ZXING_READERS)
|
||||
find_package(OpenCV QUIET)
|
||||
if (OpenCV_FOUND)
|
||||
add_executable (ZXingOpenCV ZXingOpenCV.cpp)
|
||||
target_include_directories (ZXingOpenCV PRIVATE ${OpenCV_INCLUDE_DIRS})
|
||||
target_link_libraries (ZXingOpenCV ZXing::ZXing ${OpenCV_LIBS})
|
||||
else ()
|
||||
message("INFO: OpenCV not found, skipping ZXingOpenCV example")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_executable (ZXingBarcodeFormat ZXingBarcodeFormat.cpp)
|
||||
target_link_libraries (ZXingBarcodeFormat ZXing::ZXing)
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "BarcodeFormat.h"
|
||||
|
||||
#include <version>
|
||||
#ifdef __cpp_lib_format
|
||||
|
||||
#include "StdPrint.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
static void PrintUsage(const char* exePath)
|
||||
{
|
||||
std::print("ZXingBarcodeFormat - A command line tool to generate wrapper source code for the BarcodeFormat enum\n\n");
|
||||
std::println("Usage: {} <Android|C#|Go|K/N|Rust|Swift>", exePath);
|
||||
}
|
||||
|
||||
static void PrintBFs(
|
||||
std::format_string<std::string, int> fmt,
|
||||
std::function<std::string(const char*)> name = [](const char* name) { return std::string(name); })
|
||||
{
|
||||
std::println(fmt, name("Invalid"), 0xffff);
|
||||
#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) std::println(fmt, name(#NAME), ZX_BCF_ID(SYM, VAR));
|
||||
ZX_BCF_LIST(X)
|
||||
#undef X
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
auto is = [&, i=1](const char* str) { return strncmp(argv[i], str, strlen(argv[i])) == 0; };
|
||||
|
||||
if (argc != 2) {
|
||||
PrintUsage(argv[0]);
|
||||
return -1;
|
||||
} else if (is("Go")) {
|
||||
std::println("package zxingcpp\n");
|
||||
std::println("const (");
|
||||
PrintBFs(" BarcodeFormat{:14} BarcodeFormat = 0x{:04X}");
|
||||
std::println(")");
|
||||
} else if (is("C#")) {
|
||||
PrintBFs(" public static readonly BarcodeFormat {:15} = new BarcodeFormat(0x{:04X});");
|
||||
} else if (is("K/N")) {
|
||||
PrintBFs(" {0:15}(ZXing_BarcodeFormat.ZXing_BarcodeFormat_{0}),");
|
||||
} else if (is("Rust")) {
|
||||
PrintBFs("pub const ZXing_BarcodeFormat_{}: ZXing_BarcodeFormat = 0x{:04X};");
|
||||
} else if (is("Swift")) {
|
||||
auto swiftName = [](const char* name) {
|
||||
std::string sv(name);
|
||||
std::string ret = (char)std::tolower(sv[0]) + sv.substr(1);
|
||||
// Convert to camelCase, but keep uppercase letters if they are followed by a lowercase letter
|
||||
// (e.g. QRCode -> qrCode). This is a bit hacky, but it works for the current set of names and avoids the
|
||||
// need for a manual mapping. This ensures that the generated Swift code follows typical naming patterns.
|
||||
for (int i = 2; i < ret.size() && (std::isupper(ret[i]) || std::isdigit(ret[i])); ++i)
|
||||
ret[i - 1] = std::tolower(ret[i - 1]);
|
||||
ret.back() = std::tolower(ret.back());
|
||||
return ret == "eanupc" ? "eanUPC" : ret == "upca" ? "upcA" : ret == "upce" ? "upcE" : ret;
|
||||
};
|
||||
PrintBFs(" public static let {:15} = BarcodeFormat(rawValue: 0x{:04X})", swiftName);
|
||||
} else if (is("Android")) {
|
||||
auto androidName = [](const char* name) {
|
||||
std::string sv(name);
|
||||
std::string ret = std::string(1, (char)std::toupper(sv[0]));
|
||||
|
||||
for (size_t i = 1; i < sv.size(); ++i) {
|
||||
const int c = sv[i];
|
||||
const int prev = sv[i - 1];
|
||||
const int next = i + 1 < sv.size() ? sv[i + 1] : 0;
|
||||
if ((std::isdigit(c) != std::isdigit(prev)) || (std::isupper(c) && std::islower(prev))
|
||||
|| (std::isupper(c) && std::isupper(prev) && next != 0 && std::islower(next)))
|
||||
ret.push_back('_');
|
||||
ret.push_back(static_cast<char>(std::toupper(c)));
|
||||
}
|
||||
|
||||
return ret == "EANUPC" ? "EAN_UPC"
|
||||
: ret == "UPCA" ? "UPC_A"
|
||||
: ret == "UPCE" ? "UPC_E"
|
||||
: ret == "ALL_GS_1" ? "ALL_GS1"
|
||||
: ret;
|
||||
};
|
||||
PrintBFs("\t\t{:20}(0x{:04X}),", androidName);
|
||||
} else {
|
||||
PrintUsage(argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("This tool requires C++20 support with std::format. Please use a compatible compiler.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2021 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "ZXingOpenCV.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
int main()
|
||||
{
|
||||
namedWindow("Display window");
|
||||
|
||||
Mat image;
|
||||
VideoCapture cap(0);
|
||||
|
||||
if (!cap.isOpened())
|
||||
std::cout << "cannot open camera";
|
||||
else
|
||||
while (waitKey(25) != 27) {
|
||||
cap >> image;
|
||||
auto barcodes = ReadBarcodes(image);
|
||||
for (auto& barcode : barcodes)
|
||||
DrawBarcode(image, barcode);
|
||||
imshow("Display window", image);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ReadBarcode.h"
|
||||
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
inline ZXing::ImageView ImageViewFromMat(const cv::Mat& image)
|
||||
{
|
||||
using ZXing::ImageFormat;
|
||||
|
||||
auto fmt = ImageFormat::None;
|
||||
switch (image.channels()) {
|
||||
case 1: fmt = ImageFormat::Lum; break;
|
||||
case 2: fmt = ImageFormat::LumA; break;
|
||||
case 3: fmt = ImageFormat::BGR; break;
|
||||
case 4: fmt = ImageFormat::BGRA; break;
|
||||
}
|
||||
|
||||
if (image.depth() != CV_8U || fmt == ImageFormat::None)
|
||||
return {nullptr, 0, 0, ImageFormat::None};
|
||||
|
||||
return {image.data, image.cols, image.rows, fmt};
|
||||
}
|
||||
|
||||
inline ZXing::Barcodes ReadBarcodes(const cv::Mat& image, const ZXing::ReaderOptions& options = {})
|
||||
{
|
||||
return ZXing::ReadBarcodes(ImageViewFromMat(image), options);
|
||||
}
|
||||
|
||||
inline void DrawBarcode(cv::Mat& img, ZXing::Barcode barcode)
|
||||
{
|
||||
auto pos = barcode.position();
|
||||
auto zx2cv = [](ZXing::PointI p) { return cv::Point(p.x, p.y); };
|
||||
auto contour = std::vector<cv::Point>{zx2cv(pos[0]), zx2cv(pos[1]), zx2cv(pos[2]), zx2cv(pos[3])};
|
||||
const auto* pts = contour.data();
|
||||
int npts = contour.size();
|
||||
|
||||
cv::polylines(img, &pts, &npts, 1, true, CV_RGB(0, 255, 0));
|
||||
cv::putText(img, barcode.text(), zx2cv(pos[3]) + cv::Point(0, 20), cv::FONT_HERSHEY_DUPLEX, 0.5, CV_RGB(0, 255, 0));
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2019 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "GTIN.h"
|
||||
#include "ZXingCpp.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include <stb_image_write.h>
|
||||
|
||||
#ifdef ZXING_USE_WEBP
|
||||
#include <webp/decode.h>
|
||||
#endif
|
||||
|
||||
using namespace ZXing;
|
||||
|
||||
struct CLI
|
||||
{
|
||||
std::vector<std::string> filePaths;
|
||||
std::string outPath;
|
||||
int forceChannels = 0;
|
||||
int rotate = 0;
|
||||
bool oneLine = false;
|
||||
bool bytesOnly = false;
|
||||
bool showSymbol = false;
|
||||
bool json = false;
|
||||
};
|
||||
|
||||
static void PrintUsage(const char* exePath)
|
||||
{
|
||||
std::cout << "Usage: " << exePath << " [options] <image file>...\n"
|
||||
<< " -fast Skip some lines/pixels during detection (faster)\n"
|
||||
<< " -norotate Don't try rotated image during detection (faster)\n"
|
||||
<< " -noinvert Don't search for inverted codes during detection (faster)\n"
|
||||
<< " -noscale Don't try downscaled images during detection (faster)\n"
|
||||
<< " -formats <FORMAT[,...]>\n"
|
||||
<< " Only detect given format(s) (faster)\n"
|
||||
<< " -single Stop after the first barcode is detected (faster)\n"
|
||||
<< " -ispure Assume the image contains only a 'pure'/perfect code (faster)\n"
|
||||
<< " -errors Include barcodes with errors (like checksum error)\n"
|
||||
<< " -binarizer <local|global|fixed>\n"
|
||||
<< " Binarizer to be used for gray to binary conversion\n"
|
||||
<< " -mode <plain|eci|hri|escaped>\n"
|
||||
<< " Text mode used to render the raw byte content into text\n"
|
||||
<< " -1 Print only file name, content/error on one line per file/barcode (implies '-mode Escaped')\n"
|
||||
<< " -symbol Print the detected symbol (if available)\n"
|
||||
<< " -json Print a complete JSON formatted serialization\n"
|
||||
#ifdef ZXING_EXPERIMENTAL_API
|
||||
<< " -denoise Use extra denoiseing (closing operation)\n"
|
||||
#endif
|
||||
<< " -bytes Write (only) the bytes content of the symbol(s) to stdout\n"
|
||||
<< " -pngout <file name>\n"
|
||||
<< " Write a copy of the input image with barcodes outlined by a green line\n"
|
||||
<< " -help Print usage information\n"
|
||||
<< " -version Print version information\n"
|
||||
<< "\n"
|
||||
<< "Supported formats are (Symbology : Variants):";
|
||||
for (auto f : BarcodeFormats::list(BarcodeFormat::AllReadable)) {
|
||||
if (Symbology(f) == f)
|
||||
std::cout << "\n " << std::setw(13) << ToString(f) << " : ";
|
||||
else
|
||||
std::cout << ToString(f) << ", ";
|
||||
}
|
||||
std::cout << "\n\n";
|
||||
|
||||
std::cout << "BarcodeFormats can be lowercase, with or without any of ' -_/', separated by ',' or '|'\n";
|
||||
}
|
||||
|
||||
static bool ParseOptions(int argc, char* argv[], ReaderOptions& options, CLI& cli)
|
||||
{
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
auto is = [&](const char* str) { return strlen(argv[i]) > 1 && strncmp(argv[i], str, strlen(argv[i])) == 0; };
|
||||
if (is("-fast")) {
|
||||
options.tryHarder(false);
|
||||
} else if (is("-norotate")) {
|
||||
options.tryRotate(false);
|
||||
} else if (is("-noinvert")) {
|
||||
options.tryInvert(false);
|
||||
} else if (is("-noscale")) {
|
||||
options.tryDownscale(false);
|
||||
#ifdef ZXING_EXPERIMENTAL_API
|
||||
} else if (is("-denoise")) {
|
||||
options.tryDenoise(true);
|
||||
#endif
|
||||
} else if (is("-single")) {
|
||||
options.maxNumberOfSymbols(1);
|
||||
} else if (is("-ispure")) {
|
||||
options.isPure(true);
|
||||
options.binarizer(Binarizer::FixedThreshold);
|
||||
} else if (is("-errors")) {
|
||||
options.returnErrors(true);
|
||||
} else if (is("-formats")) {
|
||||
if (++i == argc)
|
||||
return false;
|
||||
try {
|
||||
options.formats(BarcodeFormatsFromString(argv[i]));
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << "\n\n";
|
||||
return false;
|
||||
}
|
||||
} else if (is("-binarizer")) {
|
||||
if (++i == argc)
|
||||
return false;
|
||||
else if (is("local"))
|
||||
options.binarizer(Binarizer::LocalAverage);
|
||||
else if (is("global"))
|
||||
options.binarizer(Binarizer::GlobalHistogram);
|
||||
else if (is("fixed"))
|
||||
options.binarizer(Binarizer::FixedThreshold);
|
||||
else
|
||||
return false;
|
||||
} else if (is("-mode")) {
|
||||
if (++i == argc)
|
||||
return false;
|
||||
else if (is("plain"))
|
||||
options.textMode(TextMode::Plain);
|
||||
else if (is("eci"))
|
||||
options.textMode(TextMode::ECI);
|
||||
else if (is("hri"))
|
||||
options.textMode(TextMode::HRI);
|
||||
else if (is("escaped"))
|
||||
options.textMode(TextMode::Escaped);
|
||||
else
|
||||
return false;
|
||||
} else if (is("-1")) {
|
||||
cli.oneLine = true;
|
||||
} else if (is("-bytes")) {
|
||||
cli.bytesOnly = true;
|
||||
} else if (is("-symbol")) {
|
||||
cli.showSymbol = true;
|
||||
} else if (is("-json")) {
|
||||
cli.json = true;
|
||||
} else if (is("-pngout")) {
|
||||
if (++i == argc)
|
||||
return false;
|
||||
cli.outPath = argv[i];
|
||||
} else if (is("-channels")) {
|
||||
if (++i == argc)
|
||||
return false;
|
||||
cli.forceChannels = std::stoi(argv[i]);
|
||||
} else if (is("-rotate")) {
|
||||
if (++i == argc)
|
||||
return false;
|
||||
cli.rotate = std::stoi(argv[i]);
|
||||
} else if (is("-help") || is("--help")) {
|
||||
PrintUsage(argv[0]);
|
||||
exit(0);
|
||||
} else if (is("-version") || is("--version")) {
|
||||
std::cout << "ZXingReader version " << Version() << "\n";
|
||||
exit(0);
|
||||
} else {
|
||||
cli.filePaths.push_back(argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return !cli.filePaths.empty();
|
||||
}
|
||||
|
||||
void drawLine(const ImageView& iv, PointI a, PointI b, bool error)
|
||||
{
|
||||
int steps = maxAbsComponent(b - a);
|
||||
PointF dir = bresenhamDirection(PointF(b - a));
|
||||
int R = RedIndex(iv.format()), G = GreenIndex(iv.format()), B = BlueIndex(iv.format());
|
||||
for (int i = 0; i < steps; ++i) {
|
||||
auto p = PointI(centered(a + i * dir));
|
||||
auto* dst = const_cast<uint8_t*>(iv.data(p.x, p.y));
|
||||
if (dst < iv.data(0, 0) || dst > iv.data(iv.width() - 1, iv.height() - 1))
|
||||
continue;
|
||||
dst[R] = error ? 0xff : 0;
|
||||
dst[G] = error ? 0 : 0xff;
|
||||
dst[B] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void drawRect(const ImageView& image, const Position& pos, bool error)
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
drawLine(image, pos[i], pos[(i + 1) % 4], error);
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t, void (*)(void*)> LoadWebP(const std::string& filePath, int* width, int* height, int* channels)
|
||||
{
|
||||
#ifdef ZXING_USE_WEBP
|
||||
std::ifstream file(filePath, std::ios::binary);
|
||||
if (!file)
|
||||
throw std::runtime_error("Failed to read image: " + filePath + " (failed to open file)");
|
||||
|
||||
std::vector<uint8_t> compressedData(std::istreambuf_iterator<char>(file), {});
|
||||
if (compressedData.empty())
|
||||
throw std::runtime_error("Failed to read image: " + filePath + " (empty file)");
|
||||
|
||||
*channels = 3;
|
||||
uint8_t* img = WebPDecodeRGB(compressedData.data(), compressedData.size(), width, height);
|
||||
|
||||
if (img == nullptr)
|
||||
throw std::runtime_error("Failed to read image: " + filePath + " (WebP decode failed)");
|
||||
|
||||
return std::unique_ptr<uint8_t, void (*)(void*)>(img, WebPFree);
|
||||
#else
|
||||
std::cerr << "Failed to read image: " << filePath << " (WebP support is not enabled in this build)" << "\n";
|
||||
return std::unique_ptr<uint8_t, void (*)(void*)>(nullptr, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t, void (*)(void*)> LoadStbi(const std::string& filePath, int* width, int* height, int* channels, int forceChannels)
|
||||
{
|
||||
auto buffer = std::unique_ptr<uint8_t, void (*)(void*)>(
|
||||
filePath == "-" ? stbi_load_from_file(stdin, width, height, channels, forceChannels)
|
||||
: stbi_load(filePath.c_str(), width, height, channels, forceChannels),
|
||||
stbi_image_free);
|
||||
if (buffer == nullptr)
|
||||
std::cerr << "Failed to read image: " << filePath << " (" << stbi_failure_reason() << ")" << "\n";
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool IEndsWith(std::string_view str, std::string_view suffix)
|
||||
{
|
||||
return str.size() > suffix.size() && std::equal(str.end() - suffix.size(), str.end(), suffix.begin(),
|
||||
[](char a, char b) { return std::tolower(static_cast<unsigned char>(a)) == b; });
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
ReaderOptions options;
|
||||
CLI cli;
|
||||
Barcodes allBarcodes;
|
||||
int ret = 0;
|
||||
|
||||
options.textMode(TextMode::HRI);
|
||||
options.eanAddOnSymbol(EanAddOnSymbol::Read);
|
||||
|
||||
if (!ParseOptions(argc, argv, options, cli)) {
|
||||
PrintUsage(argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::cout.setf(std::ios::boolalpha);
|
||||
|
||||
if (!cli.outPath.empty())
|
||||
cli.forceChannels = 3; // the drawing code only works for RGB data
|
||||
|
||||
for (const auto& filePath : cli.filePaths) {
|
||||
int width, height, channels;
|
||||
auto buffer = IEndsWith(filePath, ".webp")
|
||||
? LoadWebP(filePath, &width, &height, &channels)
|
||||
: LoadStbi(filePath, &width, &height, &channels, cli.forceChannels);
|
||||
if (buffer == nullptr)
|
||||
continue;
|
||||
|
||||
channels = cli.forceChannels ? cli.forceChannels : channels;
|
||||
|
||||
auto ImageFormatFromChannels = std::array{ImageFormat::None, ImageFormat::Lum, ImageFormat::LumA, ImageFormat::RGB, ImageFormat::RGBA};
|
||||
ImageView image{buffer.get(), width, height, ImageFormatFromChannels.at(channels)};
|
||||
auto barcodes = ReadBarcodes(image.rotated(cli.rotate), options);
|
||||
|
||||
// if we did not find anything, insert a dummy to produce some output for each file
|
||||
if (barcodes.empty())
|
||||
barcodes.emplace_back();
|
||||
|
||||
allBarcodes.insert(allBarcodes.end(), barcodes.begin(), barcodes.end());
|
||||
if (filePath == cli.filePaths.back()) {
|
||||
auto merged = MergeStructuredAppendSequences(allBarcodes);
|
||||
// report all merged sequences as part of the last file to make the logic not overly complicated here
|
||||
barcodes.insert(barcodes.end(), std::make_move_iterator(merged.begin()), std::make_move_iterator(merged.end()));
|
||||
}
|
||||
|
||||
for (auto&& barcode : barcodes) {
|
||||
|
||||
if (!cli.outPath.empty())
|
||||
drawRect(image, barcode.position(), bool(barcode.error()));
|
||||
|
||||
ret |= static_cast<int>(barcode.error().type());
|
||||
|
||||
if (cli.bytesOnly) {
|
||||
std::cout.write(reinterpret_cast<const char*>(barcode.bytes().data()), barcode.bytes().size());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cli.json) {
|
||||
if (barcode.format() != BarcodeFormat::None)
|
||||
std::cout << "{\"FilePath\":\"" << filePath << "\"," << barcode.extra("ALL").substr(1) << "\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cli.oneLine) {
|
||||
std::cout << filePath << " " << ToString(barcode.format());
|
||||
if (barcode.isValid())
|
||||
std::cout << " \"" << barcode.text(TextMode::Escaped) << "\"";
|
||||
else if (barcode.error())
|
||||
std::cout << " " << ToString(barcode.error());
|
||||
std::cout << "\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cli.filePaths.size() > 1 || barcodes.size() > 1) {
|
||||
static bool firstFile = true;
|
||||
if (!firstFile)
|
||||
std::cout << "\n";
|
||||
if (cli.filePaths.size() > 1)
|
||||
std::cout << "File: " << filePath << "\n";
|
||||
firstFile = false;
|
||||
}
|
||||
|
||||
if (barcode.format() == BarcodeFormat::None) {
|
||||
std::cout << "No barcode found\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
std::cout << "Text: \"" << barcode.text() << "\"\n"
|
||||
<< "Bytes: " << barcode.text(options.textMode() == TextMode::ECI ? TextMode::HexECI : TextMode::Hex) << "\n"
|
||||
<< "Format: " << ToString(barcode.format()) << "\n"
|
||||
<< "Symbology: " << ToString(barcode.symbology()) << "\n"
|
||||
<< "Identifier: " << barcode.symbologyIdentifier() << "\n"
|
||||
<< "Content: " << ToString(barcode.contentType()) << "\n"
|
||||
<< "HasECI: " << barcode.hasECI() << "\n"
|
||||
<< "Position: " << ToString(barcode.position()) << "\n"
|
||||
<< "Rotation: " << barcode.orientation() << " deg\n"
|
||||
<< "IsMirrored: " << barcode.isMirrored() << "\n"
|
||||
<< "IsInverted: " << barcode.isInverted() << "\n";
|
||||
|
||||
auto printOptional = [](const char* key, const std::string& v) {
|
||||
if (!v.empty())
|
||||
std::cout << key << v << "\n";
|
||||
};
|
||||
|
||||
printOptional("ECLevel: ", barcode.ecLevel());
|
||||
printOptional("Version: ", barcode.version());
|
||||
printOptional("Error: ", ToString(barcode.error()));
|
||||
|
||||
if (barcode.lineCount())
|
||||
std::cout << "Lines: " << barcode.lineCount() << "\n";
|
||||
|
||||
if (barcode.symbology() == BarcodeFormat::EANUPC) {
|
||||
printOptional("Country: ", GTIN::LookupCountryIdentifier(barcode.text(), barcode.format()));
|
||||
printOptional("Add-On: ", GTIN::EanAddOn(barcode));
|
||||
printOptional("Price: ", GTIN::Price(GTIN::EanAddOn(barcode)));
|
||||
printOptional("Issue #: ", GTIN::IssueNr(GTIN::EanAddOn(barcode)));
|
||||
} else if (barcode.format() == BarcodeFormat::ITF && barcode.bytes().size() == 14) {
|
||||
printOptional("Country: ", GTIN::LookupCountryIdentifier(barcode.text(), barcode.format()));
|
||||
}
|
||||
|
||||
if (barcode.isPartOfSequence())
|
||||
std::cout << "Structured Append: symbol " << barcode.sequenceIndex() + 1 << " of "
|
||||
<< barcode.sequenceSize() << " (parity/id: '" << barcode.sequenceId() << "')\n";
|
||||
else if (barcode.sequenceSize() > 0)
|
||||
std::cout << "Structured Append: merged result from " << barcode.sequenceSize() << " symbols (parity/id: '"
|
||||
<< barcode.sequenceId() << "')\n";
|
||||
|
||||
printOptional("Extra: ", barcode.extra());
|
||||
if (cli.showSymbol && barcode.symbol().data())
|
||||
std::cout << "Symbol:\n" << WriteBarcodeToUtf8(barcode);
|
||||
}
|
||||
|
||||
if (cli.filePaths.size() == 1 && !cli.outPath.empty())
|
||||
stbi_write_png(cli.outPath.c_str(), image.width(), image.height(), 3, image.data(), image.rowStride());
|
||||
|
||||
#ifdef NDEBUG
|
||||
if (getenv("MEASURE_PERF")) {
|
||||
auto startTime = std::chrono::high_resolution_clock::now();
|
||||
auto duration = startTime - startTime;
|
||||
int N = 0;
|
||||
int blockSize = 1;
|
||||
do {
|
||||
for (int i = 0; i < blockSize; ++i)
|
||||
ReadBarcodes(image, options);
|
||||
N += blockSize;
|
||||
duration = std::chrono::high_resolution_clock::now() - startTime;
|
||||
if (blockSize < 1000 && duration < std::chrono::milliseconds(100))
|
||||
blockSize *= 10;
|
||||
} while (duration < std::chrono::seconds(1));
|
||||
fprintf(stderr, "time: %5.2f ms per frame\n", double(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count()) / N);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// #define USE_OLD_WRITER_API
|
||||
|
||||
#ifndef USE_OLD_WRITER_API
|
||||
#include "CreateBarcode.h"
|
||||
#include "WriteBarcode.h"
|
||||
#else
|
||||
#include "BitMatrix.h"
|
||||
#include "BitMatrixIO.h"
|
||||
#include "CharacterSet.h"
|
||||
#include "MultiFormatWriter.h"
|
||||
#endif
|
||||
#include "Version.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include <stb_image_write.h>
|
||||
|
||||
using namespace ZXing;
|
||||
|
||||
static void PrintUsage(const char* exePath)
|
||||
{
|
||||
std::cout << "Usage: " << exePath
|
||||
<< " [-options <creator-options>] [-scale <factor>] [-binary] [-noqz] [-hrt] [-invert] <format> <text> <output>\n"
|
||||
<< " -options Comma separated list of format specific options and flags\n"
|
||||
<< " -scale module size of generated image / negative numbers mean 'target size in pixels'\n"
|
||||
<< " -rotate Rotate image by given angle (90, 180 or 270)\n"
|
||||
// << " -encoding Encoding used to encode input text\n"
|
||||
<< " -binary Interpret <text> as a file name containing binary data\n"
|
||||
<< " -noqz Print barcode without quiet zone\n"
|
||||
<< " -hrt Print human readable text below the barcode (if supported)\n"
|
||||
<< " -invert Invert colors (switch black and white)\n"
|
||||
<< " -help Print usage information\n"
|
||||
<< " -version Print version information\n"
|
||||
<< "\n"
|
||||
<< "Supported formats are (Symbology : Variants):";
|
||||
for (auto f : BarcodeFormats::list(BarcodeFormat::AllCreatable)) {
|
||||
if (Symbology(f) == f || f == BarcodeFormat::DXFilmEdge)
|
||||
std::cout << "\n " << std::setw(13) << ToString(f) << " : ";
|
||||
else
|
||||
std::cout << ToString(f) << ", ";
|
||||
}
|
||||
std::cout << "\n\n";
|
||||
|
||||
std::cout << "Format can be lowercase letters, with or without any of ' -_/'.\n"
|
||||
<< "Output format is determined by file name, supported are png, jpg and svg.\n";
|
||||
}
|
||||
|
||||
struct CLI
|
||||
{
|
||||
BarcodeFormat format = BarcodeFormat::None;
|
||||
int scale = 0;
|
||||
int rotate = 0;
|
||||
std::string input;
|
||||
std::string outPath;
|
||||
std::string options;
|
||||
bool inputIsFile = false;
|
||||
bool invert = false;
|
||||
bool addHRT = false;
|
||||
bool addQZs = true;
|
||||
bool verbose = false;
|
||||
// CharacterSet encoding = CharacterSet::Unknown;
|
||||
};
|
||||
|
||||
static bool ParseOptions(int argc, char* argv[], CLI& cli)
|
||||
{
|
||||
int nonOptArgCount = 0;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
auto is = [&](const char* str) { return strncmp(argv[i], str, strlen(argv[i])) == 0; };
|
||||
if (is("-scale")) {
|
||||
if (++i == argc)
|
||||
return false;
|
||||
cli.scale = std::stoi(argv[i]);
|
||||
} else if (is("-rotate")) {
|
||||
if (++i == argc)
|
||||
return false;
|
||||
cli.rotate = std::stoi(argv[i]);
|
||||
// } else if (is("-encoding")) {
|
||||
// if (++i == argc)
|
||||
// return false;
|
||||
// cli.encoding = CharacterSetFromString(argv[i]);
|
||||
} else if (is("-binary")) {
|
||||
cli.inputIsFile = true;
|
||||
} else if (is("-hrt")) {
|
||||
cli.addHRT = true;
|
||||
} else if (is("-noqz")) {
|
||||
cli.addQZs = false;
|
||||
} else if (is("-invert")) {
|
||||
cli.invert = true;
|
||||
} else if (is("-options")) {
|
||||
if (++i == argc)
|
||||
return false;
|
||||
cli.options = argv[i];
|
||||
} else if (is("-verbose")) {
|
||||
cli.verbose = true;
|
||||
} else if (is("-help") || is("--help")) {
|
||||
PrintUsage(argv[0]);
|
||||
exit(0);
|
||||
} else if (is("-version") || is("--version")) {
|
||||
std::cout << "ZXingWriter version " << ZXING_VERSION_STR << " (with "
|
||||
#ifdef ZXING_USE_ZINT
|
||||
<< "zint"
|
||||
#else
|
||||
<< "zxing-cpp"
|
||||
#endif
|
||||
<< " backend) \n";
|
||||
exit(0);
|
||||
} else if (nonOptArgCount == 0) {
|
||||
try {
|
||||
cli.format = BarcodeFormatFromString(argv[i]);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << "\n\n";
|
||||
return false;
|
||||
}
|
||||
++nonOptArgCount;
|
||||
} else if (nonOptArgCount == 1) {
|
||||
cli.input = argv[i];
|
||||
++nonOptArgCount;
|
||||
} else if (nonOptArgCount == 2) {
|
||||
cli.outPath = argv[i];
|
||||
++nonOptArgCount;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return nonOptArgCount == 3;
|
||||
}
|
||||
|
||||
static std::string GetExtension(const std::string& path)
|
||||
{
|
||||
auto fileNameStart = path.find_last_of("/\\");
|
||||
auto fileName = fileNameStart == std::string::npos ? path : path.substr(fileNameStart + 1);
|
||||
auto extStart = fileName.find_last_of('.');
|
||||
auto ext = extStart == std::string::npos ? "" : fileName.substr(extStart + 1);
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), [](char c) { return std::tolower(c); });
|
||||
return ext;
|
||||
}
|
||||
|
||||
template <typename T = char>
|
||||
std::vector<T> ReadFile(const std::string& fn)
|
||||
{
|
||||
std::basic_ifstream<T> ifs(fn, std::ios::binary);
|
||||
if (!ifs.good())
|
||||
throw std::runtime_error("failed to open/read file " + fn);
|
||||
return ifs ? std::vector(std::istreambuf_iterator<T>(ifs), std::istreambuf_iterator<T>()) : std::vector<T>();
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
CLI cli;
|
||||
|
||||
if (!ParseOptions(argc, argv, cli)) {
|
||||
PrintUsage(argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
#if 1
|
||||
auto cOpts = CreatorOptions(cli.format, cli.options);
|
||||
auto barcode = cli.inputIsFile ? CreateBarcodeFromBytes(ReadFile(cli.input), cOpts) : CreateBarcodeFromText(cli.input, cOpts);
|
||||
|
||||
auto wOpts = WriterOptions().scale(cli.scale).addQuietZones(cli.addQZs).addHRT(cli.addHRT).invert(cli.invert).rotate(cli.rotate);
|
||||
auto bitmap = WriteBarcodeToImage(barcode, wOpts);
|
||||
|
||||
if (cli.verbose) {
|
||||
std::cout.setf(std::ios::boolalpha);
|
||||
std::cout << "Text: \"" << barcode.text() << "\"\n"
|
||||
<< "Bytes: " << barcode.text(TextMode::Hex) << "\n"
|
||||
<< "Format: " << ToString(barcode.format()) << "\n"
|
||||
<< "Symbology: " << ToString(barcode.symbology()) << "\n"
|
||||
<< "Identifier: " << barcode.symbologyIdentifier() << "\n"
|
||||
<< "Content: " << ToString(barcode.contentType()) << "\n"
|
||||
<< "HasECI: " << barcode.hasECI() << "\n"
|
||||
<< "Position: " << ToString(barcode.position()) << "\n"
|
||||
<< "Rotation: " << barcode.orientation() << " deg\n"
|
||||
<< "IsMirrored: " << barcode.isMirrored() << "\n"
|
||||
<< "IsInverted: " << barcode.isInverted() << "\n"
|
||||
<< "ECLevel: " << barcode.ecLevel() << "\n";
|
||||
std::cout << WriteBarcodeToUtf8(barcode, wOpts);
|
||||
}
|
||||
#else // 'old' writer API (non zint based)
|
||||
auto writer = MultiFormatWriter(cli.format).setMargin(cli.addQZs ? 10 : 0);
|
||||
|
||||
BitMatrix matrix;
|
||||
if (cli.inputIsFile) {
|
||||
auto file = ReadFile(cli.input);
|
||||
std::wstring bytes;
|
||||
for (uint8_t c : file)
|
||||
bytes.push_back(c);
|
||||
writer.setEncoding(CharacterSet::BINARY);
|
||||
matrix = writer.encode(bytes, cli.scale, std::clamp(cli.scale / 2, 50, 300));
|
||||
} else {
|
||||
writer.setEncoding(CharacterSet::UTF8);
|
||||
matrix = writer.encode(cli.input, cli.scale, std::clamp(cli.scale / 2, 50, 300));
|
||||
}
|
||||
auto bitmap = ToMatrix<uint8_t>(matrix);
|
||||
#endif
|
||||
|
||||
auto ext = GetExtension(cli.outPath);
|
||||
int success = 0;
|
||||
if (ext == "" || ext == "png") {
|
||||
success = stbi_write_png(cli.outPath.c_str(), bitmap.width(), bitmap.height(), 1, bitmap.data(), 0);
|
||||
} else if (ext == "jpg" || ext == "jpeg") {
|
||||
success = stbi_write_jpg(cli.outPath.c_str(), bitmap.width(), bitmap.height(), 1, bitmap.data(), 0);
|
||||
} else if (ext == "svg") {
|
||||
#ifndef USE_OLD_WRITER_API
|
||||
success = (std::ofstream(cli.outPath) << WriteBarcodeToSVG(barcode, wOpts)).good();
|
||||
#else
|
||||
success = (std::ofstream(cli.outPath) << ToSVG(matrix)).good();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
std::cerr << "Failed to write image: " << cli.outPath << "\n";
|
||||
return -1;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << e.what() << "\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,752 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2019 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "BlackboxTestRunner.h"
|
||||
|
||||
#include "ByteArray.h"
|
||||
#include "ImageLoader.h"
|
||||
#include "ReadBarcode.h"
|
||||
#include "Utf.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
#include "StdPrint.h"
|
||||
#include "Version.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __GNUC__
|
||||
// turns out that libstdc++ is depending on TBB without even linking against it, so this fails to link :-/
|
||||
#undef __cpp_lib_execution
|
||||
#endif
|
||||
|
||||
#ifdef __cpp_lib_execution
|
||||
#include <execution>
|
||||
#else
|
||||
#include <future>
|
||||
#endif
|
||||
|
||||
namespace ZXing::Test {
|
||||
|
||||
namespace {
|
||||
|
||||
struct PureTag {} pure;
|
||||
|
||||
struct TestCase
|
||||
{
|
||||
struct TC
|
||||
{
|
||||
std::string name = {};
|
||||
int minPassCount = 0; // The number of images which must decode for the test to pass.
|
||||
int maxMisreads = 0; // Maximum number of successfully read images with the wrong contents.
|
||||
std::set<fs::path> notDetectedFiles = {};
|
||||
std::map<fs::path, std::string> misReadFiles = {};
|
||||
};
|
||||
|
||||
TC tc[2] = {};
|
||||
int rotation = 0; // The rotation in degrees clockwise to use for this test.
|
||||
|
||||
TestCase(int mntf, int mnts, int mmf, int mms, int r) : tc{{"fast", mntf, mmf}, {"slow", mnts, mms}}, rotation(r) {}
|
||||
TestCase(int mntf, int mnts, int r) : TestCase(mntf, mnts, 0, 0, r) {}
|
||||
TestCase(int mntp, int mmp, PureTag) : tc{{"pure", mntp, mmp}} {}
|
||||
};
|
||||
|
||||
struct FalsePositiveTestCase
|
||||
{
|
||||
int maxAllowed; // Maximum number of images which can fail due to successfully reading the wrong contents
|
||||
int rotation; // The rotation in degrees clockwise to use for this test.
|
||||
};
|
||||
}
|
||||
|
||||
// Helper for `compareResult()` - map `key` to Barcode property, converting value to std::string
|
||||
static std::string getBarcodeValue(const Barcode& barcode, const std::string& key)
|
||||
{
|
||||
if (key == "format")
|
||||
return ToString(barcode.format());
|
||||
if (key == "contentType")
|
||||
return ToString(barcode.contentType());
|
||||
if (key == "ecLevel")
|
||||
return barcode.ecLevel();
|
||||
if (key == "orientation")
|
||||
return std::to_string(barcode.orientation());
|
||||
if (key == "symbologyIdentifier")
|
||||
return barcode.symbologyIdentifier();
|
||||
if (key == "sequenceSize")
|
||||
return std::to_string(barcode.sequenceSize());
|
||||
if (key == "sequenceIndex")
|
||||
return std::to_string(barcode.sequenceIndex());
|
||||
if (key == "sequenceId")
|
||||
return barcode.sequenceId();
|
||||
if (key == "isLastInSequence")
|
||||
return barcode.isLastInSequence() ? "true" : "false";
|
||||
if (key == "isPartOfSequence")
|
||||
return barcode.isPartOfSequence() ? "true" : "false";
|
||||
if (key == "isMirrored")
|
||||
return barcode.isMirrored() ? "true" : "false";
|
||||
if (key == "isInverted")
|
||||
return barcode.isInverted() ? "true" : "false";
|
||||
if (key == "readerInit")
|
||||
return barcode.readerInit() ? "true" : "false";
|
||||
|
||||
return std::format("***Unknown key '{}'***", key);
|
||||
}
|
||||
|
||||
// Read ".result.txt" file contents `expected` with lines "key=value" and compare to `actual`
|
||||
static bool compareResult(const Barcode& barcode, const std::string& expected, std::string& actual)
|
||||
{
|
||||
bool ret = true;
|
||||
|
||||
actual.clear();
|
||||
actual.reserve(expected.size());
|
||||
|
||||
std::stringstream expectedLines(expected);
|
||||
std::string expectedLine;
|
||||
while (std::getline(expectedLines, expectedLine)) {
|
||||
if (expectedLine.empty() || expectedLine[0] == '#')
|
||||
continue;
|
||||
auto equals = expectedLine.find('=');
|
||||
if (equals == std::string::npos) {
|
||||
actual += "***Bad format, missing equals***\n";
|
||||
return false;
|
||||
}
|
||||
std::string key = expectedLine.substr(0, equals);
|
||||
std::string expectedValue = expectedLine.substr(equals + 1);
|
||||
std::string actualValue = getBarcodeValue(barcode, key);
|
||||
if (actualValue != expectedValue) {
|
||||
ret = false;
|
||||
actualValue += " ***Mismatch***";
|
||||
}
|
||||
actual += StrCat(key, '=', actualValue, '\n');
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static std::string checkResult(const fs::path& imgPath, BarcodeFormat expectedFormat, const Barcode& barcode)
|
||||
{
|
||||
if (expectedFormat != barcode.format() && expectedFormat != barcode.symbology()) // allow matching either format or symbology
|
||||
return std::format("Format mismatch: expected '{}' but got '{}'", ToString(expectedFormat), ToString(barcode.format()));
|
||||
|
||||
auto readFile = [imgPath](const char* ending) {
|
||||
std::ifstream ifs(fs::path(imgPath).replace_extension(ending), std::ios::binary);
|
||||
return ifs ? std::optional(std::string(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>())) : std::nullopt;
|
||||
};
|
||||
|
||||
if (auto expected = readFile(".result.txt")) {
|
||||
std::string actual;
|
||||
if (!compareResult(barcode, *expected, actual))
|
||||
return std::format("Result mismatch: expected\n{} but got\n{}", *expected, actual);
|
||||
}
|
||||
|
||||
if (auto expected = readFile(".txt")) {
|
||||
expected = EscapeNonGraphical(*expected);
|
||||
auto utf8Result = barcode.text(TextMode::Escaped);
|
||||
return utf8Result != *expected ? std::format("Content mismatch: expected '{}' but got '{}'", *expected, utf8Result) : "";
|
||||
}
|
||||
|
||||
if (auto expected = readFile(".bin")) {
|
||||
ByteArray binaryExpected(*expected);
|
||||
return barcode.bytes() != binaryExpected
|
||||
? std::format("Content mismatch: expected '{}' but got '{}'", ToHex(binaryExpected), ToHex(barcode.bytes()))
|
||||
: "";
|
||||
}
|
||||
|
||||
return "Error reading file";
|
||||
}
|
||||
|
||||
static int failed = 0;
|
||||
static int extra = 0;
|
||||
static int totalImageLoadTime = 0;
|
||||
|
||||
int timeSince(std::chrono::steady_clock::time_point startTime)
|
||||
{
|
||||
auto duration = std::chrono::steady_clock::now() - startTime;
|
||||
return narrow_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
|
||||
}
|
||||
|
||||
// pre-load images into cache, so the disc io time does not end up in the timing measurement
|
||||
void preloadImageCache(const std::vector<fs::path>& imgPaths)
|
||||
{
|
||||
auto startTime = std::chrono::steady_clock::now();
|
||||
ImageLoader::clearCache();
|
||||
for (const auto& imgPath : imgPaths)
|
||||
ImageLoader::load(imgPath);
|
||||
totalImageLoadTime += timeSince(startTime);
|
||||
}
|
||||
|
||||
static std::string printPositiveTestStats(int imageCount, const TestCase::TC& tc)
|
||||
{
|
||||
int passCount = imageCount - Size(tc.misReadFiles) - Size(tc.notDetectedFiles);
|
||||
|
||||
std::print(" | {}: {:3} of {:3}, misread {} of {}", tc.name, passCount, tc.minPassCount, Size(tc.misReadFiles), tc.maxMisreads);
|
||||
|
||||
std::string failures;
|
||||
if (passCount < tc.minPassCount && !tc.notDetectedFiles.empty()) {
|
||||
failures += std::format(" Not detected ({}):", tc.name);
|
||||
for (const auto& f : tc.notDetectedFiles)
|
||||
failures += std::format(" {}", f.filename().string());
|
||||
failures += "\n";
|
||||
failed += tc.minPassCount - passCount;
|
||||
}
|
||||
|
||||
extra += std::max(0, passCount - tc.minPassCount);
|
||||
if (passCount > tc.minPassCount)
|
||||
failures += std::format(" Unexpected detections ({}): {}\n", tc.name, passCount - tc.minPassCount);
|
||||
|
||||
if (Size(tc.misReadFiles) > tc.maxMisreads) {
|
||||
failures += std::format(" Read error ({}):", tc.name);
|
||||
for (const auto& [path, error] : tc.misReadFiles)
|
||||
failures += std::format(" {}: {}\n", path.filename().string(), error);
|
||||
failed += Size(tc.misReadFiles) - tc.maxMisreads;
|
||||
}
|
||||
return failures;
|
||||
}
|
||||
|
||||
static std::vector<fs::path> getImagesInDirectory(const fs::path& directory)
|
||||
{
|
||||
std::vector<fs::path> result;
|
||||
for (const auto& entry : fs::directory_iterator(directory))
|
||||
if (fs::is_regular_file(entry.status()) && Contains({".webp", ".png", ".jpg", ".pgm", ".gif"}, entry.path().extension()))
|
||||
result.push_back(entry.path());
|
||||
|
||||
preloadImageCache(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void doRunTests(const fs::path& directory, BarcodeFormat format, int totalTests, const std::vector<TestCase>& tests,
|
||||
ReaderOptions opts)
|
||||
{
|
||||
auto imgPaths = getImagesInDirectory(directory);
|
||||
auto folderName = directory.stem();
|
||||
|
||||
if (Size(imgPaths) != totalTests)
|
||||
std::println("TEST {} => Expected number of tests: {}, got: {} => FAILED", folderName.string(), totalTests, imgPaths.size());
|
||||
|
||||
for (auto& test : tests) {
|
||||
std::print("{:20} @ {:3}, {:3}", folderName.string(), test.rotation, Size(imgPaths));
|
||||
std::vector<int> times;
|
||||
std::string failures;
|
||||
for (auto tc : test.tc) {
|
||||
if (tc.name.empty())
|
||||
break;
|
||||
auto startTime = std::chrono::steady_clock::now();
|
||||
opts.tryDownscale(tc.name == "slow_");
|
||||
opts.downscaleFactor(2);
|
||||
opts.downscaleThreshold(180);
|
||||
opts.tryHarder(tc.name == "slow");
|
||||
opts.tryRotate(tc.name == "slow");
|
||||
opts.tryInvert(tc.name == "slow");
|
||||
opts.isPure(tc.name == "pure");
|
||||
if (opts.isPure())
|
||||
opts.binarizer(Binarizer::FixedThreshold);
|
||||
#ifndef PRINT_DEBUG
|
||||
#ifdef __cpp_lib_execution
|
||||
std::vector<Barcode> barcodes(imgPaths.size());
|
||||
std::transform(std::execution::par, imgPaths.begin(), imgPaths.end(), barcodes.begin(), [&](const fs::path& imgPath) {
|
||||
return ReadBarcode(ImageLoader::load(imgPath).rotated(test.rotation), opts);
|
||||
});
|
||||
for (size_t i = 0; i < imgPaths.size(); ++i) {
|
||||
const auto& imgPath = imgPaths[i];
|
||||
const auto& barcode = barcodes[i];
|
||||
#else
|
||||
auto futures = std::vector<std::pair<fs::path, std::future<Barcode>>>{};
|
||||
for (const auto& imgPath : imgPaths) {
|
||||
futures.push_back(std::make_pair(imgPath, std::async(std::launch::async, [&](const fs::path& path) {
|
||||
return ReadBarcode(ImageLoader::load(path).rotated(test.rotation), opts);
|
||||
}, imgPath)));
|
||||
}
|
||||
for (auto& [imgPath, fut] : futures) {
|
||||
auto barcode = fut.get();
|
||||
#endif // __cpp_lib_execution
|
||||
#else
|
||||
for (const auto& imgPath : imgPaths) {
|
||||
auto barcode = ReadBarcode(ImageLoader::load(imgPath).rotated(test.rotation), opts);
|
||||
#endif
|
||||
if (barcode.isValid()) {
|
||||
auto error = checkResult(imgPath, format, barcode);
|
||||
if (!error.empty())
|
||||
tc.misReadFiles[imgPath] = error;
|
||||
} else {
|
||||
tc.notDetectedFiles.insert(imgPath);
|
||||
}
|
||||
}
|
||||
|
||||
times.push_back(timeSince(startTime));
|
||||
failures += printPositiveTestStats(Size(imgPaths), tc);
|
||||
}
|
||||
std::println(" | time: {:3} vs {:3} ms", times.front(), times.back());
|
||||
if (!failures.empty())
|
||||
std::println("\n{}", failures);
|
||||
}
|
||||
}
|
||||
|
||||
static Barcode readMultiple(const std::vector<fs::path>& imgPaths, BarcodeFormat format)
|
||||
{
|
||||
Barcodes allBarcodes;
|
||||
for (const auto& imgPath : imgPaths) {
|
||||
auto barcodes = ReadBarcodes(ImageLoader::load(imgPath),
|
||||
ReaderOptions().formats(format).tryDownscale(false));
|
||||
allBarcodes.insert(allBarcodes.end(), barcodes.begin(), barcodes.end());
|
||||
}
|
||||
|
||||
return MergeStructuredAppendSequence(allBarcodes);
|
||||
}
|
||||
|
||||
static void doRunStructuredAppendTest(const fs::path& directory, BarcodeFormat format, int totalTests,
|
||||
const std::vector<TestCase>& tests)
|
||||
{
|
||||
auto imgPaths = getImagesInDirectory(directory);
|
||||
auto folderName = directory.stem();
|
||||
|
||||
std::map<fs::path, std::vector<fs::path>> imageGroups;
|
||||
for (const auto& imgPath : imgPaths) {
|
||||
std::string fn = imgPath.filename().string();
|
||||
auto p = fn.find_last_of('-');
|
||||
imageGroups[imgPath.parent_path() / fn.substr(0, p)].push_back(imgPath);
|
||||
}
|
||||
|
||||
if (Size(imageGroups) != totalTests)
|
||||
std::println("TEST {} => Expected number of tests: {}, got: {} => FAILED", folderName.string(), totalTests,
|
||||
imageGroups.size());
|
||||
|
||||
for (auto& test : tests) {
|
||||
std::print("{:20} @ {:3}, {:3}", folderName.string(), test.rotation, Size(imgPaths));
|
||||
auto tc = test.tc[0];
|
||||
auto startTime = std::chrono::steady_clock::now();
|
||||
|
||||
for (const auto& [testPath, testImgPaths] : imageGroups) {
|
||||
auto barcode = readMultiple(testImgPaths, format);
|
||||
if (barcode.isValid()) {
|
||||
auto error = checkResult(testPath, format, barcode);
|
||||
if (!error.empty())
|
||||
tc.misReadFiles[testPath] = error;
|
||||
} else {
|
||||
tc.notDetectedFiles.insert(testPath);
|
||||
}
|
||||
}
|
||||
|
||||
auto failures = printPositiveTestStats(Size(imageGroups), tc);
|
||||
std::println(" | time: {:3} ms", timeSince(startTime));
|
||||
if (!failures.empty())
|
||||
std::println("\n{}", failures);
|
||||
}
|
||||
}
|
||||
|
||||
int runBlackBoxTests(const fs::path& testPathPrefix, const std::set<std::string>& includedTests)
|
||||
{
|
||||
auto hasTest = [&includedTests](const fs::path& dir) {
|
||||
auto stem = dir.stem().string();
|
||||
return includedTests.empty() || Contains(includedTests, stem) ||
|
||||
Contains(includedTests, stem.substr(0, stem.size() - 2));
|
||||
};
|
||||
|
||||
auto runTests = [&](std::string_view directory, BarcodeFormat format, int total,
|
||||
const std::vector<TestCase>& tests, const ReaderOptions& opts = ReaderOptions()) {
|
||||
if (hasTest(directory) && format & (BarcodeFormat::AllReadable | BarcodeFormat::None))
|
||||
doRunTests(testPathPrefix / directory, format, total, tests, opts);
|
||||
};
|
||||
|
||||
auto runStructuredAppendTest = [&](std::string_view directory, BarcodeFormat format, int total,
|
||||
const std::vector<TestCase>& tests) {
|
||||
if (hasTest(directory) && format & (BarcodeFormat::AllReadable | BarcodeFormat::None))
|
||||
doRunStructuredAppendTest(testPathPrefix / directory, format, total, tests);
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
auto startTime = std::chrono::steady_clock::now();
|
||||
|
||||
using enum BarcodeFormat;
|
||||
// clang-format off
|
||||
|
||||
// Expected failures:
|
||||
// abc-inverted.png (fast) - fast does not try inverted
|
||||
// az-thick.png (pure)
|
||||
runTests("aztec-1", Aztec, 32, {
|
||||
{ 31, 32, 0 },
|
||||
{ 31, 32, 90 },
|
||||
{ 31, 32, 180 },
|
||||
{ 31, 32, 270 },
|
||||
{ 30, 0, pure },
|
||||
});
|
||||
|
||||
runTests("aztec-2", Aztec, 16, {
|
||||
{ 16, 16, 0 },
|
||||
{ 16, 16, 90 },
|
||||
{ 16, 16, 180 },
|
||||
{ 16, 16, 270 },
|
||||
});
|
||||
|
||||
runTests("datamatrix-1", DataMatrix, 30, {
|
||||
{ 30, 30, 0 },
|
||||
{ 0, 28, 90 },
|
||||
{ 0, 28, 180 },
|
||||
{ 0, 28, 270 },
|
||||
{ 28, 0, pure },
|
||||
});
|
||||
|
||||
runTests("datamatrix-2", DataMatrix, 13, {
|
||||
{ 13, 13, 0 },
|
||||
{ 0, 13, 90 },
|
||||
{ 0, 13, 180 },
|
||||
{ 0, 13, 270 },
|
||||
});
|
||||
|
||||
runTests("datamatrix-3", DataMatrix, 28, {
|
||||
{ 26, 27, 0 },
|
||||
{ 0, 27, 90 },
|
||||
{ 0, 27, 180 },
|
||||
{ 0, 27, 270 },
|
||||
});
|
||||
|
||||
runTests("datamatrix-4", DataMatrix, 21, {
|
||||
{ 21, 21, 0 },
|
||||
{ 0, 21, 90 },
|
||||
{ 0, 21, 180 },
|
||||
{ 0, 21, 270 },
|
||||
{ 19, 0, pure },
|
||||
});
|
||||
|
||||
runTests("dxfilmedge-1", DXFilmEdge, 3, {
|
||||
{ 1, 3, 0 },
|
||||
{ 0, 3, 180 },
|
||||
});
|
||||
|
||||
runTests("codabar-1", Codabar, 11, {
|
||||
{ 11, 11, 0 },
|
||||
{ 11, 11, 180 },
|
||||
});
|
||||
|
||||
runTests("codabar-2", Codabar, 4, {
|
||||
{ 3, 4, 0 },
|
||||
{ 3, 4, 180 },
|
||||
});
|
||||
|
||||
runTests("code39-1", Code39, 6, {
|
||||
{ 6, 6, 0 },
|
||||
{ 6, 6, 180 },
|
||||
});
|
||||
|
||||
runTests("code39-2", Code39Ext, 3, {
|
||||
{ 3, 3, 0 },
|
||||
{ 3, 3, 180 },
|
||||
});
|
||||
|
||||
runTests("code39-3", Code39, 5, {
|
||||
{ 5, 5, 0 },
|
||||
{ 5, 5, 180 },
|
||||
});
|
||||
|
||||
runTests("code93-1", Code93, 3, {
|
||||
{ 3, 3, 0 },
|
||||
{ 3, 3, 180 },
|
||||
});
|
||||
|
||||
runTests("code128-1", Code128, 6, {
|
||||
{ 6, 6, 0 },
|
||||
{ 6, 6, 180 },
|
||||
});
|
||||
|
||||
runTests("code128-2", Code128, 14, {
|
||||
{ 13, 14, 0 },
|
||||
{ 13, 14, 180 },
|
||||
});
|
||||
|
||||
runTests("code128-3", Code128, 2, {
|
||||
{ 2, 2, 0 },
|
||||
{ 2, 2, 180 },
|
||||
});
|
||||
|
||||
runTests("ean8-1", EAN8, 9, {
|
||||
{ 9, 9, 0 },
|
||||
{ 9, 9, 180 },
|
||||
{ 8, 0, pure },
|
||||
});
|
||||
|
||||
runTests("ean13-1", EAN13, 16, {
|
||||
{ 10, 14, 0 },
|
||||
{ 11, 14, 180 },
|
||||
});
|
||||
|
||||
runTests("ean13-2", EAN13, 16, {
|
||||
{ 1, 6, 0 },
|
||||
{ 1, 6, 180 },
|
||||
});
|
||||
|
||||
runTests("ean13-3", EAN13, 8, {
|
||||
{ 8, 8, 0 },
|
||||
{ 8, 8, 180 },
|
||||
});
|
||||
|
||||
runTests("ean13-4", EAN13, 20, {
|
||||
{ 8, 12, 0 },
|
||||
{ 7, 12, 180 },
|
||||
});
|
||||
|
||||
runTests("ean13-extension-1", EAN13, 5, {
|
||||
{ 4, 5, 0 },
|
||||
{ 4, 5, 180 },
|
||||
}, ReaderOptions().eanAddOnSymbol(EanAddOnSymbol::Require));
|
||||
|
||||
runTests("itf-1", ITF, 14, {
|
||||
{ 13, 14, 0 },
|
||||
{ 13, 14, 180 },
|
||||
});
|
||||
|
||||
runTests("itf-2", ITF, 6, {
|
||||
{ 6, 6, 0 },
|
||||
{ 6, 6, 180 },
|
||||
});
|
||||
|
||||
runTests("telepen-1", Telepen, 4, {
|
||||
{ 4, 4, 0 },
|
||||
{ 4, 4, 180 },
|
||||
});
|
||||
|
||||
runTests("upca-1", UPCA, 5, {
|
||||
{ 4, 5, 0 },
|
||||
{ 4, 5, 180 },
|
||||
}, ReaderOptions().formats(UPCA));
|
||||
|
||||
runTests("upca-2", UPCA, 26, {
|
||||
{ 10, 16, 0 },
|
||||
{ 12, 16, 180 },
|
||||
}, ReaderOptions().formats(UPCA));
|
||||
|
||||
runTests("upca-3", UPCA, 6, {
|
||||
{ 3, 6, 0 },
|
||||
{ 3, 6, 180 },
|
||||
}, ReaderOptions().formats(UPCA));
|
||||
|
||||
runTests("upca-4", UPCA, 13, {
|
||||
{ 5, 9, 0 },
|
||||
{ 6, 9, 180 },
|
||||
}, ReaderOptions().formats(UPCA));
|
||||
|
||||
runTests("upca-5", UPCA, 20, {
|
||||
{ 7, 9, 0 },
|
||||
{ 7, 9, 180 },
|
||||
}, ReaderOptions().formats(UPCA));
|
||||
|
||||
runTests("upca-extension-1", UPCA, 6, {
|
||||
{ 4, 5, 0 },
|
||||
{ 4, 5, 180 },
|
||||
}, ReaderOptions().eanAddOnSymbol(EanAddOnSymbol::Require).formats(UPCA));
|
||||
|
||||
runTests("upce-1", UPCE, 3, {
|
||||
{ 3, 3, 0 },
|
||||
{ 3, 3, 180 },
|
||||
{ 3, 0, pure },
|
||||
});
|
||||
|
||||
runTests("upce-2", UPCE, 15, {
|
||||
{ 9, 10, 0 },
|
||||
{ 9, 10, 180 },
|
||||
});
|
||||
|
||||
runTests("upce-3", UPCE, 11, {
|
||||
{ 5, 7, 0 },
|
||||
{ 5, 7, 180 },
|
||||
});
|
||||
|
||||
runTests("rss14-1", DataBarOmni, 13, {
|
||||
{ 11, 11, 0 },
|
||||
{ 11, 11, 180 },
|
||||
});
|
||||
|
||||
runTests("rss14-2", DataBarStk, 7, {
|
||||
{ 5, 6, 0 },
|
||||
{ 6, 6, 180 },
|
||||
});
|
||||
|
||||
runTests("rssexpanded-1", DataBarExp, 37, {
|
||||
{ 37, 37, 0 },
|
||||
{ 37, 37, 180 },
|
||||
{ 37, 0, pure },
|
||||
});
|
||||
|
||||
runTests("rssexpanded-2", DataBarExp, 7, {
|
||||
{ 5, 7, 0 },
|
||||
{ 5, 7, 180 },
|
||||
});
|
||||
|
||||
runTests("rssexpanded-3", DataBarExp, 118, {
|
||||
{ 118, 118, 0 },
|
||||
{ 118, 118, 180 },
|
||||
{ 118, 0, pure },
|
||||
});
|
||||
|
||||
runTests("rssexpandedstacked-1", DataBarExpStk, 62, {
|
||||
{ 52, 62, 0 },
|
||||
{ 52, 62, 180 },
|
||||
{ 57, 0, pure },
|
||||
});
|
||||
|
||||
runTests("rssexpandedstacked-2", DataBarExpStk, 2, {
|
||||
{ 2, 2, 0 },
|
||||
{ 2, 2, 180 },
|
||||
});
|
||||
|
||||
runTests("databarltd-1", DataBarLtd, 2, {
|
||||
{ 2, 2, 0 },
|
||||
{ 2, 2, 180 },
|
||||
{ 1, 0, pure },
|
||||
});
|
||||
|
||||
runTests("maxicode-1", MaxiCode, 9, {
|
||||
{ 9, 9, 0 },
|
||||
});
|
||||
|
||||
runTests("maxicode-2", MaxiCode, 4, {
|
||||
{ 0, 0, 0 },
|
||||
});
|
||||
|
||||
runTests("qrcode-1", QRCode, 8, {
|
||||
{ 8, 8, 0 },
|
||||
{ 8, 8, 90 },
|
||||
{ 8, 8, 180 },
|
||||
{ 8, 8, 270 },
|
||||
});
|
||||
|
||||
runTests("qrcode-2", QRCode, 54, {
|
||||
{ 48, 51, 0 },
|
||||
{ 49, 52, 90 },
|
||||
{ 48, 51, 180 },
|
||||
{ 48, 51, 270 },
|
||||
{ 23, 1, pure }, // the misread is the 'outer' symbol in 16.png
|
||||
});
|
||||
|
||||
runTests("qrcode-3", QRCode, 18, {
|
||||
{ 18, 18, 0 },
|
||||
{ 18, 18, 90 },
|
||||
{ 18, 18, 180 },
|
||||
{ 18, 18, 270 },
|
||||
});
|
||||
|
||||
runTests("qrcode-4", QRCode, 24, {
|
||||
{ 15, 15, 0 },
|
||||
{ 15, 15, 90 },
|
||||
{ 15, 15, 180 },
|
||||
{ 15, 15, 270 },
|
||||
});
|
||||
|
||||
runTests("qrcode-5", QRCode, 16, {
|
||||
{ 16, 16, 0 },
|
||||
{ 16, 16, 90 },
|
||||
{ 16, 16, 180 },
|
||||
{ 16, 16, 270 },
|
||||
{ 4, 0, pure },
|
||||
});
|
||||
|
||||
runTests("qrcode-6", QRCode, 15, {
|
||||
{ 15, 15, 0 },
|
||||
{ 15, 15, 90 },
|
||||
{ 15, 15, 180 },
|
||||
{ 15, 15, 270 },
|
||||
});
|
||||
|
||||
runStructuredAppendTest("qrcode-7", QRCode, 1, {
|
||||
{ 1, 1, 0 },
|
||||
});
|
||||
|
||||
runTests("microqrcode-1", MicroQRCode, 16, {
|
||||
{ 15, 15, 0 },
|
||||
{ 14, 14, 90 },
|
||||
{ 14, 14, 180 }, // ughs: 1 result is platform/compiler dependent (e.g. -march=core2 vs. haswell)
|
||||
{ 15, 15, 270 },
|
||||
{ 9, 0, pure },
|
||||
});
|
||||
|
||||
runTests("rmqrcode-1", RMQRCode, 3, {
|
||||
{ 2, 3, 0 },
|
||||
{ 2, 3, 90 },
|
||||
{ 2, 3, 180 },
|
||||
{ 2, 3, 270 },
|
||||
{ 2, 2, pure },
|
||||
});
|
||||
|
||||
runTests("pdf417-1", PDF417, 17, {
|
||||
{ 16, 17, 0 },
|
||||
{ 1, 17, 90 },
|
||||
{ 16, 17, 180 },
|
||||
{ 1, 17, 270 },
|
||||
{ 16, 0, pure },
|
||||
});
|
||||
|
||||
runTests("pdf417-2", PDF417, 25, {
|
||||
{ 25, 25, 0 },
|
||||
{ 0, 25, 90 },
|
||||
{ 25, 25, 180 },
|
||||
{ 0, 25, 270 },
|
||||
});
|
||||
|
||||
runTests("pdf417-3", PDF417, 16, {
|
||||
{ 16, 16, 0 },
|
||||
{ 0, 16, 90 },
|
||||
{ 16, 16, 180 },
|
||||
{ 0, 16, 270 },
|
||||
{ 7, 0, pure },
|
||||
});
|
||||
|
||||
runStructuredAppendTest("pdf417-4", PDF417, 3, {
|
||||
{ 3, 3, 0 },
|
||||
});
|
||||
|
||||
runTests("micropdf417-1", MicroPDF417, 5, {
|
||||
{ 4, 4, 0 },
|
||||
{ 0, 4, 90 },
|
||||
{ 4, 4, 180 },
|
||||
{ 0, 4, 270 },
|
||||
{ 1, 0, pure },
|
||||
});
|
||||
|
||||
runTests("falsepositives-1", None, 21, {
|
||||
{ 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 90 },
|
||||
{ 0, 0, 0, 0, 180 },
|
||||
{ 0, 0, 0, 0, 270 },
|
||||
{ 0, 0, pure },
|
||||
});
|
||||
|
||||
runTests("falsepositives-2", None, 25, {
|
||||
{ 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 90 },
|
||||
{ 0, 0, 0, 0, 180 },
|
||||
{ 0, 0, 0, 0, 270 },
|
||||
{ 0, 0, pure },
|
||||
});
|
||||
// clang-format on
|
||||
|
||||
int totalTime = timeSince(startTime);
|
||||
int decodeTime = totalTime - totalImageLoadTime;
|
||||
std::println("load time: {} ms.", totalImageLoadTime);
|
||||
std::println("decode time: {} ms.", decodeTime);
|
||||
std::println("total time: {} ms.", totalTime);
|
||||
if (failed)
|
||||
std::println("WARNING: {} tests failed.", failed);
|
||||
if (extra)
|
||||
std::println("INFO: {} tests succeeded unexpectedly.", extra);
|
||||
|
||||
return failed;
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::println("{}", e.what());
|
||||
}
|
||||
catch (...) {
|
||||
std::println("Internal error");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // ZXing::Test
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ZXing::Test {
|
||||
|
||||
int runBlackBoxTests(const fs::path& testPathPrefix, const std::set<std::string>& includedTests);
|
||||
|
||||
} // ZXing::Test
|
||||
@@ -0,0 +1,37 @@
|
||||
zxing_add_package_stb()
|
||||
zxing_add_package(WebP WebP https://github.com/webmproject/libwebp.git v1.6.0)
|
||||
|
||||
if (ZXING_READERS)
|
||||
add_executable (ReaderTest
|
||||
TestReaderMain.cpp
|
||||
ImageLoader.h
|
||||
ImageLoader.cpp
|
||||
BlackboxTestRunner.h
|
||||
BlackboxTestRunner.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(ReaderTest
|
||||
ZXing::ZXing stb::stb WebP::webp
|
||||
$<$<AND:$<CXX_COMPILER_ID:GNU>,$<VERSION_LESS:$<CXX_COMPILER_VERSION>,9.0>>:stdc++fs>
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
add_custom_command(TARGET ReaderTest POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
$<TARGET_RUNTIME_DLLS:ReaderTest>
|
||||
$<TARGET_FILE_DIR:ReaderTest>
|
||||
COMMAND_EXPAND_LISTS
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
add_test(NAME ReaderTest COMMAND ReaderTest ${CMAKE_CURRENT_SOURCE_DIR}/../samples)
|
||||
endif()
|
||||
|
||||
if (ZXING_WRITERS)
|
||||
add_executable (WriterTest TestWriterMain.cpp)
|
||||
|
||||
target_link_libraries (WriterTest ZXing::ZXing stb::stb)
|
||||
|
||||
add_test(NAME WriterTest COMMAND WriterTest)
|
||||
endif()
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2019 Axel Waggersauser.
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "ImageLoader.h"
|
||||
|
||||
#include "BinaryBitmap.h"
|
||||
#include "ImageView.h"
|
||||
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
|
||||
#include <webp/decode.h>
|
||||
|
||||
namespace ZXing::Test {
|
||||
|
||||
class STBImage : public ImageView
|
||||
{
|
||||
std::unique_ptr<uint8_t[], void (*)(void*)> _memory = {nullptr, nullptr};
|
||||
|
||||
public:
|
||||
void load(const fs::path& imgPath)
|
||||
{
|
||||
int width = 0, height = 0, channels = 0;
|
||||
int rowStride = 0;
|
||||
|
||||
if (imgPath.extension() == ".webp") {
|
||||
std::ifstream file(imgPath, std::ios::binary);
|
||||
if (!file)
|
||||
throw std::runtime_error("Failed to read image: " + imgPath.string() + " (failed to open file)");
|
||||
|
||||
std::vector<uint8_t> compressedData(std::istreambuf_iterator<char>(file), {});
|
||||
if (compressedData.empty())
|
||||
throw std::runtime_error("Failed to read image: " + imgPath.string() + " (empty file)");
|
||||
|
||||
#if 0
|
||||
// directly decoding to YUV is faster and makes more sense but a couple of tests fail with this, so we decode to RGB and convert to Lum instead
|
||||
channels = 1;
|
||||
uint8_t *u = nullptr, *v = nullptr;
|
||||
int uvStride = 0;
|
||||
_memory = std::unique_ptr<uint8_t[], void (*)(void*)>(
|
||||
WebPDecodeYUV(compressedData.data(), compressedData.size(), &width, &height, &u, &v, &rowStride, &uvStride), WebPFree);
|
||||
#else
|
||||
channels = 3;
|
||||
_memory = std::unique_ptr<uint8_t[], void (*)(void*)>(
|
||||
WebPDecodeRGB(compressedData.data(), compressedData.size(), &width, &height), WebPFree);
|
||||
#endif
|
||||
if (_memory == nullptr)
|
||||
throw std::runtime_error("Failed to read image: " + imgPath.string() + " (WebP decode failed)");
|
||||
|
||||
} else {
|
||||
_memory = std::unique_ptr<uint8_t[], void (*)(void*)>(stbi_load(imgPath.string().c_str(), &width, &height, &channels, 0), stbi_image_free);
|
||||
if (_memory == nullptr)
|
||||
throw std::runtime_error("Failed to read image: " + imgPath.string() + " (" + stbi_failure_reason() + ")");
|
||||
rowStride = width * channels;
|
||||
}
|
||||
|
||||
auto ImageFormatFromChannels = std::array{ImageFormat::None, ImageFormat::Lum, ImageFormat::LumA, ImageFormat::RGB, ImageFormat::RGBA};
|
||||
ImageView::operator=({_memory.get(), width, height, ImageFormatFromChannels.at(channels), rowStride});
|
||||
|
||||
// preconvert from RGB -> Lum to do this only once instead of for each rotation
|
||||
if (_format == ImageFormat::RGB) {
|
||||
auto* img = _memory.get();
|
||||
for (int i = 0; i < width * height; ++i)
|
||||
img[i] = RGBToLum(img[3 * i + 0], img[3 * i + 1], img[3 * i + 2]);
|
||||
ImageView::operator=({_memory.get(), width, height, ImageFormat::Lum});
|
||||
}
|
||||
}
|
||||
|
||||
operator bool() const { return _data; }
|
||||
};
|
||||
|
||||
std::map<fs::path, STBImage> cache;
|
||||
std::mutex cacheMutex;
|
||||
|
||||
void ImageLoader::clearCache()
|
||||
{
|
||||
std::scoped_lock lock(cacheMutex);
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
const ImageView& ImageLoader::load(const fs::path& imgPath)
|
||||
{
|
||||
std::scoped_lock lock(cacheMutex);
|
||||
auto& binImg = cache[imgPath];
|
||||
if (!binImg)
|
||||
binImg.load(imgPath);
|
||||
|
||||
return binImg;
|
||||
}
|
||||
|
||||
} // namespace ZXing::Test
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ImageView.h"
|
||||
|
||||
#include <filesystem>
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ZXing::Test::ImageLoader {
|
||||
|
||||
void clearCache();
|
||||
const ImageView& load(const fs::path& imgPath);
|
||||
|
||||
} // namespace ZXing::Test::ImageLoader
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2017 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "BlackboxTestRunner.h"
|
||||
#include "ImageLoader.h"
|
||||
#include "ReadBarcode.h"
|
||||
#include "StdPrint.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
|
||||
using namespace ZXing;
|
||||
using namespace ZXing::Test;
|
||||
|
||||
int getEnv(const char* name, int fallback = 0)
|
||||
{
|
||||
auto var = getenv(name);
|
||||
return var ? std::stoi(var) : fallback;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc <= 1) {
|
||||
std::println("Usage: {} <test_path_prefix>", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fs::path pathPrefix = argv[1];
|
||||
|
||||
if (Contains({".png", ".jpg", ".pgm", ".gif", ".webp", ".jxl"}, pathPrefix.extension())) {
|
||||
auto opts = ReaderOptions().tryHarder(!getEnv("FAST", false)).tryRotate(true).isPure(getEnv("IS_PURE"));
|
||||
if (getenv("FORMATS"))
|
||||
opts.formats(BarcodeFormatsFromString(getenv("FORMATS")));
|
||||
int rotation = getEnv("ROTATION");
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
Barcode barcode = ReadBarcode(ImageLoader::load(argv[i]).rotated(rotation), opts);
|
||||
std::print("{}: ", argv[i]);
|
||||
if (barcode.isValid())
|
||||
std::println("{}: {}", ToString(barcode.format()), barcode.text());
|
||||
else
|
||||
std::println("FAILED");
|
||||
if (barcode.isValid() && getenv("WRITE_TEXT")) {
|
||||
std::ofstream f(fs::path(argv[i]).replace_extension(".txt"));
|
||||
f << barcode.text();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
std::set<std::string> includedTests;
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
if (std::strlen(argv[i]) > 2 && argv[i][0] == '-' && argv[i][1] == 't') {
|
||||
includedTests.insert(argv[i] + 2);
|
||||
}
|
||||
}
|
||||
|
||||
return runBlackBoxTests(pathPrefix, includedTests);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2019 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "ZXingCpp.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace ZXing;
|
||||
using namespace std::literals;
|
||||
using enum BarcodeFormat;
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include <stb_image_write.h>
|
||||
|
||||
void savePng(ImageView iv, BarcodeFormat format)
|
||||
{
|
||||
stbi_write_png((ToString(format) + ".png"s).c_str(), iv.width(), iv.height(), iv.pixStride(), iv.data(), iv.rowStride());
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
std::string text = "zxing-cpp";
|
||||
for (auto format : BarcodeFormats::list(AllMatrix))
|
||||
if (format & AllCreatable && format != AztecRune)
|
||||
savePng(CreateBarcodeFromText(text, format).symbol(), format);
|
||||
|
||||
text = "012345678901234567890123456789";
|
||||
using FormatSpecs = std::vector<std::pair<BarcodeFormat, size_t>>;
|
||||
for (const auto& [format, length] : FormatSpecs({
|
||||
// {Codabar, 0}, // needs to start with A, B, C or D
|
||||
{Code39, 0},
|
||||
{Code93, 0},
|
||||
{Code128, 0},
|
||||
{EAN8, 7},
|
||||
{EAN13, 12},
|
||||
{ITF, 0},
|
||||
{UPCA, 11},
|
||||
{UPCE, 7}
|
||||
}))
|
||||
{
|
||||
auto input = length > 0 ? text.substr(0, length) : text;
|
||||
if (format & AllCreatable)
|
||||
savePng(WriteBarcodeToImage(CreateBarcodeFromText(input, format)), format);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
cmake_minimum_required (VERSION 3.16)
|
||||
|
||||
|
||||
# -fsanitize=fuzzer only available in clang / llvm
|
||||
# cmake -DCMAKE_CXX_COMPILER=/path/to/your/clang++
|
||||
|
||||
project (ZXingFuzz)
|
||||
|
||||
set (CMAKE_CXX_STANDARD 20)
|
||||
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -march=native -fsanitize=address,undefined,fuzzer")
|
||||
|
||||
set (ZXING_WRITERS BOTH)
|
||||
set (ZXING_READERS ON)
|
||||
|
||||
add_definitions (-DZXING_BUILD_FOR_TEST)
|
||||
add_subdirectory (${CMAKE_CURRENT_SOURCE_DIR}/../../core ${CMAKE_BINARY_DIR}/ZXing)
|
||||
|
||||
set (TESTS
|
||||
DBEDecoder
|
||||
DMEncoder
|
||||
ReadLinear
|
||||
ReadMatrix
|
||||
DecodeMatrix
|
||||
)
|
||||
|
||||
foreach (test ${TESTS})
|
||||
set (name "fuzz${test}")
|
||||
add_executable (${name} "${name}.cpp")
|
||||
target_compile_options (${name} PRIVATE -DZXING_INTERNAL)
|
||||
target_link_libraries (${name} ZXing::ZXing)
|
||||
endforeach()
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2021 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "BitArray.h"
|
||||
#include "Error.h"
|
||||
#include "oned/ODDataBarExpandedBitDecoder.h"
|
||||
|
||||
using namespace ZXing;
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
if (size < 2)
|
||||
return 0;
|
||||
|
||||
BitArray bits;
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
bits.appendBits(data[i], 8);
|
||||
|
||||
try {
|
||||
OneD::DataBar::DecodeExpandedBits(bits);
|
||||
} catch (std::out_of_range) {
|
||||
} catch (Error) {
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2021 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "ByteArray.h"
|
||||
#include "datamatrix/DMHighLevelEncoder.h"
|
||||
|
||||
using namespace ZXing;
|
||||
using namespace ZXing::DataMatrix;
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
std::wstring txt(data, data + size);
|
||||
try {
|
||||
Encode(txt);
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2023 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "BitArray.h"
|
||||
#include "ByteArray.h"
|
||||
#include "DecoderResult.h"
|
||||
#include "qrcode/QRErrorCorrectionLevel.h"
|
||||
#include "qrcode/QRVersion.h"
|
||||
#include "pdf417/PDFDecoder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
using namespace ZXing;
|
||||
|
||||
namespace ZXing::Aztec {
|
||||
DecoderResult Decode(const BitArray& bits);
|
||||
}
|
||||
|
||||
namespace ZXing::DataMatrix::DecodedBitStreamParser {
|
||||
DecoderResult Decode(ByteArray&& bytes, const bool isDMRE);
|
||||
}
|
||||
|
||||
namespace ZXing::QRCode {
|
||||
DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCorrectionLevel ecLevel);
|
||||
}
|
||||
|
||||
void az(const uint8_t* data, size_t size)
|
||||
{
|
||||
BitArray bits;
|
||||
for (size_t i = 1; i < size - 1; ++i)
|
||||
bits.appendBits(data[i], 8);
|
||||
|
||||
bits.appendBits(data[size - 1], (data[0] & 0x7) + 1);
|
||||
|
||||
Aztec::Decode(bits);
|
||||
}
|
||||
|
||||
void dm(const uint8_t* data, size_t size)
|
||||
{
|
||||
ByteArray ba;
|
||||
ba.insert(ba.begin(), data, data + size);
|
||||
try {
|
||||
DataMatrix::DecodedBitStreamParser::Decode(std::move(ba), false);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void qr(const uint8_t* data, size_t size)
|
||||
{
|
||||
auto version = QRCode::Version::Model2(std::clamp(data[0] & 0x3F, 1, 40));
|
||||
auto ecLevel = QRCode::ECLevelFromBits(data[0] >> 6);
|
||||
|
||||
ByteArray ba;
|
||||
ba.insert(ba.begin(), data, data + size);
|
||||
|
||||
QRCode::DecodeBitStream(std::move(ba), *version, ecLevel);
|
||||
}
|
||||
|
||||
void pd(const uint8_t* data, size_t size)
|
||||
{
|
||||
auto codewords = std::vector<int>(size / 2);
|
||||
auto u16 = reinterpret_cast<const uint16_t*>(data);
|
||||
|
||||
for (int i = 0; i < static_cast<int>(codewords.size()); ++i)
|
||||
codewords[i] = u16[i] % 929;
|
||||
|
||||
codewords[0] = static_cast<int>(codewords.size());
|
||||
|
||||
Pdf417::Decode(codewords);
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
if (size < 3)
|
||||
return 0;
|
||||
|
||||
az(data, size);
|
||||
dm(data, size);
|
||||
qr(data, size);
|
||||
pd(data, size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2021 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "oned/ODMultiUPCEANReader.h"
|
||||
#include "oned/ODCode39Reader.h"
|
||||
#include "oned/ODCode93Reader.h"
|
||||
#include "oned/ODCode128Reader.h"
|
||||
#include "oned/ODDataBarReader.h"
|
||||
#include "oned/ODDataBarExpandedReader.h"
|
||||
#include "oned/ODDXFilmEdgeReader.h"
|
||||
#include "oned/ODITFReader.h"
|
||||
#include "oned/ODCodabarReader.h"
|
||||
#include "ReaderOptions.h"
|
||||
#include "Barcode.h"
|
||||
|
||||
using namespace ZXing;
|
||||
using namespace ZXing::OneD;
|
||||
|
||||
static std::vector<std::unique_ptr<RowReader>> readers;
|
||||
|
||||
bool init()
|
||||
{
|
||||
static auto opts = ReaderOptions().returnErrors(true);
|
||||
readers.emplace_back(new MultiUPCEANReader(opts));
|
||||
readers.emplace_back(new Code39Reader(opts));
|
||||
readers.emplace_back(new Code93Reader(opts));
|
||||
readers.emplace_back(new Code128Reader(opts));
|
||||
readers.emplace_back(new ITFReader(opts));
|
||||
readers.emplace_back(new CodabarReader(opts));
|
||||
readers.emplace_back(new DataBarReader(opts));
|
||||
readers.emplace_back(new DataBarExpandedReader(opts));
|
||||
readers.emplace_back(new DXFilmEdgeReader(opts));
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
if (size < 1)
|
||||
return 0;
|
||||
|
||||
static bool inited [[maybe_unused]] = init();
|
||||
std::vector<std::unique_ptr<RowReader::DecodingState>> decodingState(readers.size());
|
||||
|
||||
PatternRow row(size * 2 + 1);
|
||||
for (size_t i = 0; i < size; ++i){
|
||||
auto v = data[i];
|
||||
row[i * 2 + 0] = (v & 0xf) + 1;
|
||||
row[i * 2 + 1] = (v >> 4) + 1;
|
||||
}
|
||||
row.back() = 0;
|
||||
|
||||
for (size_t r = 0; r < readers.size(); ++r) {
|
||||
PatternView next(row);
|
||||
while (next.isValid()) {
|
||||
readers[r]->decodePattern(0, next, decodingState[r]);
|
||||
// make sure we make progress and we start the next try on a bar
|
||||
next.shift(2 - (next.index() % 2));
|
||||
next.extend();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2023 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "ReadBarcode.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <csignal>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
using namespace ZXing;
|
||||
|
||||
uint64_t Expand(uint8_t b)
|
||||
{
|
||||
uint64_t shift = 0x0000040810204081ul; // bits set: 0, 7, 14, 21, 28, 35, 42
|
||||
uint64_t mask = 0x0001010101010101ul; // bits set: 0, 8, 16, 24, 32, 40, 48
|
||||
return ((uint64_t)(b & 127) * shift & mask) | (uint64_t)(b & 128) << 49;
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
if (size < 3)
|
||||
return 0;
|
||||
|
||||
static auto opts = ReaderOptions()
|
||||
.formats(BarcodeFormat::AllMatrix)
|
||||
.binarizer(Binarizer::BoolCast)
|
||||
.returnErrors(true)
|
||||
.tryInvert(false)
|
||||
.tryRotate(false);
|
||||
int ratio = data[0] + 1;
|
||||
int nBits = (size - 1) * 8;
|
||||
int width = std::clamp(nBits * ratio / 256, 1, nBits);
|
||||
int height = std::clamp(nBits / width, 1, nBits);
|
||||
|
||||
assert(width * height <= nBits);
|
||||
|
||||
std::vector<uint8_t> buffer(nBits);
|
||||
for (size_t i = 1; i < size; ++i)
|
||||
*reinterpret_cast<uint64_t*>(&buffer[(i - 1) * 8]) = Expand(data[i]);
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("s: %zu, r: %d, n: %d -> %d x %d\n", size, ratio, nBits, width, height);
|
||||
#endif
|
||||
|
||||
auto image = ImageView(buffer.data(), width, height, ImageFormat::Lum);
|
||||
auto res = ReadBarcodes(image, opts);
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
for (const auto& r : res)
|
||||
printf("%s: %s / %s\n", ToString(r.format()).c_str(), r.text().c_str(), ToString(r.error()).c_str());
|
||||
#endif
|
||||
|
||||
static int detectedSybols = 0;
|
||||
detectedSybols += static_cast<int>(res.size());
|
||||
if (!res.empty() && detectedSybols % 100 == 0)
|
||||
printf("detected barcode symbols: %d\n", detectedSybols);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(ZXingInstallTest LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17) # make sure the public API is still c++17 compatible
|
||||
|
||||
find_package(ZXing CONFIG REQUIRED)
|
||||
|
||||
add_executable(InstallTest main.cpp)
|
||||
|
||||
target_link_libraries(InstallTest PRIVATE ZXing::ZXing)
|
||||
|
||||
if (ZXING_WRITERS MATCHES "BOTH|OLD")
|
||||
add_executable(InstallTestOLD mainOLD.cpp)
|
||||
|
||||
target_link_libraries(InstallTestOLD PRIVATE ZXing::ZXing)
|
||||
endif()
|
||||
@@ -0,0 +1,22 @@
|
||||
#include "ZXing/ZXingCpp.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
using namespace ZXing;
|
||||
|
||||
auto copts = CreatorOptions(BarcodeFormat::QRCode, "eclevel=L");
|
||||
auto barcode = CreateBarcodeFromText("Test", copts);
|
||||
auto wopts = WriterOptions().scale(2);
|
||||
auto img = WriteBarcodeToImage(barcode, wopts);
|
||||
|
||||
auto iv = ImageView(img.data(), img.width(), img.height(), img.format());
|
||||
auto ropts = ReaderOptions().formats(BarcodeFormat::AllLinear);
|
||||
auto barcodes = ReadBarcodes(iv, ropts);
|
||||
|
||||
for (const auto& b : barcodes)
|
||||
std::cout << ToString(b.format()) << ": " << b.text() << "\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#include "ZXing/MultiFormatWriter.h"
|
||||
#include "ZXing/BitMatrix.h"
|
||||
#include "ZXing/BitMatrixIO.h"
|
||||
#include "ZXing/ZXVersion.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
using namespace ZXing;
|
||||
|
||||
std::cout << "zxing-cpp version " << ZXING_VERSION_STR << "\n";
|
||||
|
||||
MultiFormatWriter writer(BarcodeFormat::QRCode);
|
||||
BitMatrix bitmatrix = writer.encode("Hello, World!", 200, 200);
|
||||
auto matrix = ToMatrix<int8_t>(bitmatrix);
|
||||
std::cout << ToString(bitmatrix) << "\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
set(install_prefix "${PROJECT_BINARY_DIR}/InstallTest-prefix")
|
||||
set(consumer_build "${PROJECT_BINARY_DIR}/InstallTest-build")
|
||||
|
||||
# 1. Install zxing-cpp
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_COMMAND}" --install "${PROJECT_BINARY_DIR}" --prefix "${install_prefix}"
|
||||
RESULT_VARIABLE install_result
|
||||
)
|
||||
if(NOT install_result EQUAL 0)
|
||||
message(FATAL_ERROR "Install step failed")
|
||||
endif()
|
||||
|
||||
# 2. Configure consumer project
|
||||
execute_process(
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}"
|
||||
-S "${SOURCE_DIR}"
|
||||
-B "${consumer_build}"
|
||||
-DCMAKE_PREFIX_PATH=${install_prefix}
|
||||
-DZXING_WRITERS=${ZXING_WRITERS}
|
||||
RESULT_VARIABLE configure_result
|
||||
)
|
||||
if(NOT configure_result EQUAL 0)
|
||||
message(FATAL_ERROR "Consumer configure failed")
|
||||
endif()
|
||||
|
||||
# 3. Build consumer project
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_COMMAND}" --build "${consumer_build}"
|
||||
RESULT_VARIABLE build_result
|
||||
)
|
||||
if(NOT build_result EQUAL 0)
|
||||
message(FATAL_ERROR "Consumer build failed")
|
||||
endif()
|
||||
@@ -0,0 +1 @@
|
||||
* binary
|
||||
@@ -0,0 +1 @@
|
||||
This is an example Aztec symbol for Wikipedia.
|
||||
|
After Width: | Height: | Size: 164 B |
|
After Width: | Height: | Size: 170 B |
@@ -0,0 +1 @@
|
||||
I have the best words.
|
||||
|
After Width: | Height: | Size: 192 B |
@@ -0,0 +1 @@
|
||||
symbologyIdentifier=]z0
|
||||
@@ -0,0 +1 @@
|
||||
Code 2D!
|
||||
|
After Width: | Height: | Size: 456 B |
@@ -0,0 +1 @@
|
||||
This is an example Aztec symbol for Wikipedia.
|
||||
|
After Width: | Height: | Size: 196 B |
@@ -0,0 +1 @@
|
||||
Histórico
|
||||
|
After Width: | Height: | Size: 527 B |
@@ -0,0 +1 @@
|
||||
Históóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóórico
|
||||
|
After Width: | Height: | Size: 195 B |
@@ -0,0 +1,4 @@
|
||||
symbologyIdentifier=]z6
|
||||
sequenceSize=7
|
||||
sequenceIndex=3
|
||||
sequenceId=Z1.txt
|
||||
@@ -0,0 +1 @@
|
||||
3456
|
||||
|
After Width: | Height: | Size: 226 B |
@@ -0,0 +1 @@
|
||||
abcdefghijklmnopqrstuvwxyz
|
||||
|
After Width: | Height: | Size: 481 B |
@@ -0,0 +1 @@
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
|
After Width: | Height: | Size: 260 B |
@@ -0,0 +1 @@
|
||||
abcdefghijklmnopqrstuvwxyz
|
||||
|
After Width: | Height: | Size: 264 B |
@@ -0,0 +1 @@
|
||||
abcdefghijklmnopqrstuvwxyz
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1 @@
|
||||
Aztec Code is a public domain 2D matrix barcode symbology of nominally square symbols built on a square grid with a distinctive square bullseye pattern at their center.
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1 @@
|
||||
Aztec Code is a public domain 2D matrix barcode symbology of nominally square symbols built on a square grid with a distinctive square bullseye pattern at their center.
|
||||
|
After Width: | Height: | Size: 186 B |
@@ -0,0 +1 @@
|
||||
3333h3i3jITIT
|
||||
|
After Width: | Height: | Size: 241 B |
@@ -0,0 +1 @@
|
||||
symbologyIdentifier=]z1
|
||||
@@ -0,0 +1 @@
|
||||
01095040000591012112345678p901101234567p171411208200http://www.gs1.org/demo/
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1 @@
|
||||
hello
|
||||
|
After Width: | Height: | Size: 883 B |
@@ -0,0 +1 @@
|
||||
In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tristique ac, p
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1 @@
|
||||
In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tristique ac, p.In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tristique ac, p
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1 @@
|
||||
In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tristique ac, p.In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tristique ac, p. In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tristique ac, p.In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tris. In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo e.
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1 @@
|
||||
In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tristique ac, p.In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tristique ac, p. In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tristique ac, p.In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tris. In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem.
|
||||
|
After Width: | Height: | Size: 650 B |
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
||||
KF5::Prison - The KDE barcode generation framework.
|
||||
|
After Width: | Height: | Size: 190 B |
|
After Width: | Height: | Size: 129 B |
@@ -0,0 +1,2 @@
|
||||
symbologyIdentifier=]z0
|
||||
readerInit=true
|
||||
@@ -0,0 +1 @@
|
||||
$I
|
||||