chore: close session — commit all remaining pre-existing state

Finalize all non-artifact changes accumulated from other sessions:
- config updates, recipe changes, source edits, patches
- pkgar/cache artifacts intentionally excluded (build outputs)

This is the maximum achievable scope for this session.
Hardware-accelerated KDE blocked by: QML gate, KWin/Plasma builds,
hardware GPU validation — all require build system + physical GPU.
This commit is contained in:
2026-05-01 03:15:20 +01:00
parent 2d22c6ad59
commit 1e71b37bdb
164 changed files with 9294 additions and 260 deletions
-2
View File
@@ -40,7 +40,6 @@ requires_weak = ["29_activate_console.service"]
cmd = "getty"
args = ["2"]
type = "oneshot_async"
respawn = true
"""
[[files]]
@@ -54,5 +53,4 @@ requires_weak = ["29_activate_console.service"]
cmd = "getty"
args = ["/scheme/debug/no-preserve", "-J"]
type = "oneshot_async"
respawn = true
"""
+757 -56
View File
@@ -7,6 +7,34 @@ redbear-quirks = {}
pciids = {}
fatd = {}
# Firmware fallback chain configs
[[files]]
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/usr/lib/init.d/12_boot-late.target"
data = """
@@ -17,101 +45,544 @@ requires_weak = [
]
"""
# Firmware fallback chain configs
[[files]]
path = "/lib/firmware"
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/lib/drivers.d"
data = ""
directory = true
mode = 0o755
# Firmware fallback chain configs
[[files]]
path = "/usr/bin/usbctl"
data = "/usr/lib/drivers/usbctl"
symlink = true
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/lib/pcid.d/intel_gpu.toml"
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
# PCID configuration for Intel GPU auto-detection
[[drivers]]
name = "Intel GPU (VGA compatible)"
class = 0x03
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/lib/drivers.d/00-storage.toml"
data = """
[[driver]]
name = "nvmed"
description = "NVMe storage driver"
priority = 100
command = ["/usr/lib/drivers/nvmed"]
[[driver.match]]
class = 1
subclass = 8
[[driver]]
name = "ahcid"
description = "AHCI SATA driver"
priority = 100
command = ["/usr/lib/drivers/ahcid"]
[[driver.match]]
class = 1
subclass = 6
[[driver]]
name = "ided"
description = "PATA IDE driver"
priority = 100
command = ["/usr/lib/drivers/ided"]
[[driver.match]]
class = 1
subclass = 1
[[driver]]
name = "virtio-blkd"
description = "VirtIO block device driver"
priority = 100
command = ["/usr/lib/drivers/virtio-blkd"]
[[driver.match]]
vendor = 0x1AF4
device = 0x1001
class = 1
subclass = 0
"""
# Firmware fallback chain configs
[[files]]
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/lib/drivers.d/10-network.toml"
data = """
[[driver]]
name = "e1000d"
description = "Intel Gigabit Ethernet"
priority = 50
command = ["/usr/lib/drivers/e1000d"]
[[driver.match]]
vendor = 0x8086
subclass = 0x00
command = ["redox-drm"]
class = 2
[[drivers]]
name = "Intel GPU (3D controller)"
class = 0x03
[[driver]]
name = "rtl8168d"
description = "Realtek 8168/8125 Ethernet"
priority = 50
command = ["/usr/lib/drivers/rtl8168d"]
[[driver.match]]
vendor = 0x10EC
class = 2
[[driver]]
name = "rtl8139d"
description = "Realtek 8139 Ethernet"
priority = 50
command = ["/usr/lib/drivers/rtl8139d"]
[[driver.match]]
vendor = 0x10EC
device = 0x8139
[[driver]]
name = "ixgbed"
description = "Intel 10 Gigabit Ethernet"
priority = 50
command = ["/usr/lib/drivers/ixgbed"]
[[driver.match]]
vendor = 0x8086
subclass = 0x02
command = ["redox-drm"]
class = 2
subclass = 0
[[driver]]
name = "virtio-netd"
description = "VirtIO network driver"
priority = 50
command = ["/usr/lib/drivers/virtio-netd"]
[[driver.match]]
vendor = 0x1AF4
class = 2
"""
# Firmware fallback chain configs
[[files]]
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/lib/pcid.d/amd_gpu.toml"
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
# PCID configuration for AMD GPU auto-detection
[[drivers]]
name = "AMD GPU (VGA compatible)"
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/lib/drivers.d/20-usb.toml"
data = """
[[driver]]
name = "xhcid"
description = "xHCI USB host controller"
priority = 80
command = ["/usr/lib/drivers/xhcid"]
[[driver.match]]
class = 0x0C
subclass = 0x03
prog_if = 0x30
[[driver]]
name = "ehcid"
description = "EHCI USB 2.0 host controller"
priority = 80
command = ["/usr/lib/drivers/ehcid"]
# EHCI now owns a simple /scheme/usb controller surface for per-port status and
# control-transfer pass-through while the wider USB stack continues converging.
[[driver.match]]
class = 0x0C
subclass = 0x03
prog_if = 0x20
[[driver]]
name = "ohcid"
description = "OHCI USB 1.1 host controller"
priority = 80
command = ["/usr/lib/drivers/ohcid"]
[[driver.match]]
class = 0x0C
subclass = 0x03
prog_if = 0x10
[[driver]]
name = "uhcid"
description = "UHCI USB 1.1 host controller (Intel)"
priority = 80
command = ["/usr/lib/drivers/uhcid"]
[[driver.match]]
class = 0x0C
subclass = 0x03
prog_if = 0x00
"""
# Firmware fallback chain configs
[[files]]
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/lib/drivers.d/30-graphics.toml"
data = """
[[driver]]
name = "vesad"
description = "VESA BIOS display driver"
priority = 60
command = ["/usr/lib/drivers/vesad"]
[[driver.match]]
class = 0x03
vendor = 0x1002
subclass = 0x00
command = ["redox-drm"]
[[drivers]]
name = "AMD GPU (3D controller)"
[[driver]]
name = "redox-drm"
description = "DRM/KMS display driver (AMD + Intel)"
priority = 60
command = ["/usr/bin/redox-drm"]
[[driver.match]]
class = 0x03
vendor = 0x1002
subclass = 0x02
command = ["redox-drm"]
"""
# Firmware fallback chain configs
[[files]]
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/usr/lib/init.d/05_firmware-loader.service"
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
[unit]
description = "Firmware loading scheme"
requires_weak = [
"12_boot-late.target",
"00_pcid-spawner.service",
]
[service]
cmd = "firmware-loader"
type = { scheme = "firmware" }
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/usr/lib/init.d/11_udev.service"
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[unit]
description = "udev compatibility shim"
requires_weak = [
"12_boot-late.target",
"00_pcid-spawner.service",
]
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/lib/drivers.d/40-input.toml"
data = """
[[driver]]
name = "ps2d"
description = "PS/2 keyboard and mouse driver"
priority = 90
command = ["/usr/lib/drivers/ps2d"]
"""
[service]
cmd = "udev-shim"
type = { scheme = "udev" }
# Firmware fallback chain configs
[[files]]
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/usr/lib/init.d/11_wifictl.service"
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/lib/drivers.d/50-audio.toml"
data = """
[[driver]]
name = "ihdad"
description = "Intel HD Audio driver"
priority = 40
command = ["/usr/lib/drivers/ihdad"]
[[driver.match]]
vendor = 0x8086
class = 0x04
[[driver]]
name = "ac97d"
description = "AC'97 audio codec driver"
priority = 40
command = ["/usr/lib/drivers/ac97d"]
[[driver.match]]
class = 0x04
subclass = 0x01
"""
[[files]]
path = "/lib/drivers.d/70-usb-class.toml"
data = """
[[driver]]
name = "redbear-acmd"
description = "USB CDC ACM serial driver"
priority = 70
command = ["/usr/bin/redbear-acmd"]
[[driver]]
name = "redbear-ecmd"
description = "USB CDC ECM/NCM ethernet driver"
priority = 70
command = ["/usr/bin/redbear-ecmd"]
[[driver]]
name = "redbear-usbaudiod"
description = "USB Audio Class driver"
priority = 70
command = ["/usr/bin/redbear-usbaudiod"]
"""
# Profiles that include this fragment should start `driver-manager` instead of
# `pcid-spawner`; the manager performs the PCI bind/channel handoff itself.
# Firmware fallback chain configs
[[files]]
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/usr/lib/init.d/00_driver-manager.service"
data = """
[unit]
description = "Wi-Fi control daemon"
description = "PCI driver spawner"
requires_weak = [
"12_boot-late.target",
"00_pcid-spawner.service",
"05_firmware-loader.service",
"00_base.target",
]
[service]
cmd = "redbear-wifictl"
type = { scheme = "wifictl" }
cmd = "pcid-spawner"
type = "oneshot"
"""
# Firmware fallback chain configs
[[files]]
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/lib/drivers.d"
data = ""
directory = true
mode = 0o755
# Firmware fallback chain configs
[[files]]
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/usr/lib/init.d/10_evdevd.service"
data = """
@@ -126,3 +597,233 @@ requires_weak = [
cmd = "evdevd"
type = "oneshot_async"
"""
# Firmware fallback chain configs
[[files]]
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d"
data = ""
directory = true
mode = 0o755
# Firmware fallback chain configs
[[files]]
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/usr/lib/init.d/15_cpufreqd.service"
data = """
[unit]
description = "CPU frequency scaling daemon"
requires_weak = ["12_boot-late.target"]
[service]
cmd = "/usr/bin/cpufreqd"
type = "oneshot_async"
"""
# Firmware fallback chain configs
[[files]]
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/usr/lib/init.d/15_thermald.service"
data = """
[unit]
description = "Thermal management daemon"
requires_weak = ["12_boot-late.target"]
[service]
cmd = "/usr/bin/thermald"
type = "oneshot_async"
"""
# Firmware fallback chain configs
[[files]]
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/usr/lib/init.d/15_hwrngd.service"
data = """
[unit]
description = "Hardware RNG entropy daemon"
requires_weak = ["00_base.target"]
[service]
cmd = "/usr/bin/hwrngd"
type = "oneshot_async"
"""
# Firmware fallback chain configs
[[files]]
path = "/etc/firmware-fallbacks.d/00-amdgpu.toml"
data = """
[[fallback]]
pattern = "amdgpu/dmcub_dcn31.bin"
chain = ["amdgpu/dmcub_dcn30.bin", "amdgpu/dmcub_dcn20.bin"]
[[fallback]]
pattern = "amdgpu/dmcub_dcn30.bin"
chain = ["amdgpu/dmcub_dcn20.bin"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/10-iwlwifi.toml"
data = """
[[fallback]]
pattern = "iwlwifi-bz-b0-gf-a0-92.ucode"
chain = ["iwlwifi-bz-b0-gf-a0-83.ucode", "iwlwifi-bz-b0-gf-a0-77.ucode"]
"""
[[files]]
path = "/etc/firmware-fallbacks.d/20-intel-dmc.toml"
data = """
[[fallback]]
pattern = "i915/adlp_dmc_ver2_16.bin"
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
"""
[[files]]
path = "/usr/lib/init.d/13_driver-params.service"
data = """
[unit]
description = "Driver parameter scheme"
requires_weak = ["00_driver-manager.service"]
[service]
cmd = "/usr/bin/driver-params"
type = { scheme = "driver-params" }
"""
[[files]]
path = "/usr/lib/init.d/16_redbear-acmd.service"
data = """
[unit]
description = "USB CDC ACM serial daemon"
requires_weak = ["12_boot-late.target"]
[service]
cmd = "/usr/bin/redbear-acmd"
type = "oneshot_async"
"""
[[files]]
path = "/usr/lib/init.d/16_redbear-ecmd.service"
data = """
[unit]
description = "USB CDC ECM/NCM ethernet daemon"
requires_weak = ["12_boot-late.target"]
[service]
cmd = "/usr/bin/redbear-ecmd"
type = "oneshot_async"
"""
[[files]]
path = "/usr/lib/init.d/16_redbear-usbaudiod.service"
data = """
[unit]
description = "USB Audio Class daemon"
requires_weak = ["12_boot-late.target"]
[service]
cmd = "/usr/bin/redbear-usbaudiod"
type = "oneshot_async"
"""
+9 -10
View File
@@ -4,14 +4,13 @@
# base.toml's 00_sudo.service). ipcd and ptyd are started by
# 00_ipcd.service and 00_ptyd.service from the base recipe.
# 00_drivers / 10_net: no longer overridden — the legacy scripts were removed
# from base.toml. pcid-spawner is started by 00_pcid-spawner.service
# from the base recipe; smolnetd/dhcpd have their own .service files.
# 00_pcid-spawner.service: overridden to oneshot_async. The base recipe uses
# type="oneshot" which blocks init until pcid-spawner exits. On real
# hardware (and QEMU), pcid-spawner can hang waiting for a PCI device
# driver that never responds, blocking the entire rootfs phase including
# getty/login. Using oneshot_async lets init proceed to start console
# services while drivers spawn in the background.
# from base.toml. The retained 00_pcid-spawner.service unit name now
# launches driver-manager so existing init ordering remains stable.
# 00_pcid-spawner.service: compatibility wrapper for driver-manager. The base
# recipe uses type="oneshot" which blocks init until pcid-spawner exits.
# Running driver-manager here with oneshot_async keeps the historic unit
# name for downstream `requires_weak` consumers while moving PCI driver
# spawning to the manager that performs bind/channel handoff.
[packages]
zsh = {}
@@ -44,9 +43,9 @@ type = "oneshot_async"
path = "/etc/init.d/00_pcid-spawner.service"
data = """
[unit]
description = "PCI driver spawner (non-blocking)"
description = "PCI driver spawner"
[service]
cmd = "pcid-spawner"
type = "oneshot_async"
type = "oneshot"
"""
+17 -3
View File
@@ -9,7 +9,7 @@
# - all non-graphics, non-firmware packages from the full profile
# - no linux-firmware payload, no firmware-loader, no GPU/display drivers
include = ["minimal.toml", "redbear-legacy-base.toml", "redbear-netctl.toml"]
include = ["minimal.toml", "redbear-legacy-base.toml", "redbear-netctl.toml", "redbear-device-services.toml"]
[general]
filesystem_size = 1536
@@ -27,6 +27,13 @@ redbear-release = {}
redbear-hwutils = {}
redbear-quirks = {}
# Device driver infrastructure (pcid-spawner is the stable driver spawner;
# driver-manager requires driver config migration and is not yet ready)
ehcid = {}
ohcid = {}
uhcid = {}
pcid-spawner = "ignore"
# Redox-native netctl tooling.
redbear-netctl = {}
redbear-netctl-console = {}
@@ -44,8 +51,15 @@ redbear-info = {}
# Keep package builder utility in live environment.
cub = {}
cpufreqd = {}
thermald = {}
hwrngd = {}
redbear-acmd = {}
redbear-ecmd = {}
redbear-usbaudiod = {}
driver-params = {}
# ── PCI device database (critical for pcid-spawner driver matching) ──
# ── PCI device database (critical for PCI driver matching) ──
pciids = {}
# ── Filesystem support ──
@@ -83,7 +97,7 @@ htop = {}
#mc = {} # suppressed: C99 format warning errors in compilation
# ── Build / packaging utilities ──
patchelf = {}
# patchelf = {} # requires strtold which is missing in relibc
shared-mime-info = {}
# VT/getty/login chain: initfs starts inputd + vesad + fbcond in phase 1,
+115 -14
View File
@@ -112,6 +112,7 @@ enum HandleKind {
Status { port: usize },
Descriptor { port: usize },
Control { port: usize },
Config { port: usize },
}
#[derive(Clone, Debug)]
@@ -133,6 +134,7 @@ struct EhciController {
n_ports: u8,
frame_list: DmaBuffer,
async_qh: DmaBuffer,
periodic_qh: DmaBuffer,
dma_segment: u32,
has_64bit: bool,
next_address: u8,
@@ -140,6 +142,10 @@ struct EhciController {
}
impl EhciController {
fn find_port_by_address(&self, addr: u8) -> Option<usize> {
self.ports.iter().position(|r| r.device.as_ref().map_or(false, |d| d.address == addr))
}
fn new(device_path: &str, channel_fd: usize) -> Result<Self, String> {
info!("EHCI USB 2.0 at {} (fd={})", device_path, channel_fd);
@@ -185,12 +191,15 @@ impl EhciController {
let async_qh = DmaBuffer::allocate(size_of::<QueueHead>(), 64)
.map_err(|err| format!("failed to allocate async queue head: {err}"))?;
let periodic_qh = DmaBuffer::allocate(size_of::<QueueHead>(), 64)
.map_err(|err| format!("failed to allocate periodic queue head: {err}"))?;
let dma_segment = ensure_dma_segment(
has_64bit,
&[
frame_list.physical_address() as u64,
async_qh.physical_address() as u64,
periodic_qh.physical_address() as u64,
],
)?;
@@ -201,6 +210,7 @@ impl EhciController {
n_ports,
frame_list,
async_qh,
periodic_qh,
dma_segment,
has_64bit,
next_address: 1,
@@ -336,12 +346,12 @@ impl EhciController {
}
}
fn prepare_async_qh(&mut self, device_address: u8, max_packet_size: u16, first_td_phys: u32) {
fn prepare_async_qh(&mut self, device_address: u8, endpoint: u8, max_packet_size: u16, first_td_phys: u32) {
let qh_ptr = self.async_qh.as_mut_ptr() as *mut QueueHead;
unsafe {
let qh = &mut *qh_ptr;
qh.horiz_link = qh_link_pointer(self.async_qh.physical_address() as u64);
qh.caps[0] = qh_endpoint_characteristics(device_address, 0, max_packet_size, true);
qh.caps[0] = qh_endpoint_characteristics(device_address, endpoint, max_packet_size, true);
qh.caps[1] = qh_endpoint_capabilities();
qh.current_qtd = 0;
qh.overlay[0] = first_td_phys & !0x1F;
@@ -371,6 +381,30 @@ impl EhciController {
}
}
fn arm_periodic_qh(&mut self, device_address: u8, max_packet: u16, endpoint: u8, td_phys: u64) {
let fl = self.frame_list.as_mut_ptr() as *mut u32;
let qh_val = qh_endpoint_characteristics(device_address, endpoint, max_packet, false);
unsafe {
let qh_ptr = self.periodic_qh.as_mut_ptr() as *mut QueueHead;
let qh = &mut *qh_ptr;
qh.horiz_link = qh_link_pointer(self.periodic_qh.physical_address() as u64);
qh.caps[0] = qh_val;
qh.caps[1] = qh_endpoint_capabilities();
qh.current_qtd = 0;
qh.overlay[0] = (td_phys as u32) & !0x1F;
qh.overlay[1] = TD_TERMINATE;
qh.overlay[2] = 0; qh.overlay[3] = 0;
qh.overlay[4] = 0; qh.overlay[5] = 0;
qh.overlay[6] = 0; qh.overlay[7] = 0;
*fl = (self.periodic_qh.physical_address() as u32) | 2;
}
}
fn disarm_periodic_qh(&mut self) {
let fl = self.frame_list.as_mut_ptr() as *mut u32;
unsafe { *fl = TD_TERMINATE; }
}
fn ensure_controller_running(&mut self) {
let status = self.read_op32(USBSTS);
let command = self.read_op32(USBCMD);
@@ -775,7 +809,7 @@ impl EhciController {
)
.ok_or(UsbError::IoError)?;
self.prepare_async_qh(device_address, max_packet_size, first_td_phys);
self.prepare_async_qh(device_address, 0, max_packet_size, first_td_phys);
self.clear_interrupt_status();
fence(Ordering::SeqCst);
@@ -958,21 +992,72 @@ impl UsbHostController for EhciController {
fn bulk_transfer(
&mut self,
_device_address: u8,
_endpoint: u8,
_data: &mut [u8],
_direction: TransferDirection,
device_address: u8,
endpoint: u8,
data: &mut [u8],
direction: TransferDirection,
) -> Result<usize, UsbError> {
Err(UsbError::Unsupported)
let port = self.find_port_by_address(device_address).ok_or(UsbError::NoDevice)?;
let max_packet = self.ports[port].device.as_ref().map(|d| d.max_packet_size0).unwrap_or(512);
self.ensure_controller_running();
if data.len() > 0x7FFF { return Err(UsbError::Unsupported); }
let mut data_dma = if data.is_empty() { None }
else { Some(DmaBuffer::allocate(data.len(), 4096).map_err(|_| UsbError::IoError)?) };
if let Some(buf) = data_dma.as_mut() {
self.ensure_dma_segment_matches(buf.physical_address() as u64, "bulk_data")?;
if direction != TransferDirection::In { dma_write_bytes(buf, data); }
}
let mut td_dma = DmaBuffer::allocate(2 * size_of::<TransferDescriptor>(), 32).map_err(|_| UsbError::IoError)?;
self.ensure_dma_segment_matches(td_dma.physical_address() as u64, "bulk_td")?;
let tds = unsafe { std::slice::from_raw_parts_mut(td_dma.as_mut_ptr() as *mut TransferDescriptor, 2) };
let dma_phys = data_dma.as_ref().map(|b| b.physical_address() as u64).unwrap_or(0);
let first = build_bulk_transfer(dma_phys, data.len(), direction == TransferDirection::In, tds, td_dma.physical_address() as u64);
self.prepare_async_qh(device_address, endpoint, max_packet, first);
fence(Ordering::SeqCst);
for _ in 0..CONTROL_TRANSFER_TIMEOUT_POLLS {
let t0 = read_td_token(&td_dma, 0); let t1 = read_td_token(&td_dma, 1);
if (t0 | t1) & (TD_HALTED | TD_BUFERR | TD_BABBLE | TD_XACTERR | TD_MISSED) != 0 { self.disarm_async_qh(); return Err(map_td_error(t0 | t1)); }
if (t0 | t1) & TD_ACTIVE == 0 {
let rem = ((t0 & TD_TOTAL_BYTES_MASK) >> TD_TOTAL_BYTES_SHIFT) as usize;
let actual = data.len().saturating_sub(rem);
if direction == TransferDirection::In && actual > 0 { if let Some(buf) = data_dma.as_ref() { dma_read_bytes(buf, &mut data[..actual]); } }
self.disarm_async_qh(); return Ok(actual);
}
thread::sleep(WAIT_STEP);
}
self.disarm_async_qh(); Err(UsbError::Timeout)
}
fn interrupt_transfer(
&mut self,
_device_address: u8,
_endpoint: u8,
_data: &mut [u8],
device_address: u8,
endpoint: u8,
data: &mut [u8],
) -> Result<usize, UsbError> {
Err(UsbError::Unsupported)
let port = self.find_port_by_address(device_address).ok_or(UsbError::NoDevice)?;
let max_packet = self.ports[port].device.as_ref().map(|d| d.max_packet_size0).unwrap_or(64);
self.ensure_controller_running();
if data.len() > 64 { return Err(UsbError::Unsupported); }
let data_dma = DmaBuffer::allocate(data.len(), 64).map_err(|_| UsbError::IoError)?;
self.ensure_dma_segment_matches(data_dma.physical_address() as u64, "intr_data")?;
let mut td_dma = DmaBuffer::allocate(size_of::<TransferDescriptor>(), 32).map_err(|_| UsbError::IoError)?;
self.ensure_dma_segment_matches(td_dma.physical_address() as u64, "intr_td")?;
let td = unsafe { &mut *(td_dma.as_mut_ptr() as *mut TransferDescriptor) };
*td = build_intr_td(data_dma.physical_address() as u64, data.len(), td_dma.physical_address() as u64);
self.arm_periodic_qh(device_address, max_packet, endpoint, td_dma.physical_address() as u64);
fence(Ordering::SeqCst);
for _ in 0..(CONTROL_TRANSFER_TIMEOUT_POLLS / 2) {
let token = read_td_token(&td_dma, 0);
if token & (TD_HALTED | TD_BUFERR | TD_BABBLE | TD_XACTERR | TD_MISSED) != 0 { self.disarm_periodic_qh(); return Err(map_td_error(token)); }
if token & TD_ACTIVE == 0 {
let rem = ((token & TD_TOTAL_BYTES_MASK) >> TD_TOTAL_BYTES_SHIFT) as usize;
let actual = data.len().saturating_sub(rem);
if actual > 0 { dma_read_bytes(&data_dma, &mut data[..actual]); }
self.disarm_periodic_qh(); return Ok(actual);
}
thread::sleep(WAIT_STEP);
}
self.disarm_periodic_qh(); Err(UsbError::Timeout)
}
fn set_address(&mut self, device_address: u8) -> bool {
@@ -1043,6 +1128,7 @@ impl EhciScheme {
(Some("status"), None) => Ok(HandleKind::Status { port }),
(Some("descriptor"), None) => Ok(HandleKind::Descriptor { port }),
(Some("control"), None) => Ok(HandleKind::Control { port }),
(Some("config"), None) => Ok(HandleKind::Config { port }),
_ => Err(SysError::new(ENOENT)),
}
}
@@ -1052,6 +1138,7 @@ impl EhciScheme {
"status" => Ok(HandleKind::Status { port }),
"descriptor" => Ok(HandleKind::Descriptor { port }),
"control" => Ok(HandleKind::Control { port }),
"config" => Ok(HandleKind::Config { port }),
_ => Err(SysError::new(ENOENT)),
}
}
@@ -1065,6 +1152,18 @@ impl EhciScheme {
listing.into_bytes()
}
fn config_bytes(&self, port: usize) -> SysResult<Vec<u8>> {
let ctrl = self.controller.lock().map_err(|_| SysError::new(syscall::EIO))?;
let record = ctrl.ports.get(port).ok_or(SysError::new(syscall::ENOENT))?;
if let Some(ref dev) = record.device {
Ok(format!("class={:02x} subclass={:02x} vendor={:04x} device={:04x}\n",
dev.device_class, dev.device_subclass, dev.vendor_id, dev.product_id,
).into_bytes())
} else {
Ok(b"no device\n".to_vec())
}
}
fn status_bytes(&self, port: usize) -> SysResult<Vec<u8>> {
let controller = lock_controller(&self.controller);
let Some(record) = controller.port_record(port) else {
@@ -1158,10 +1257,11 @@ impl EhciScheme {
let handle = self.handle(id)?;
match &handle.kind {
HandleKind::PortDir { .. } => Ok(b"status\ndescriptor\ncontrol\n".to_vec()),
HandleKind::PortDir { .. } => Ok(b"status\ndescriptor\ncontrol\nconfig\n".to_vec()),
HandleKind::Status { port } => self.status_bytes(*port),
HandleKind::Descriptor { port } => self.descriptor_bytes(*port),
HandleKind::Control { .. } => Ok(handle.response.clone()),
HandleKind::Config { port } => self.config_bytes(*port),
}
}
@@ -1178,6 +1278,7 @@ impl EhciScheme {
format!("{SCHEME_NAME}:/port{}/descriptor", port + 1)
}
HandleKind::Control { port } => format!("{SCHEME_NAME}:/port{}/control", port + 1),
HandleKind::Config { port } => format!("{SCHEME_NAME}:/port{}/config", port + 1),
};
Ok(path)
}
@@ -1277,7 +1378,7 @@ impl SchemeSync for EhciScheme {
match self.handle(id)?.kind {
HandleKind::PortDir { .. } => MODE_DIR | 0o755,
HandleKind::Status { .. } | HandleKind::Descriptor { .. } => MODE_FILE | 0o444,
HandleKind::Control { .. } => MODE_FILE | 0o644,
HandleKind::Control { .. } | HandleKind::Config { .. } => MODE_FILE | 0o644,
}
};
@@ -328,3 +328,19 @@ pub fn build_control_transfer(
Some(setup_td_phys)
}
pub fn build_bulk_transfer(data_phys: u64, data_len: usize, dir_in: bool, tds: &mut [TransferDescriptor], td_phys: u64) -> u32 {
let pid = if dir_in { TD_PID_IN } else { TD_PID_OUT };
tds[0] = TransferDescriptor { next_qtd: 0, alt_qtd: 0, token: TD_ACTIVE | pid | ((data_len as u32 & 0x7FFF) << 16), buffers: [0; 5] };
tds[0].buffers[0] = data_phys as u32;
let next = (td_phys as u32 + size_of::<TransferDescriptor>() as u32) & !0x1F;
tds[0].next_qtd = next;
tds[1] = TransferDescriptor { next_qtd: TD_TERMINATE, alt_qtd: TD_TERMINATE, token: TD_ACTIVE | (1 << 15) | 0x8000_0000 /* IOC */, buffers: [0; 5] };
td_phys as u32
}
pub fn build_intr_td(data_phys: u64, data_len: usize, _td_phys: u64) -> TransferDescriptor {
let mut td = TransferDescriptor { next_qtd: TD_TERMINATE, alt_qtd: TD_TERMINATE, token: TD_ACTIVE | TD_PID_IN | ((data_len as u32 & 0x7FFF) << 16), buffers: [0; 5] };
td.buffers[0] = data_phys as u32;
td
}
+17 -5
View File
@@ -2,7 +2,9 @@ mod registers;
use std::env;
use std::process;
use log::{info, error, LevelFilter};
use std::fs;
use log::{info, error, warn, LevelFilter};
use registers::*;
struct StderrLogger;
impl log::Log for StderrLogger {
@@ -14,10 +16,20 @@ impl log::Log for StderrLogger {
fn main() {
log::set_logger(&StderrLogger).ok();
log::set_max_level(LevelFilter::Info);
let channel_fd: usize = match env::var("PCID_CLIENT_CHANNEL") {
Ok(s) => match s.parse() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } },
let _fd = match env::var("PCID_CLIENT_CHANNEL") {
Ok(s) => match s.parse::<usize>() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } },
Err(_) => { error!("PCID_CLIENT_CHANNEL not set"); process::exit(1); }
};
info!("OHCI USB 1.1 controller (PCI fd: {})", channel_fd);
info!("ohcid: ready");
let device_path = env::var("PCID_DEVICE_PATH").unwrap_or_default();
info!("OHCI USB 1.1 at {}", device_path);
let config_path = format!("{}/config", device_path);
match fs::read(&config_path) {
Ok(data) if data.len() >= 0x14 => {
let bar0 = u32::from_le_bytes([data[0x10], data[0x11], data[0x12], data[0x13]]);
info!("OHCI MMIO base: 0x{:08X} (BAR0)", bar0 & 0xFFFFFFF0);
info!("ohcid: MMIO detected, ready for port enumeration");
}
_ => warn!("cannot read PCI config"),
}
loop { std::thread::sleep(std::time::Duration::from_secs(10)); }
}
+17 -5
View File
@@ -2,7 +2,9 @@ mod registers;
use std::env;
use std::process;
use log::{info, error, LevelFilter};
use std::fs;
use log::{info, error, warn, LevelFilter};
use registers::*;
struct StderrLogger;
impl log::Log for StderrLogger {
@@ -14,10 +16,20 @@ impl log::Log for StderrLogger {
fn main() {
log::set_logger(&StderrLogger).ok();
log::set_max_level(LevelFilter::Info);
let channel_fd: usize = match env::var("PCID_CLIENT_CHANNEL") {
Ok(s) => match s.parse() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } },
let _fd = match env::var("PCID_CLIENT_CHANNEL") {
Ok(s) => match s.parse::<usize>() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } },
Err(_) => { error!("PCID_CLIENT_CHANNEL not set"); process::exit(1); }
};
info!("UHCI USB 1.1 controller (PCI fd: {})", channel_fd);
info!("uhcid: ready");
let device_path = env::var("PCID_DEVICE_PATH").unwrap_or_default();
info!("UHCI USB 1.1 at {}", device_path);
let config_path = format!("{}/config", device_path);
match fs::read(&config_path) {
Ok(data) if data.len() >= 0x14 => {
let bar4 = u32::from_le_bytes([data[0x20], data[0x21], data[0x22], data[0x23]]);
info!("UHCI I/O base: 0x{:04X} (BAR4)", bar4 & 0xFFE0);
info!("uhcid: I/O port detected, ready for port enumeration");
}
_ => warn!("cannot read PCI config"),
}
loop { std::thread::sleep(std::time::Duration::from_secs(10)); }
}
@@ -57,7 +57,7 @@ add_subdirectory(src)
# Enable unit testing
if (BUILD_TESTING)
# add_subdirectory(autotests)
## add_subdirectory(autotests)
add_subdirectory(tests)
endif ()
@@ -88,6 +88,7 @@ find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
# shall we use DBus?
# enabled per default on Linux & BSD systems
@@ -66,6 +66,7 @@ find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].")
+6
View File
@@ -36,12 +36,18 @@ sed -i 's/^ add_subdirectory(localedata-qml)/# add_subdirectory(localedata
rm -f CMakeCache.txt
rm -rf CMakeFiles
export LDFLAGS+=" -liconv"
cmake "${COOKBOOK_SOURCE}" \
-DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \
-DQT_HOST_PATH="${HOST_BUILD}" \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}" \
-DCMAKE_C_STANDARD_LIBRARIES="-liconv" \
-DCMAKE_CXX_STANDARD_LIBRARIES="-liconv" \
-DCMAKE_SHARED_LINKER_FLAGS="-liconv" \
-DCMAKE_EXE_LINKER_FLAGS="-liconv" \
-DBUILD_TESTING=OFF \
-DBUILD_QCH=OFF \
-DBUILD_WITH_QML=OFF \
@@ -38,7 +38,7 @@ set_package_properties(Qt6Qml PROPERTIES
)
if (TARGET Qt6::Qml)
### include(ECMQmlModule)
#### include(ECMQmlModule)
endif()
set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].")
@@ -1,6 +1,6 @@
add_subdirectory(core)
if (TARGET Qt6::Qml)
## add_subdirectory(qml)
### add_subdirectory(qml)
endif()
ecm_qt_install_logging_categories(
@@ -39,6 +39,9 @@ find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED)
set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].")
@@ -41,12 +41,16 @@ sed -i '/^find_package(Wayland 1.15/a find_package(Qt6WaylandClientPrivate REQUI
rm -f CMakeCache.txt
rm -rf CMakeFiles
export LDFLAGS+=" -lffi"
cmake "${COOKBOOK_SOURCE}" \
-DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \
-DQT_HOST_PATH="${HOST_BUILD}" \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}" \
-DCMAKE_SHARED_LINKER_FLAGS="-lffi" \
-DCMAKE_EXE_LINKER_FLAGS="-lffi" \
-DWaylandScanner_EXECUTABLE=/usr/bin/wayland-scanner \
-DWaylandProtocols_DATADIR="${COOKBOOK_SYSROOT}/share/wayland-protocols" \
-DPLASMA_WAYLAND_PROTOCOLS_DIR="${COOKBOOK_SYSROOT}/share/plasma-wayland-protocols" \
@@ -68,6 +68,10 @@ find_package(Qt6WaylandClientPrivate REQUIRED)
find_package(Qt6WaylandClientPrivate REQUIRED)
find_package(Qt6WaylandClientPrivate REQUIRED)
find_package(Qt6WaylandClientPrivate REQUIRED)
find_package(Qt6WaylandClientPrivate REQUIRED)
find_package(Qt6WaylandClientPrivate REQUIRED)
find_package(Qt6WaylandClientPrivate REQUIRED)
find_package(Qt6WaylandClientPrivate REQUIRED)
set_package_properties(Wayland PROPERTIES
TYPE REQUIRED
)
@@ -0,0 +1,4 @@
#clang-format
7dd564adbcfc6151473c97a3a7737cfaef5694b7
#clang-tidy
4c41ac844a27089083578eea1b947d7bbbd517c7
@@ -0,0 +1,28 @@
# Ignore the following files
*~
*.[oa]
*.diff
*.kate-swp
*.kdev4
.kdev_include_paths
*.kdevelop.pcs
*.moc
*.moc.cpp
*.orig
*.user
.*.swp
.swp.*
Doxyfile
Makefile
avail
random_seed
/build*/
CMakeLists.txt.user*
*.unc-backup*
.cmake/
/.clang-format
/compile_commands.json
.clangd
.idea
/cmake-build*
.cache
@@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: CC0-1.0
include:
- project: sysadmin/ci-utilities
file:
- /gitlab-templates/linux-qt6.yml
- /gitlab-templates/linux-qt6-static.yml
- /gitlab-templates/android-qt6.yml
- /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/windows-qt6.yml
- /gitlab-templates/alpine-qt6.yml
@@ -0,0 +1,9 @@
Dependencies:
- 'on': ['Linux', 'FreeBSD', 'Windows', 'Android']
'require':
'frameworks/extra-cmake-modules': '@same'
'third-party/zxing-cpp': '@latest'
Options:
test-before-installing: True
require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ]
@@ -0,0 +1,7 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: KF5::Prison
Source: https://invent.kde.org/frameworks/prison
Files: autotests/aztec/encoding/*.png autotests/aztec/rendering/*.png autotests/code128/*.png autotests/datamatrix/*.png autotests/qr/*.png
Copyright: none
License: CC0-1.0
@@ -0,0 +1,121 @@
cmake_minimum_required(VERSION 3.16)
set(KF_VERSION "6.10.0") # handled by release scripts
project(prison VERSION ${KF_VERSION})
# ECM setup
include(FeatureSummary)
find_package(ECM 6.10.0 NO_MODULE)
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" "${ECM_MODULE_PATH}")
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDEGitCommitHooks)
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
include(GenerateExportHeader)
include(ECMGenerateHeaders)
include(ECMGenerateExportHeader)
include(ECMAddQch)
include(ECMAddTests)
include(CMakePackageConfigHelpers)
include(ECMSetupVersion)
include(ECMQtDeclareLoggingCategory)
include(ECMDeprecationSettings)
set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].")
option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF)
add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)")
set(REQUIRED_QT_VERSION 6.6.0)
option(WITH_QUICK "Build QtQuick components" ON)
option(WITH_MULTIMEDIA "Build scanner support" ON)
find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Core Gui)
if (WITH_QUICK)
include(ECMQmlModule)
find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Quick)
endif()
if (WITH_MULTIMEDIA)
find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Multimedia)
endif()
find_package(QRencode)
set_package_properties(QRencode PROPERTIES
PURPOSE "Required for generation of QRCode barcodes."
TYPE REQUIRED)
find_package(Dmtx)
set_package_properties(Dmtx PROPERTIES
PURPOSE "Required for generation of Data Matrix barcodes."
TYPE RECOMMENDED)
find_package(ZXing 2.0)
if (NOT TARGET ZXing::ZXing)
find_package(ZXing 1.2.0)
endif()
set_package_properties(ZXing PROPERTIES
PURPOSE "Required for generation of PDF417 barcodes and for scanning of barcodes from live video feed."
TYPE RECOMMENDED)
ecm_setup_version(PROJECT VARIABLE_PREFIX PRISON
VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/prison_version.h"
PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF6PrisonConfigVersion.cmake"
SOVERSION 6
)
ecm_set_disabled_deprecation_versions(
QT 6.8
)
add_subdirectory(src)
if(BUILD_TESTING)
find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Test Concurrent Widgets)
add_subdirectory(autotests)
add_subdirectory(tests)
endif()
set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF6Prison")
if (BUILD_QCH)
ecm_install_qch_export(
TARGETS KF6Prison_QCH
FILE KF6PrisonQchTargets.cmake
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF6PrisonQchTargets.cmake\")")
endif()
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/KF6PrisonConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/KF6PrisonConfig.cmake"
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/KF6PrisonConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/KF6PrisonConfigVersion.cmake"
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
install(EXPORT KF6PrisonTargets
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
FILE KF6PrisonTargets.cmake
NAMESPACE KF6::
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/prison_version.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/Prison
COMPONENT Devel
)
include(ECMFeatureSummary)
ecm_feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
@@ -0,0 +1,7 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
find_dependency(Qt6Gui @REQUIRED_QT_VERSION@)
include("${CMAKE_CURRENT_LIST_DIR}/KF6PrisonTargets.cmake")
@PACKAGE_INCLUDE_QCHTARGETS@
@@ -0,0 +1,26 @@
Copyright (c) <year> <owner>. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
@@ -0,0 +1,19 @@
MIT License Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,54 @@
# Prison
A barcode abstraction layer providing uniform access to generation of barcodes with data.
## Introduction
Prison has a Prison::AbstractBarcode, which is the base class for the actual
barcode generators. Prison currently implements barcode generators for the following formats:
- QRCode
- Data Matrix
- Aztec
- Code39
- Code93
- [Code128](https://en.wikipedia.org/wiki/Code_128)
- [PDF417](https://en.wikipedia.org/wiki/PDF417) (ISO/IEC 15438)
- [EAN13](https://en.wikipedia.org/wiki/International_Article_Number)
Prison currently ships the org.kde.prison.Barcode QML element that can be used to render barcodes in QML code.
## Supported Barcode types
There are basically two types of barcodes:
* barcodes that carry the data
* barcodes that carry a lookup number, and require a specific server to
look up the actual data.
Prison isn't as such designed for the latter, it will probably work, but
patches implementing barcode support for such barcodes will not be accepted.
An example is [EZCode](https://en.wikipedia.org/wiki/EZcode).
Prison is currently using [libdmtx](https://github.com/dmtx/libdmtx) for generation of
[DataMatrix](https://en.wikipedia.org/wiki/Datamatrix) barcodes,
[libqrencode](https://fukuchi.org/works/qrencode/) for generation
of [QRCode](https://en.wikipedia.org/wiki/QR_Code) barcodes and
[ZXing](https://github.com/nu-book/zxing-cpp) for generating
[EAN13 and PDF417](https://en.wikipedia.org/wiki/PDF417) barcodes.
# Prison Scanner
A barcode scanner consuming a live video feed from QtMultimedia.
## Introduction
Prison's barcode scanner can be used from C++ code using the Prison::VideoScanner
class, or from QML using the org.kde.prison.scanner.VideoScanner QML element.
There are standalone QML examples in tests/scanner-qt(5|6).qml demonstrating
the QML API.
## Supported barcode formats
Barcode detection is implemented using the [ZXing](https://github.com/nu-book/zxing-cpp)
library, all formats supported by ZXing can be detected.
@@ -0,0 +1,52 @@
# SPDX-FileCopyrightText: 2017-2018 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: BSD-3-Clause
set(aztecbarcodetest_srcs
aztecbarcodetest.cpp
aztec/aztec.qrc
../src/lib/aztecbarcode.cpp
../src/lib/abstractbarcode_p.cpp
../src/lib/barcodeutil.cpp
../src/lib/bitvector.cpp
../src/lib/reedsolomon.cpp
${CMAKE_CURRENT_BINARY_DIR}/../src/lib/prison_debug.cpp
)
ecm_add_test(${aztecbarcodetest_srcs} TEST_NAME prison-aztecbarcodetest LINK_LIBRARIES Qt6::Test KF6::Prison)
ecm_add_test(
reedsolomontest.cpp
../src/lib/bitvector.cpp
../src/lib/reedsolomon.cpp
TEST_NAME prison-reedsolomontest
LINK_LIBRARIES Qt6::Test KF6::Prison
)
set(code128barcodetest_srcs
code128barcodetest.cpp
code128/code128.qrc
../src/lib/abstractbarcode_p.cpp
../src/lib/barcodeutil.cpp
../src/lib/code128barcode.cpp
../src/lib/bitvector.cpp
${CMAKE_CURRENT_BINARY_DIR}/../src/lib/prison_debug.cpp
)
ecm_add_test(${code128barcodetest_srcs} TEST_NAME prison-code128barcodetest LINK_LIBRARIES Qt6::Test KF6::Prison)
if(TARGET Dmtx::Dmtx)
ecm_add_test(datamatrixtest.cpp datamatrix/datamatrix.qrc TEST_NAME prison-datamatrixtest LINK_LIBRARIES Qt6::Test KF6::Prison)
endif()
ecm_add_test(qrtest.cpp qr/qr.qrc TEST_NAME prison-qrtest LINK_LIBRARIES Qt6::Test KF6::Prison)
if (TARGET ZXing::ZXing)
ecm_add_test(zxingutiltest.cpp ${PROJECT_SOURCE_DIR}/src/lib/zxingutil.cpp TEST_NAME prison-zxingutiltest LINK_LIBRARIES Qt6::Test ZXing::ZXing KF6::Prison)
kde_source_files_enable_exceptions(${PROJECT_SOURCE_DIR}/src/lib/zxingutil.cpp )
ecm_add_test(imagescannertest.cpp qr/qr.qrc TEST_NAME prison-imagescannertest LINK_LIBRARIES Qt6::Test Qt6::Concurrent KF6::PrisonScanner)
endif()
ecm_add_test(
mecardtest.cpp
TEST_NAME prison-mecardtest
LINK_LIBRARIES Qt6::Test KF6::Prison
)
@@ -0,0 +1,34 @@
<!--
SPDX-FileCopyrightText: none
SPDX-License-Identifier: CC0-1.0
-->
<RCC>
<qresource prefix="/aztec/">
<file>rendering/aztec-full-grid.png</file>
<file>rendering/aztec-full-data-0011.png</file>
<file>rendering/aztec-full-data-0101.png</file>
<file>rendering/aztec-full-data-1001.png</file>
<file>rendering/aztec-full-data-1010.png</file>
<file>rendering/aztec-full-data-1111.png</file>
<file>rendering/aztec-full-mode-1111.png</file>
<file>rendering/aztec-full-mode-1234.png</file>
<file>rendering/aztec-full-mode-1234-rev.png</file>
<file>rendering/aztec-compact-grid.png</file>
<file>rendering/aztec-compact-data-0011.png</file>
<file>rendering/aztec-compact-data-0101.png</file>
<file>rendering/aztec-compact-data-1001.png</file>
<file>rendering/aztec-compact-data-1010.png</file>
<file>rendering/aztec-compact-data-1111.png</file>
<file>rendering/aztec-compact-mode-1111.png</file>
<file>rendering/aztec-compact-mode-1234.png</file>
<file>rendering/aztec-compact-mode-1234-rev.png</file>
<file>encoding/aztec-complete-compact1.png</file>
<file>encoding/aztec-complete-compact3.png</file>
<file>encoding/aztec-complete-compact4.png</file>
<file>encoding/aztec-complete-full5.png</file>
<file>encoding/aztec-complete-big.png</file>
<file>encoding/aztec-binary.png</file>
</qresource>
</RCC>
Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

@@ -0,0 +1,528 @@
/*
SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include "../src/lib/aztecbarcode_p.h"
#include "../src/lib/bitvector_p.h"
#include <prison.h>
#include <QImage>
#include <QObject>
#include <QTest>
#include <memory>
Q_DECLARE_METATYPE(Prison::BitVector)
using namespace Prison;
class AztecBarcodeTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testAztecEncode_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<BitVector>("output");
QTest::newRow("empty") << QByteArray() << BitVector();
BitVector v;
v.appendMSB(12, 5);
v.appendMSB(5, 5);
v.appendMSB(6, 5);
QTest::newRow("all uppper") << QByteArray("KDE") << v;
v.clear();
v.appendMSB(28, 5);
v.appendMSB(12, 5);
v.appendMSB(5, 5);
v.appendMSB(6, 5);
QTest::newRow("all lower") << QByteArray("kde") << v;
v.clear();
v.appendMSB(12, 5);
v.appendMSB(28, 5);
v.appendMSB(5, 5);
v.appendMSB(6, 5);
QTest::newRow("upper -> lower latch") << QByteArray("Kde") << v;
v.clear();
v.appendMSB(28, 5);
v.appendMSB(2, 5);
v.appendMSB(29, 5);
v.appendMSB(30, 5);
v.appendMSB(16, 5);
v.appendMSB(16, 5);
QTest::newRow("lower -> punct latch") << QByteArray("a++") << v;
v.clear();
v.appendMSB(30, 5);
v.appendMSB(6, 4);
v.appendMSB(4, 4);
QTest::newRow("digit") << QByteArray("42") << v;
v.clear();
v.appendMSB(29, 5);
v.appendMSB(30, 5);
v.appendMSB(25, 5);
v.appendMSB(24, 5);
v.appendMSB(31, 5);
v.appendMSB(30, 5);
v.appendMSB(11, 4);
v.appendMSB(2, 4);
QTest::newRow("punct -> digit latch") << QByteArray(">=90") << v;
v.clear();
v.appendMSB(30, 5);
v.appendMSB(10, 4);
v.appendMSB(3, 4);
v.appendMSB(14, 4);
v.appendMSB(29, 5);
v.appendMSB(30, 5);
v.appendMSB(13, 5);
v.appendMSB(14, 5);
QTest::newRow("digit -> punct latch") << QByteArray("81()") << v;
v.clear();
v.appendMSB(29, 5);
v.appendMSB(11, 5);
v.appendMSB(8, 5);
QTest::newRow("mixed") << QByteArray("\n\a") << v;
v.clear();
v.appendMSB(29, 5);
v.appendMSB(30, 5);
v.appendMSB(2, 5);
v.appendMSB(2, 5);
QTest::newRow("CR LF") << QByteArray("\r\n\r\n") << v;
v.clear();
v.appendMSB(31, 5);
v.appendMSB(2, 5);
v.appendMSB(128, 8);
v.appendMSB(129, 8);
QTest::newRow("binary") << QByteArray("\x80\x81") << v;
v.clear();
v.appendMSB(31, 5);
v.appendMSB(2, 5);
v.appendMSB(255, 8);
v.appendMSB(254, 8);
v.appendMSB(28, 5);
v.appendMSB(3, 5);
QTest::newRow("binary/lower") << QByteArray(
"\xff\xfe"
"b") << v;
v.clear();
v.appendMSB(12, 5);
v.appendMSB(0, 5);
v.appendMSB(6, 5);
QTest::newRow("upper -> punct shift") << QByteArray("K!") << v;
v.clear();
v.appendMSB(30, 5);
v.appendMSB(9, 4);
v.appendMSB(4, 4);
v.appendMSB(15, 4);
v.appendMSB(6, 5);
v.appendMSB(5, 4);
QTest::newRow("digit -> upper shift") << QByteArray("72E3") << v;
v.clear();
v.appendMSB(25, 5);
v.appendMSB(1, 5);
v.appendMSB(26, 5);
QTest::newRow("upper space") << QByteArray("X Y") << v;
v.clear();
v.appendMSB(20, 5);
v.appendMSB(0, 5);
v.appendMSB(4, 5);
v.appendMSB(23, 5);
QTest::newRow("upper punct double char shift") << QByteArray("S, V") << v;
v.clear();
v.appendMSB(17, 5);
v.appendMSB(0, 5);
v.appendMSB(17, 5);
v.appendMSB(18, 5);
QTest::newRow("upper ambiguous punct shift") << QByteArray("P,Q") << v;
v.clear();
v.appendMSB(30, 5);
v.appendMSB(13, 4);
v.appendMSB(7, 4);
QTest::newRow("digit ambiguous punct latch") << QByteArray(".5") << v;
v.clear();
v.appendMSB(29, 5);
v.appendMSB(30, 5);
v.appendMSB(25, 5);
v.appendMSB(26, 5);
v.appendMSB(31, 5);
v.appendMSB(29, 5);
v.appendMSB(20, 5);
v.appendMSB(29, 5);
v.appendMSB(2, 5);
QTest::newRow("punct/mixed/upper sequence") << QByteArray(">?@A") << v;
v.clear();
v.appendMSB(2, 5);
v.appendMSB(3, 5);
v.appendMSB(4, 5);
v.appendMSB(29, 5); // latch to Punct via latch to Mixed, shift to Punct directly would be more efficient here
v.appendMSB(30, 5);
v.appendMSB(19, 5);
v.appendMSB(31, 5); // latch to Upper due to shift to Binary not available in Punct
v.appendMSB(31, 5);
v.appendMSB(2, 5);
v.appendMSB(0, 8);
v.appendMSB(0, 8);
QTest::newRow("upper/special -> binary") << QByteArray("ABC.\x00\x00", 6) << v;
}
void testAztecEncode()
{
QFETCH(QByteArray, input);
QFETCH(BitVector, output);
AztecBarcode code;
const auto v = code.aztecEncode(input);
if (v != output) {
qDebug() << "Actual :" << v;
qDebug() << "Expected:" << output;
}
QCOMPARE(v, output);
}
void testStuffAndPad_data()
{
QTest::addColumn<BitVector>("input");
QTest::addColumn<BitVector>("output");
QTest::addColumn<int>("codeWordSize");
BitVector in;
BitVector out;
QTest::newRow("empty") << in << out << 4;
in.appendMSB(0x2, 2);
out.appendMSB(0xB, 4);
QTest::newRow("pad only") << in << out << 4;
in.clear();
out.clear();
in.appendMSB(0x3, 2);
out.appendMSB(0xE, 4);
QTest::newRow("pad only inverted") << in << out << 4;
in.clear();
out.clear();
in.appendMSB(0xe0, 8);
out.appendMSB(0xe13, 12);
QTest::newRow("stuff and pad") << in << out << 4;
in.clear();
out.clear();
in.appendMSB(0, 6);
out.appendMSB(0x11, 8);
QTest::newRow("stuff only") << in << out << 4;
}
void testStuffAndPad()
{
QFETCH(BitVector, input);
QFETCH(BitVector, output);
QFETCH(int, codeWordSize);
AztecBarcode code;
const auto res = code.bitStuffAndPad(input, codeWordSize);
QCOMPARE(res.size(), output.size());
if (res != output) {
qDebug() << "Actual :" << res;
qDebug() << "Expected:" << output;
}
QCOMPARE(res, output);
}
void testFullGrid()
{
AztecBarcode code;
QImage img(151, 151, QImage::Format_ARGB32_Premultiplied);
img.fill(code.m_background);
code.paintFullGrid(&img);
QImage ref(QStringLiteral(":/aztec/rendering/aztec-full-grid.png"));
ref = ref.convertToFormat(img.format());
QCOMPARE(img, ref);
}
void testCompactGrid()
{
AztecBarcode code;
QImage img(27, 27, QImage::Format_ARGB32_Premultiplied);
img.fill(code.m_background);
code.paintCompactGrid(&img);
img.save(QStringLiteral("aztec-compact-grid.png"));
QImage ref(QStringLiteral(":/aztec/rendering/aztec-compact-grid.png"));
ref = ref.convertToFormat(img.format());
QCOMPARE(img, ref);
}
void testFullData_data()
{
QTest::addColumn<BitVector>("data");
QTest::addColumn<QString>("refName");
QTest::addColumn<int>("layer");
BitVector v;
for (int i = 0; i < 1248; ++i) {
v.appendLSB(0x9249, 16);
}
QTest::newRow("1001-31") << v << QStringLiteral("aztec-full-data-1001.png") << 32;
v.clear();
for (int i = 0; i < 1248 * 8; ++i) {
v.appendLSB(0x2, 2);
}
QTest::newRow("0101-31") << v << QStringLiteral("aztec-full-data-0101.png") << 32;
v.clear();
for (int i = 0; i < 1248; ++i) {
v.appendLSB(0xffff, 16);
}
QTest::newRow("1111-31") << v << QStringLiteral("aztec-full-data-1111.png") << 32;
v.clear();
for (int i = 0; i < 704 * 4; ++i) {
v.appendLSB(0x1, 2);
}
QTest::newRow("1010-15") << v << QStringLiteral("aztec-full-data-1010.png") << 16;
v.clear();
for (int i = 0; i < 16; ++i) {
v.appendLSB(0xCC, 8);
}
QTest::newRow("0011-0") << v << QStringLiteral("aztec-full-data-0011.png") << 1;
}
void testFullData()
{
QFETCH(BitVector, data);
QFETCH(QString, refName);
QFETCH(int, layer);
AztecBarcode code;
QImage img(151, 151, QImage::Format_ARGB32_Premultiplied);
img.fill(code.m_background);
code.paintFullData(&img, data, layer);
img.save(refName);
QImage ref(QStringLiteral(":/aztec/rendering/") + refName);
ref = ref.convertToFormat(img.format());
QCOMPARE(img, ref);
}
void testFullModeMessage_data()
{
QTest::addColumn<BitVector>("data");
QTest::addColumn<QString>("refName");
BitVector v;
for (int i = 0; i < 8; ++i) {
v.appendMSB(i + 1, 5);
}
QTest::newRow("1234") << v << QStringLiteral("aztec-full-mode-1234.png");
v.clear();
for (int i = 0; i < 8; ++i) {
v.appendLSB(i + 1, 5);
}
QTest::newRow("1234-rev") << v << QStringLiteral("aztec-full-mode-1234-rev.png");
v.clear();
for (int i = 0; i < 4; ++i) {
v.appendMSB(0xffff, 10);
}
QTest::newRow("1111") << v << QStringLiteral("aztec-full-mode-1111.png");
}
void testFullModeMessage()
{
QFETCH(BitVector, data);
QFETCH(QString, refName);
AztecBarcode code;
QImage img(151, 151, QImage::Format_ARGB32_Premultiplied);
img.fill(code.m_background);
code.paintFullModeMessage(&img, data);
img.save(refName);
QImage ref(QStringLiteral(":/aztec/rendering/") + refName);
ref = ref.convertToFormat(img.format());
QCOMPARE(img, ref);
}
void testCompactData_data()
{
QTest::addColumn<BitVector>("data");
QTest::addColumn<QString>("refName");
QTest::addColumn<int>("layer");
BitVector v;
for (int i = 0; i < 304; ++i) {
v.appendLSB(0x9249, 16);
}
QTest::newRow("1001-3") << v << QStringLiteral("aztec-compact-data-1001.png") << 4;
v.clear();
for (int i = 0; i < 608 * 4; ++i) {
v.appendLSB(0x2, 2);
}
QTest::newRow("0101-3") << v << QStringLiteral("aztec-compact-data-0101.png") << 4;
v.clear();
for (int i = 0; i < 304; ++i) {
v.appendLSB(0xffff, 16);
}
QTest::newRow("1111-3") << v << QStringLiteral("aztec-compact-data-1111.png") << 4;
v.clear();
for (int i = 0; i < 102 * 4; ++i) {
v.appendLSB(0x1, 2);
}
QTest::newRow("1010-2") << v << QStringLiteral("aztec-compact-data-1010.png") << 3;
v.clear();
for (int i = 0; i < 13; ++i) {
v.appendLSB(0xCC, 8);
}
QTest::newRow("0011-0") << v << QStringLiteral("aztec-compact-data-0011.png") << 1;
}
void testCompactData()
{
QFETCH(BitVector, data);
QFETCH(QString, refName);
QFETCH(int, layer);
AztecBarcode code;
QImage img(27, 27, QImage::Format_ARGB32_Premultiplied);
img.fill(code.m_background);
code.paintCompactData(&img, data, layer);
img.save(refName);
QImage ref(QStringLiteral(":/aztec/rendering/") + refName);
ref = ref.convertToFormat(img.format());
QCOMPARE(img, ref);
}
void testCompactModeMessage_data()
{
QTest::addColumn<BitVector>("data");
QTest::addColumn<QString>("refName");
BitVector v;
for (int i = 0; i < 4; ++i) {
v.appendMSB(i + 1, 7);
}
QTest::newRow("1234") << v << QStringLiteral("aztec-compact-mode-1234.png");
v.clear();
for (int i = 0; i < 4; ++i) {
v.appendLSB(i + 1, 7);
}
QTest::newRow("1234-rev") << v << QStringLiteral("aztec-compact-mode-1234-rev.png");
v.clear();
for (int i = 0; i < 4; ++i) {
v.appendMSB(0xffff, 7);
}
QTest::newRow("1111") << v << QStringLiteral("aztec-compact-mode-1111.png");
}
void testCompactModeMessage()
{
QFETCH(BitVector, data);
QFETCH(QString, refName);
AztecBarcode code;
QImage img(151, 151, QImage::Format_ARGB32_Premultiplied);
img.fill(code.m_background);
code.paintCompactModeMessage(&img, data);
img.save(refName);
QImage ref(QStringLiteral(":/aztec/rendering/") + refName);
ref = ref.convertToFormat(img.format());
QCOMPARE(img, ref);
}
void testCodeGen_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<QString>("refName");
QTest::newRow("short compact") << QByteArray("KF5::Prison") << "aztec-complete-compact1.png";
QTest::newRow("compact 3 layer") << QByteArray("M1KRAUSE/VOLKER ABCDEFG TXLRIXBT 0212 309Y014E0063 100") << "aztec-complete-compact3.png";
QTest::newRow("long compact") << QByteArray("KF5::Prison - the barcode generation library of KDE Frameworks 5!") << "aztec-complete-compact4.png";
QTest::newRow("short full") << QByteArray("KF5::Prison - the MIT licensed free software barcode generation library of KDE Frameworks 5!")
<< "aztec-complete-full5.png";
QTest::newRow("long full") << QByteArray(
"Permission is hereby granted, free of charge, to any person\n"
"obtaining a copy of this software and associated documentation\n"
"files (the \"Software\"), to deal in the Software without\n"
"restriction, including without limitation the rights to use,\n"
"copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
"copies of the Software, and to permit persons to whom the\n"
"Software is furnished to do so, subject to the following\n"
"conditions:\n\n"
"The above copyright notice and this permission notice shall be\n"
"included in all copies or substantial portions of the Software.\n\n"
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
"EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n"
"OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
"NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n"
"HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n"
"WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
"FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
"OTHER DEALINGS IN THE SOFTWARE.")
<< "aztec-complete-big.png";
QTest::newRow("binary") << QByteArray("KDE\x0\x1\x2\x3\x4\x5\x6\x7\x8\x9kde", 16) << "aztec-binary.png";
}
void testCodeGen()
{
QFETCH(QByteArray, input);
QFETCH(QString, refName);
{
auto code = Prison::Barcode::create(Prison::Aztec);
QVERIFY(code);
code->setData(QString::fromLatin1(input.constData(), input.size()));
const auto img = code->toImage(code->minimumSize());
img.save(refName);
QImage ref(QStringLiteral(":/aztec/encoding/") + refName);
ref = ref.convertToFormat(img.format());
QCOMPARE(img, ref);
}
{
auto code = Prison::Barcode::create(Prison::Aztec);
QVERIFY(code);
code->setData(input);
const auto img = code->toImage(code->minimumSize());
img.save(refName);
QImage ref(QStringLiteral(":/aztec/encoding/") + refName);
ref = ref.convertToFormat(img.format());
QCOMPARE(img, ref);
}
}
void testDimension()
{
auto barcode = Prison::Barcode::create(Prison::Aztec);
QVERIFY(barcode);
QCOMPARE(barcode->format(), Prison::Aztec);
QCOMPARE(barcode->dimensions(), Prison::Barcode::TwoDimensions);
}
void testSize()
{
auto barcode = Prison::Barcode::create(Prison::Aztec);
QVERIFY(barcode);
QCOMPARE(barcode->format(), Prison::Aztec);
barcode->setData(QStringLiteral("UNIT TEST"));
QCOMPARE(barcode->minimumSize(), QSize(15, 15));
QCOMPARE(barcode->preferredSize(1), QSize(60, 60));
QCOMPARE(barcode->preferredSize(2), QSize(30, 30));
QCOMPARE(barcode->toImage(barcode->preferredSize(1)).size(), QSize(60, 60));
QCOMPARE(barcode->toImage({1, 1}).isNull(), true);
}
};
QTEST_APPLESS_MAIN(AztecBarcodeTest)
#include "aztecbarcodetest.moc"
Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

@@ -0,0 +1,10 @@
<!--
SPDX-FileCopyrightText: none
SPDX-License-Identifier: CC0-1.0
-->
<RCC>
<qresource prefix="/code128/">
<file>code128-text.png</file>
<file>code128-binary.png</file>
</qresource>
</RCC>
@@ -0,0 +1,240 @@
/*
SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include "../src/lib/bitvector_p.h"
#include "../src/lib/code128barcode_p.h"
#include <prison.h>
#include <QObject>
#include <QTest>
Q_DECLARE_METATYPE(Prison::BitVector)
using namespace Prison;
class Code128BarcodeTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testEncode_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<BitVector>("output");
BitVector v;
QTest::newRow("empty") << QByteArray() << v;
v.appendMSB(1680, 11);
v.appendMSB(1554, 11);
v.appendMSB(1062, 11);
v.appendMSB(1424, 11);
v.appendMSB(1220, 11);
v.appendMSB(6379, 13);
QTest::newRow("all lower") << QByteArray("kde") << v;
v.clear();
v.appendMSB(1680, 11);
v.appendMSB(1422, 11);
v.appendMSB(1416, 11);
v.appendMSB(1128, 11);
v.appendMSB(1764, 11);
v.appendMSB(6379, 13);
QTest::newRow("all uppper") << QByteArray("KDE") << v;
v.clear();
v.appendMSB(1680, 11);
v.appendMSB(1614, 11);
v.appendMSB(1764, 11);
v.appendMSB(6379, 13);
QTest::newRow("1 digit") << QByteArray("4") << v;
v.clear();
v.appendMSB(1692, 11);
v.appendMSB(1464, 11);
v.appendMSB(1134, 11);
v.appendMSB(6379, 13);
QTest::newRow("2 digits") << QByteArray("42") << v;
v.clear();
v.appendMSB(1680, 11);
v.appendMSB(1614, 11);
v.appendMSB(1650, 11);
v.appendMSB(1650, 11);
v.appendMSB(1124, 11);
v.appendMSB(6379, 13);
QTest::newRow("3 digits") << QByteArray("422") << v;
v.clear();
v.appendMSB(1692, 11);
v.appendMSB(1464, 11);
v.appendMSB(1902, 11);
v.appendMSB(1782, 11);
v.appendMSB(6379, 13);
QTest::newRow("4 digits") << QByteArray("4223") << v;
v.clear();
v.appendMSB(1680, 11);
v.appendMSB(1814, 11);
v.appendMSB(1260, 11);
v.appendMSB(1260, 11);
v.appendMSB(1896, 11);
v.appendMSB(1814, 11);
v.appendMSB(6379, 13);
QTest::newRow("mixed") << QByteArray("X00Y") << v;
v.clear();
v.appendMSB(1668, 11);
v.appendMSB(1292, 11);
v.appendMSB(1292, 11);
v.appendMSB(6379, 13);
QTest::newRow("null") << QByteArray("\0", 1) << v;
v.clear();
v.appendMSB(1668, 11);
v.appendMSB(1422, 11);
v.appendMSB(1416, 11);
v.appendMSB(1128, 11);
v.appendMSB(1292, 11);
v.appendMSB(1412, 11);
v.appendMSB(6379, 13);
QTest::newRow("Code A only") << QByteArray("KDE\0", 4) << v;
v.clear();
v.appendMSB(1668, 11);
v.appendMSB(1292, 11);
v.appendMSB(1518, 11);
v.appendMSB(1554, 11);
v.appendMSB(1062, 11);
v.appendMSB(1424, 11);
v.appendMSB(1616, 11);
v.appendMSB(6379, 13);
QTest::newRow("Start A -> Latch B") << QByteArray("\0kde", 4) << v;
v.clear();
v.appendMSB(1680, 11);
v.appendMSB(1554, 11);
v.appendMSB(1062, 11);
v.appendMSB(1886, 11);
v.appendMSB(1292, 11);
v.appendMSB(1292, 11);
v.appendMSB(1602, 11);
v.appendMSB(6379, 13);
QTest::newRow("Start B -> Latch A") << QByteArray("kd\0\0", 4) << v;
v.clear();
v.appendMSB(1668, 11);
v.appendMSB(1292, 11);
v.appendMSB(1954, 11);
v.appendMSB(1118, 11);
v.appendMSB(1590, 11);
v.appendMSB(1292, 11);
v.appendMSB(1328, 11);
v.appendMSB(6379, 13);
QTest::newRow("Start A -> Shift B") << QByteArray("\0~@\0", 4) << v;
v.clear();
v.appendMSB(1680, 11);
v.appendMSB(1974, 11);
v.appendMSB(1954, 11);
v.appendMSB(1292, 11);
v.appendMSB(1310, 11);
v.appendMSB(1844, 11);
v.appendMSB(6379, 13);
QTest::newRow("Start B -> Shift A") << QByteArray("{\0}", 3) << v;
v.clear();
v.appendMSB(1692, 11);
v.appendMSB(1436, 11);
v.appendMSB(1112, 11);
v.appendMSB(1518, 11);
v.appendMSB(1304, 11);
v.appendMSB(1112, 11);
v.appendMSB(1158, 11);
v.appendMSB(6379, 13);
QTest::newRow("Start C -> Latch B") << QByteArray("1234AB") << v;
v.clear();
v.appendMSB(1680, 11);
v.appendMSB(1304, 11);
v.appendMSB(1112, 11);
v.appendMSB(1502, 11);
v.appendMSB(1436, 11);
v.appendMSB(1112, 11);
v.appendMSB(1966, 11);
v.appendMSB(6379, 13);
QTest::newRow("Start B -> Latch C") << QByteArray("AB1234") << v;
}
void testEncode()
{
QFETCH(QByteArray, input);
QFETCH(BitVector, output);
Code128Barcode code;
const auto v = code.encode(input);
if (v != output) {
qDebug() << "Actual :" << v;
qDebug() << "Expected:" << output;
}
QCOMPARE(v, output);
}
void testDimension()
{
auto barcode = Prison::Barcode::create(Prison::Code128);
QVERIFY(barcode);
QCOMPARE(barcode->format(), Prison::Code128);
QCOMPARE(barcode->dimensions(), Prison::Barcode::OneDimension);
}
void testSize()
{
auto barcode = Prison::Barcode::create(Prison::Code128);
QVERIFY(barcode);
QCOMPARE(barcode->format(), Prison::Code128);
barcode->setData(QStringLiteral("UNIT TEST"));
QCOMPARE(barcode->minimumSize(), QSize(154, 1));
QCOMPARE(barcode->preferredSize(1), QSize(308, 50));
QCOMPARE(barcode->preferredSize(2), QSize(154, 50));
QCOMPARE(barcode->toImage(barcode->preferredSize(1)).size(), QSize(308, 50));
QCOMPARE(barcode->toImage({1, 1}).isNull(), true);
}
void testRender_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<QString>("refName");
QTest::newRow("text") << QByteArray("KF5::Prison") << "code128-text.png";
QTest::newRow("binary") << QByteArray("KDE\x0\x1\x2\x3\x4\x5\x6\x7\x8\x9kde", 16) << "code128-binary.png";
}
void testRender()
{
QFETCH(QByteArray, input);
QFETCH(QString, refName);
{
auto code = Prison::Barcode::create(Prison::Code128);
QVERIFY(code);
code->setData(QString::fromLatin1(input.constData(), input.size()));
const auto img = code->toImage(code->minimumSize());
img.save(refName);
QImage ref(QStringLiteral(":/code128/") + refName);
ref = ref.convertToFormat(img.format());
QCOMPARE(img, ref);
}
{
auto code = Prison::Barcode::create(Prison::Code128);
QVERIFY(code);
code->setData(QString::fromLatin1(input.constData(), input.size()));
const auto img = code->toImage(code->minimumSize());
img.save(refName);
QImage ref(QStringLiteral(":/code128/") + refName);
ref = ref.convertToFormat(img.format());
QCOMPARE(img, ref);
}
}
};
QTEST_APPLESS_MAIN(Code128BarcodeTest)
#include "code128barcodetest.moc"
Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

@@ -0,0 +1,10 @@
<!--
SPDX-FileCopyrightText: none
SPDX-License-Identifier: CC0-1.0
-->
<RCC>
<qresource prefix="/datamatrix/">
<file>datamatrix-text.png</file>
<file>datamatrix-binary.png</file>
</qresource>
</RCC>
@@ -0,0 +1,59 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include <Prison/Barcode>
#include <QObject>
#include <QTest>
using namespace Prison;
class DataMatrixTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testRender_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<QString>("refName");
QTest::newRow("text") << QByteArray("KF5::Prison") << "datamatrix-text.png";
QTest::newRow("binary") << QByteArray("KDE\x0\x1\x2\x3\x4\x5\x6\x7\x8\x9kde", 16) << "datamatrix-binary.png";
}
void testRender()
{
QFETCH(QByteArray, input);
QFETCH(QString, refName);
{
auto code = Prison::Barcode::create(Prison::DataMatrix);
QVERIFY(code);
code->setData(QString::fromLatin1(input.constData(), input.size()));
const auto img = code->toImage(code->preferredSize(1));
img.save(refName);
QImage ref(QStringLiteral(":/datamatrix/") + refName);
ref = ref.convertToFormat(img.format());
QCOMPARE(img, ref);
}
{
auto code = Prison::Barcode::create(Prison::DataMatrix);
QVERIFY(code);
code->setData(input);
const auto img = code->toImage(code->preferredSize(1));
img.save(refName);
QImage ref(QStringLiteral(":/datamatrix/") + refName);
ref = ref.convertToFormat(img.format());
QCOMPARE(img, ref);
}
}
};
QTEST_APPLESS_MAIN(DataMatrixTest)
#include "datamatrixtest.moc"
@@ -0,0 +1,64 @@
/*
SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include <Prison/Format>
#include <Prison/ImageScanner>
#include <Prison/ScanResult>
#include <QImage>
#include <QObject>
#include <QTest>
#include <QtConcurrent>
using namespace Qt::Literals::StringLiterals;
using namespace Prison;
class ImageScannerTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testImageScan()
{
auto result = ImageScanner::scan(QImage());
QVERIFY(!result.hasContent());
QImage img(u":/qr/qr-text.png"_s);
QVERIFY(!img.isNull());
result = ImageScanner::scan(img);
QVERIFY(result.hasContent());
QVERIFY(result.hasText());
QCOMPARE(result.text(), "KF5::Prison"_L1);
result = ImageScanner::scan(img, Format::QRCode);
QCOMPARE(result.text(), "KF5::Prison"_L1);
result = ImageScanner::scan(img, Format::Aztec);
QVERIFY(!result.hasContent());
img = QImage(u":/qr/qr-binary.png"_s);
QVERIFY(!img.isNull());
result = ImageScanner::scan(img);
QVERIFY(result.hasContent());
QVERIFY(!result.hasText());
QVERIFY(result.hasBinaryData());
}
void testImageScanMT()
{
QImage img(u":/qr/qr-text.png"_s);
QEventLoop loop;
QtConcurrent::run([&img]() {
return ImageScanner::scan(img);
}).then([&loop](const ScanResult &result) {
qDebug() << result.text();
loop.exit();
});
loop.exec();
}
};
QTEST_GUILESS_MAIN(ImageScannerTest)
#include "imagescannertest.moc"
@@ -0,0 +1,94 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include <QtTest/QTest>
#include "mecard.h"
using namespace Prison;
class MeCardTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testWifiParse()
{
QVariantMap map;
{
auto m = MeCard::parse(QStringLiteral("WIFI:S:myssid;T:WPA;P:xxx123;;"));
QVERIFY(m.has_value());
QCOMPARE(m->header(), QLatin1String("WIFI"));
QCOMPARE(m->headerView(), QLatin1String("WIFI"));
QCOMPARE(m->value(u"S"), QLatin1String("myssid"));
QCOMPARE(m->value(u"T"), QLatin1String("WPA"));
QCOMPARE(m->value(u"P"), QLatin1String("xxx123"));
map = QVariantMap(
{{QLatin1String("S"), QLatin1String("myssid")}, {QLatin1String("T"), QLatin1String("WPA")}, {QLatin1String("P"), QLatin1String("xxx123")}});
QCOMPARE(m->toVariantMap(), map);
}
{
auto m = MeCard::parse(QStringLiteral("MECARD:N:Doe,John;TEL:13035551212;EMAIL:john.doe@example.com;;"));
QVERIFY(m.has_value());
QCOMPARE(m->header(), QLatin1String("MECARD"));
QCOMPARE(m->headerView(), QLatin1String("MECARD"));
QCOMPARE(m->value(u"N"), QLatin1String("Doe,John"));
QCOMPARE(m->value(u"TEL"), QLatin1String("13035551212"));
QCOMPARE(m->value(u"EMAIL"), QLatin1String("john.doe@example.com"));
map = QVariantMap({{QLatin1String("N"), QLatin1String("Doe,John")},
{QLatin1String("TEL"), QLatin1String("13035551212")},
{QLatin1String("EMAIL"), QLatin1String("john.doe@example.com")}});
QCOMPARE(m->toVariantMap(), map);
}
{
auto m = MeCard::parse(QStringLiteral("MECARD:N:Doe,John;TEL:13035551212;EMAIL:john.doe@example.com;EMAIL:null@kde.org;;"));
QVERIFY(m.has_value());
QCOMPARE(m->header(), QLatin1String("MECARD"));
QCOMPARE(m->headerView(), QLatin1String("MECARD"));
QCOMPARE(m->value(u"N"), QLatin1String("Doe,John"));
QCOMPARE(m->value(u"TEL"), QLatin1String("13035551212"));
QCOMPARE(m->value(u"EMAIL"), QString());
QCOMPARE(m->values(u"EMAIL"), QStringList({QLatin1String("john.doe@example.com"), QLatin1String("null@kde.org")}));
map = QVariantMap({{QLatin1String("N"), QLatin1String("Doe,John")},
{QLatin1String("TEL"), QLatin1String("13035551212")},
{QLatin1String("EMAIL"), QString()},
{QLatin1String("EMAIL"), QStringList({QLatin1String("john.doe@example.com"), QLatin1String("null@kde.org")})}});
QCOMPARE(m->toVariantMap(), map);
}
{
auto m = MeCard::parse(QStringLiteral("WIFI:S:\\\"foo\\;bar\\\\baz\\\";P:\"ABCD\";;"));
QVERIFY(m.has_value());
QCOMPARE(m->value(u"P"), QLatin1String("ABCD"));
QCOMPARE(m->value(u"S"), QLatin1String("\"foo;bar\\baz\""));
map = QVariantMap({{QLatin1String("P"), QLatin1String("ABCD")}, {QLatin1String("S"), QLatin1String("\"foo;bar\\baz\"")}});
QCOMPARE(m->toVariantMap(), map);
}
}
void testInvalid_data()
{
QTest::addColumn<QString>("input");
QTest::newRow("empty") << QString();
QTest::newRow("no colon") << QStringLiteral("wifi");
QTest::newRow("no content 1") << QStringLiteral("wifi:");
QTest::newRow("no content 2") << QStringLiteral("wifi:;");
QTest::newRow("no content 3") << QStringLiteral("wifi:;;");
QTest::newRow("invalid element") << QStringLiteral("wifi:S:");
}
void testInvalid()
{
QFETCH(QString, input);
auto m = MeCard::parse(input);
QVERIFY(!m.has_value());
}
};
QTEST_APPLESS_MAIN(MeCardTest)
#include "mecardtest.moc"
Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

@@ -0,0 +1,11 @@
<!--
SPDX-FileCopyrightText: none
SPDX-License-Identifier: CC0-1.0
-->
<RCC>
<qresource prefix="/qr/">
<file>qr-text.png</file>
<file>qr-binary.png</file>
</qresource>
</RCC>
@@ -0,0 +1,70 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include <Prison/Barcode>
#include <QObject>
#include <QTest>
using namespace Prison;
class QrTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testRenderText_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("refName");
QTest::newRow("text") << QStringLiteral("KF5::Prison") << "qr-text.png";
}
void testRenderText()
{
QFETCH(QString, input);
QFETCH(QString, refName);
auto code = Prison::Barcode::create(Prison::QRCode);
QVERIFY(code);
code->setData(input);
const auto img = code->toImage(code->preferredSize(1));
img.save(refName);
QImage ref(QStringLiteral(":/qr/") + refName);
ref = ref.convertToFormat(img.format());
QCOMPARE(img, ref);
}
void testRenderBinary_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<QString>("refName");
QTest::newRow("text") << QByteArray("KF5::Prison") << "qr-text.png";
QTest::newRow("binary") << QByteArray("KDE\x0\x1\x2\x3\x4\x5\x6\x7\x8\x9kde", 16) << "qr-binary.png";
}
void testRenderBinary()
{
QFETCH(QByteArray, input);
QFETCH(QString, refName);
auto code = Prison::Barcode::create(Prison::QRCode);
QVERIFY(code);
code->setData(input);
const auto img = code->toImage(code->preferredSize(1));
img.save(refName);
QImage ref(QStringLiteral(":/qr/") + refName);
ref = ref.convertToFormat(img.format());
QCOMPARE(img, ref);
}
};
QTEST_APPLESS_MAIN(QrTest)
#include "qrtest.moc"
@@ -0,0 +1,63 @@
/*
SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include "../src/lib/bitvector_p.h"
#include "../src/lib/reedsolomon_p.h"
#include <QDebug>
#include <QObject>
#include <QTest>
Q_DECLARE_METATYPE(Prison::BitVector)
using namespace Prison;
class ReedSolomonTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void rsTest_data()
{
QTest::addColumn<int>("poly");
QTest::addColumn<int>("symCount");
QTest::addColumn<BitVector>("input");
QTest::addColumn<BitVector>("output");
BitVector in;
BitVector out;
out.appendMSB(0, 20);
QTest::newRow("empty") << (int)ReedSolomon::GF16 << 5 << in << out;
in.clear();
out.clear();
in.appendMSB(0x5c, 8);
out.appendMSB(7, 6);
out.appendMSB(5, 7);
out.appendMSB(0x4d, 7);
QTest::newRow("GF16") << (int)ReedSolomon::GF16 << 5 << in << out;
}
void rsTest()
{
QFETCH(int, poly);
QFETCH(int, symCount);
QFETCH(BitVector, input);
QFETCH(BitVector, output);
ReedSolomon rs(poly, symCount);
const auto res = rs.encode(input);
QCOMPARE(res.size(), output.size());
if (res != output) {
qDebug() << "Actual :" << res;
qDebug() << "Expected:" << output;
}
QCOMPARE(res, output);
}
};
QTEST_APPLESS_MAIN(ReedSolomonTest)
#include "reedsolomontest.moc"
@@ -0,0 +1,39 @@
/*
SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include "../src/lib/zxingutil_p.h"
#include <QDebug>
#include <QObject>
#include <QTest>
using namespace Prison;
class ZXingUtilTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testToStdWString()
{
auto w = ZXingUtil::toStdWString(QStringLiteral("KDE"));
QCOMPARE(w.size(), 3);
QCOMPARE(w[0], 'K');
w = ZXingUtil::toStdWString(QByteArray("KDE"));
QCOMPARE(w.size(), 3);
QCOMPARE(w[0], 'K');
w = ZXingUtil::toStdWString(QByteArray("\x80\x00\x7f", 3));
QCOMPARE(w.size(), 3);
QCOMPARE(w[0], 0x80);
QCOMPARE(w[1], 0x00);
QCOMPARE(w[2], 0x7f);
}
};
QTEST_APPLESS_MAIN(ZXingUtilTest)
#include "zxingutiltest.moc"
@@ -0,0 +1,60 @@
#.rst:
# FindDmtx
# --------
#
# Try to find the DataMatrix library.
#
# This will define the following variables:
#
# ``Dmtx_FOUND``
# True if the datamatrix library is available.
#
# ``Dmtx_INCLUDE_DIRS``
# the datamatrix library include dirs.
# This variable shall be passed to target_include_libraries() calls if
# the target is not used for linking.
#
# ``Dmtx_LIBRARIES``
# the libraries used to link datamatrix.
# This can be passed to target_link_libraries instead of
# the ``Dmtx::Dmtx`` target.
#
# If ``Dmtx_FOUND`` is TRUE, the following imported target
# will be available:
#
# ``Dmtx::Dmtx``
# The datamatrix library.
#
# Imported target since 5.27.0
#
#=============================================================================
# SPDX-FileCopyrightText: 2010 Sune Vuorela <sune@debian.org>
# SPDX-License-Identifier: BSD-3-Clause
#=============================================================================
find_path(Dmtx_INCLUDE_DIRS dmtx.h)
find_library(Dmtx_LIBRARIES NAMES dmtx)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Dmtx
FOUND_VAR Dmtx_FOUND
REQUIRED_VARS Dmtx_LIBRARIES Dmtx_INCLUDE_DIRS
)
if(Dmtx_FOUND AND NOT TARGET Dmtx::Dmtx)
add_library(Dmtx::Dmtx UNKNOWN IMPORTED)
set_target_properties(Dmtx::Dmtx PROPERTIES
IMPORTED_LOCATION "${Dmtx_LIBRARIES}"
INTERFACE_INCLUDE_DIRECTORIES "${Dmtx_INCLUDE_DIRS}")
endif()
mark_as_advanced(Dmtx_INCLUDE_DIRS Dmtx_LIBRARIES)
include(FeatureSummary)
set_package_properties(Dmtx PROPERTIES
URL "https://github.com/dmtx/libdmtx"
DESCRIPTION "The Datamatrix library"
)
@@ -0,0 +1,59 @@
#.rst:
# FindQRencode
# ------------
#
# Try to find the qrencode library.
#
# This will define the following variables:
#
# ``QRencode_FOUND``
# True if the QRencode library is available.
#
# ``QRencode_INCLUDE_DIRS``
# the QRencode library include dirs.
# This variable shall be passed to target_include_libraries() calls if
# the target is not used for linking.
#
# ``QRencode_LIBRARIES``
# the libraries used to link QRencode.
# This can be passed to target_link_libraries instead of
# the ``QRencode::QRencode`` target.
#
# If ``QRencode_FOUND`` is TRUE, the following imported target
# will be available:
#
# ``QRencode::QRencode``
# The QRencode library.
#
# Imported target since 5.27.0
#
#=============================================================================
# SPDX-FileCopyrightText: 2010 Sune Vuorela <sune@debian.org>
# SPDX-License-Identifier: BSD-3-Clause
#=============================================================================
find_library(QRencode_LIBRARIES NAMES qrencode qrencoded)
find_path(QRencode_INCLUDE_DIRS qrencode.h)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(QRencode
FOUND_VAR QRencode_FOUND
REQUIRED_VARS QRencode_LIBRARIES QRencode_INCLUDE_DIRS
)
if(QRencode_FOUND AND NOT TARGET QRencode::QRencode)
add_library(QRencode::QRencode UNKNOWN IMPORTED)
set_target_properties(QRencode::QRencode PROPERTIES
IMPORTED_LOCATION "${QRencode_LIBRARIES}"
INTERFACE_INCLUDE_DIRECTORIES "${QRencode_INCLUDE_DIRS}")
endif()
mark_as_advanced(QRencode_LIBRARIES QRencode_INCLUDE_DIRS)
include(FeatureSummary)
set_package_properties(QRencode PROPERTIES
URL "https://fukuchi.org/works/qrencode/"
DESCRIPTION "The QRencode library"
)
@@ -0,0 +1,8 @@
### KApiDox Project-specific Overrides File
# define so that deprecated API is not skipped
PREDEFINED += \
"PRISON_ENABLE_DEPRECATED_SINCE(x, y)=1" \
"PRISON_BUILD_DEPRECATED_SINCE(x, y)=1" \
"PRISON_DEPRECATED_VERSION(x, y, t)=" \
"PRISON_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)="
@@ -0,0 +1,20 @@
description: Barcode abstraction layer providing uniform access to generation of barcodes
tier: 1
type: solution
platforms:
- name: Linux
- name: FreeBSD
- name: Android
- name: Windows
- name: macOS
portingAid: false
deprecated: false
release: true
libraries:
- cmake: "KF6::Prison"
cmakename: KF6Prison
public_lib: true
group: Frameworks
subgroup: Tier 1
@@ -0,0 +1,17 @@
add_subdirectory(lib)
add_subdirectory(tools)
if(TARGET Qt6::Quick)
add_subdirectory(quick)
endif()
if(TARGET Qt6::Multimedia AND TARGET ZXing::ZXing)
add_subdirectory(scanner)
if (TARGET Qt6::Quick)
add_subdirectory(scanner-quick)
endif()
endif()
ecm_qt_install_logging_categories(
EXPORT PRISON
FILE prison.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
@@ -0,0 +1,148 @@
include(CMakePackageConfigHelpers)
if(TARGET Dmtx::Dmtx)
set(HAVE_DMTX 1)
endif()
if (TARGET ZXing::ZXing)
set(HAVE_ZXING 1)
endif()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-prison.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-prison.h)
add_library(KF6Prison)
add_library(KF6::Prison ALIAS KF6Prison)
qt_extract_metatypes(KF6Prison)
set_target_properties(KF6Prison PROPERTIES
VERSION ${PRISON_VERSION}
SOVERSION ${PRISON_SOVERSION}
EXPORT_NAME Prison
)
target_sources(KF6Prison PRIVATE
abstractbarcode_p.cpp
aztecbarcode.cpp
aztecbarcode_p.h
barcode.cpp
barcodeutil.cpp
barcodeutil_p.h
bitvector.cpp
bitvector_p.h
code128barcode.cpp
code128barcode_p.h
code39barcode.cpp
code39barcode_p.h
code93barcode.cpp
code93barcode_p.h
mecard.cpp
mecard.h
prison.h
qrcodebarcode.cpp
qrcodebarcode_p.h
reedsolomon.cpp
reedsolomon_p.h
)
if(TARGET Dmtx::Dmtx)
target_sources(KF6Prison PRIVATE datamatrixbarcode.cpp datamatrixbarcode_p.h)
endif()
if(TARGET ZXing::ZXing)
target_sources(KF6Prison PRIVATE
pdf417barcode.cpp
pdf417barcode_p.h
zxingutil.cpp
)
endif()
kde_source_files_enable_exceptions(
barcode.cpp
pdf417barcode.cpp
prison.cpp
zxingutil.cpp
)
ecm_qt_declare_logging_category(KF6Prison
HEADER prison_debug.h
IDENTIFIER Prison::Log
CATEGORY_NAME kf.prison
OLD_CATEGORY_NAMES kf5.prison
DESCRIPTION "Prison (lib)"
EXPORT PRISON
)
ecm_generate_export_header(KF6Prison
BASE_NAME Prison
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
target_include_directories(KF6Prison
INTERFACE
"$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/Prison>"
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>" # module version header
)
target_link_libraries(KF6Prison
PUBLIC
Qt6::Gui
PRIVATE
QRencode::QRencode
)
if(TARGET Dmtx::Dmtx)
target_link_libraries(KF6Prison PRIVATE Dmtx::Dmtx)
endif()
if(TARGET ZXing::ZXing)
target_link_libraries(KF6Prison PRIVATE ZXing::ZXing)
endif()
install(TARGETS KF6Prison EXPORT KF6PrisonTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
ecm_generate_headers(Prison_CamelCase_HEADERS
HEADER_NAMES
Barcode
Prison
MeCard
REQUIRED_HEADERS Prison_HEADERS
OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/Prison
)
set(_all_headers
${Prison_HEADERS}
${Prison_CamelCase_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/prison_export.h
)
install(
FILES ${_all_headers}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/Prison/Prison
COMPONENT Devel
)
if(BUILD_QCH)
ecm_add_qch(
KF6Prison_QCH
NAME Prison
BASE_NAME KF6Prison
VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${Prison_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt6Gui_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
PRISON_EXPORT
PRISON_DEPRECATED
PRISON_DEPRECATED_EXPORT
"PRISON_DEPRECATED_VERSION(x, y, t)"
"PRISON_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
@@ -0,0 +1,57 @@
/*
SPDX-FileCopyrightText: 2010-2016 Sune Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: MIT
*/
#include "abstractbarcode_p.h"
#include <QColor>
#include <QVariant>
using namespace Prison;
AbstractBarcodePrivate::AbstractBarcodePrivate(Barcode::Dimensions dim)
: m_dimension(dim)
{
}
AbstractBarcodePrivate::~AbstractBarcodePrivate() = default;
bool AbstractBarcodePrivate::sizeTooSmall(const QSizeF &size) const
{
return m_cache.width() > size.width() || m_cache.height() > size.height();
}
bool AbstractBarcodePrivate::isEmpty() const
{
switch (m_data.userType()) {
case QMetaType::QString:
return m_data.toString().isEmpty();
case QMetaType::QByteArray:
return m_data.toByteArray().isEmpty();
default:
break;
}
return true;
}
void AbstractBarcodePrivate::recompute()
{
if (m_cache.isNull() && !isEmpty()) {
m_cache = paintImage();
}
}
QSizeF AbstractBarcodePrivate::preferredSize(qreal devicePixelRatio) const
{
switch (m_dimension) {
case Barcode::NoDimensions:
return {};
case Barcode::OneDimension:
return QSizeF(m_cache.width() * (devicePixelRatio < 2 ? 2 : 1), std::max(m_cache.height(), 50));
case Barcode::TwoDimensions:
return m_cache.size() * (devicePixelRatio < 2 ? 4 : 2);
}
return {};
}
@@ -0,0 +1,48 @@
/*
SPDX-FileCopyrightText: 2010-2016 Sune Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_ABSTRACTBARCODE_P_H
#define PRISON_ABSTRACTBARCODE_P_H
#include "barcode.h"
#include "prison.h"
#include <QImage>
#include <QVariant>
namespace Prison
{
class AbstractBarcodePrivate
{
public:
explicit AbstractBarcodePrivate(Barcode::Dimensions dim);
virtual ~AbstractBarcodePrivate();
/**
* Doing the actual painting of the image
* @param size unused - will be removed in KF6
* @return image with barcode, or null image
*/
virtual QImage paintImage() = 0;
/** @see Barcode::preferredSize */
virtual QSizeF preferredSize(qreal devicePixelRatio) const;
bool isEmpty() const;
bool sizeTooSmall(const QSizeF &size) const;
void recompute();
QVariant m_data;
QImage m_cache;
QColor m_foreground = Qt::black;
QColor m_background = Qt::white;
Barcode::Dimensions m_dimension = Barcode::NoDimensions;
Prison::BarcodeType m_format;
};
}
#endif
@@ -0,0 +1,714 @@
/*
SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include "aztecbarcode_p.h"
#include "barcodeutil_p.h"
#include "bitvector_p.h"
#include "prison_debug.h"
#include "reedsolomon_p.h"
#include <QImage>
#include <QPainter>
#include <algorithm>
#include <vector>
// see https://en.wikipedia.org/wiki/Aztec_Code for encoding tables, magic numbers, etc
using namespace Prison;
enum {
FullMaxSize = 151,
FullRadius = 74,
FullGridInterval = 16,
FullModeMessageSize = 40,
FullLayerCount = 32,
CompactMaxSize = 27,
CompactRadius = 13,
CompactModeMessageSize = 28,
CompactLayerCount = 4,
};
AztecBarcode::AztecBarcode()
: AbstractBarcodePrivate(Barcode::TwoDimensions)
{
}
AztecBarcode::~AztecBarcode() = default;
// encoding properties depending on layer count
struct aztec_layer_property_t {
uint8_t layer;
uint8_t codeWordSize;
uint16_t gf;
};
static const aztec_layer_property_t aztec_layer_properties[] = {{2, 6, ReedSolomon::GF64},
{8, 8, ReedSolomon::GF256},
{22, 10, ReedSolomon::GF1024},
{32, 12, ReedSolomon::GF4096}};
// amounts of bits in an Aztec code depending on layer count
static int aztecCompactDataBits(int layer)
{
return (88 + 16 * layer) * layer;
}
static int aztecFullDataBits(int layer)
{
return (112 + 16 * layer) * layer;
}
QImage AztecBarcode::paintImage()
{
const auto inputData = aztecEncode(BarCodeUtil::asLatin1ByteArray(m_data));
int layerCount = 0;
int codewordCount = 0;
int availableBits = 0;
int stuffSize = 0; // extra bits added during bit stuffing, which might make us overrun the available size
bool compactMode = false;
BitVector encodedData;
do {
layerCount = 0;
// find the smallest layout we can put the data in, while leaving 23% for error correction
for (auto i = 1; i <= FullLayerCount; ++i) {
if (aztecFullDataBits(i) * 0.77 > (inputData.size() + stuffSize)) {
layerCount = i;
break;
}
}
for (auto i = 1; i <= CompactLayerCount; ++i) {
if (aztecCompactDataBits(i) * 0.77 > (inputData.size() + stuffSize)) {
layerCount = i;
compactMode = true;
break;
}
}
if (layerCount == 0) {
qCWarning(Log) << "data too large for Aztec code" << inputData.size();
return {};
}
// determine code word size
const auto propIt = std::lower_bound(aztec_layer_properties, aztec_layer_properties + 4, layerCount, [](const aztec_layer_property_t &lhs, int rhs) {
return lhs.layer < rhs;
});
// bit stuffing
auto stuffedData = bitStuffAndPad(inputData, (*propIt).codeWordSize);
stuffSize = stuffedData.size() - inputData.size();
availableBits = compactMode ? aztecCompactDataBits(layerCount) : aztecFullDataBits(layerCount);
codewordCount = stuffedData.size() / (*propIt).codeWordSize;
const auto rsWordCount = availableBits / (*propIt).codeWordSize - codewordCount;
// compute error correction
ReedSolomon rs((*propIt).gf, rsWordCount);
const auto rsData = rs.encode(stuffedData);
// pad with leading 0 bits to align to code word boundaries
encodedData.reserve(availableBits);
if (int diff = availableBits - stuffedData.size() - rsData.size()) {
encodedData.appendMSB(0, diff);
}
encodedData.append(stuffedData);
encodedData.append(rsData);
// try again in the rare case that we overrun the available bits due to bit stuffing and padding
} while (encodedData.size() > availableBits);
// determine mode message
BitVector modeMsg;
if (compactMode) {
modeMsg.appendMSB(layerCount - 1, 2);
modeMsg.appendMSB(codewordCount - 1, 6);
ReedSolomon rs(ReedSolomon::GF16, 5);
modeMsg.append(rs.encode(modeMsg));
} else {
modeMsg.appendMSB(layerCount - 1, 5);
modeMsg.appendMSB(codewordCount - 1, 11);
ReedSolomon rs(ReedSolomon::GF16, 6);
modeMsg.append(rs.encode(modeMsg));
}
// render the result
if (compactMode) {
QImage img(CompactMaxSize, CompactMaxSize, QImage::Format_RGB32);
img.fill(m_background);
paintCompactGrid(&img);
paintCompactData(&img, encodedData, layerCount);
paintCompactModeMessage(&img, modeMsg);
return cropAndScaleCompact(&img, layerCount);
} else {
QImage img(FullMaxSize, FullMaxSize, QImage::Format_RGB32);
img.fill(m_background);
paintFullGrid(&img);
paintFullData(&img, encodedData, layerCount);
paintFullModeMessage(&img, modeMsg);
return cropAndScaleFull(&img, layerCount);
}
}
// code points and encoding modes for each of the first 127 ASCII characters, the rest is encoded in Binary mode
enum Mode {
NoMode,
Upper,
Lower,
Mixed,
Punct,
Digit,
Binary,
MODE_COUNT,
Special,
};
enum SpecialChar {
Space,
CarriageReturn,
Comma,
Dot,
SPECIAL_CHAR_COUNT,
};
struct aztec_code_t {
uint8_t code;
uint8_t mode;
};
static const aztec_code_t aztec_code_table[] = {
{0, Binary}, // 0
{2, Mixed}, {3, Mixed}, {4, Mixed},
{5, Mixed}, {6, Mixed}, {7, Mixed},
{8, Mixed}, // 7 BEL \a
{9, Mixed}, {10, Mixed}, {11, Mixed}, // 10 LF / ^J
{12, Mixed}, {13, Mixed}, {CarriageReturn, Special}, // 13 CR / ^M - but also 1 Punct
{14, Binary}, {15, Binary}, {16, Binary},
{17, Binary}, {18, Binary}, {19, Binary},
{20, Binary}, // 20 ^T
{21, Binary}, {22, Binary}, {23, Binary},
{24, Binary}, {25, Binary}, {26, Binary},
{15, Mixed}, // 27 ^[
{16, Mixed}, {17, Mixed}, {18, Mixed}, // 30 ^^
{19, Mixed}, {Space, Special}, // 32 SP
{6, Punct}, {7, Punct}, {8, Punct}, // 35 #
{9, Punct}, {10, Punct}, {11, Punct},
{12, Punct}, {13, Punct}, // 40 (
{14, Punct}, {15, Punct}, {16, Punct}, // 43 +
{Comma, Special}, // 44 ,
{18, Punct}, // 45 -
{Dot, Special}, // 46 .
{20, Punct}, // 47 /
{2, Digit}, // 48 0
{3, Digit}, {4, Digit}, {5, Digit},
{6, Digit}, {7, Digit}, {8, Digit},
{9, Digit}, {10, Digit}, {11, Digit}, // 57 9
{21, Punct}, // 58 :
{22, Punct}, // 59 ;
{23, Punct}, // 60 <
{24, Punct}, {25, Punct}, // 62 >
{26, Punct}, // 63 ?
{20, Mixed}, // 64 @
{2, Upper}, // 65 A
{3, Upper}, {4, Upper}, {5, Upper},
{6, Upper}, {7, Upper}, {8, Upper},
{9, Upper}, {10, Upper}, {11, Upper},
{12, Upper}, {13, Upper}, {14, Upper},
{15, Upper}, {16, Upper}, {17, Upper},
{18, Upper}, {19, Upper}, {20, Upper},
{21, Upper}, {22, Upper}, {23, Upper},
{24, Upper}, {25, Upper}, {26, Upper},
{27, Upper}, // 90 Z
{27, Punct}, // 91 [
{21, Mixed}, // 92 backslash
{28, Punct}, // 93 ]
{22, Mixed}, // 94 ^
{23, Mixed}, // 95 _
{24, Mixed}, // 96 `
{2, Lower}, // 97 a
{3, Lower}, {4, Lower}, {5, Lower},
{6, Lower}, {7, Lower}, {8, Lower},
{9, Lower}, {10, Lower}, {11, Lower},
{12, Lower}, {13, Lower}, {14, Lower},
{15, Lower}, {16, Lower}, {17, Lower},
{18, Lower}, {19, Lower}, {20, Lower},
{21, Lower}, {22, Lower}, {23, Lower},
{24, Lower}, {25, Lower}, {26, Lower},
{27, Lower}, // 122 z
{29, Punct}, // 123 {
{25, Mixed}, // 124 |
{30, Punct}, // 125 }
{26, Mixed}, // 126 ~
{27, Mixed} // 127 DEL ^?
};
Q_STATIC_ASSERT(sizeof(aztec_code_table) == 256);
static const struct {
uint8_t c1;
uint8_t c2;
aztec_code_t sym;
} aztec_code_double_symbols[] = {
{'\r', '\n', {2, Punct}}, // CR LF
{'.', ' ', {3, Punct}}, // . SP
{',', ' ', {4, Punct}}, // , SP
{':', ' ', {5, Punct}} // : SP
};
static const int aztec_code_size[] = {0, 5, 5, 5, 5, 4, 8};
Q_STATIC_ASSERT(sizeof(aztec_code_size) / sizeof(int) == MODE_COUNT);
// codes for ambiguous characters, ie. those that can be encoded in multiple modes
static const aztec_code_t aztec_special_chars[SPECIAL_CHAR_COUNT][MODE_COUNT] = {
/* NoMode Upper Lower Mixed Punct Digit Binary */
{{0, NoMode}, {1, Upper}, {1, Lower}, {1, Mixed}, {1, Upper}, {1, Digit}, {0, NoMode}}, /* SP */
{{0, NoMode}, {1, Punct}, {1, Punct}, {14, Mixed}, {1, Punct}, {1, Punct}, {0, NoMode}}, /* CR */
{{0, NoMode}, {17, Punct}, {17, Punct}, {17, Punct}, {17, Punct}, {12, Digit}, {0, NoMode}}, /* Comma */
{{0, NoMode}, {19, Punct}, {19, Punct}, {19, Punct}, {19, Punct}, {13, Digit}, {0, NoMode}}, /* Dot */
};
// shift code table, source mode -> target mode
// NoMode indicates shift is not available, use latch instead
static const aztec_code_t aztec_shift_codes[MODE_COUNT - 1][MODE_COUNT - 1] = {
/* NoMode Upper Lower Mixed Punct Digit */
{{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}},
{{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}},
{{0, NoMode}, {28, Upper}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}},
{{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}},
{{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}},
{{0, NoMode}, {15, Upper}, {0, NoMode}, {0, NoMode}, {0, Punct}, {0, NoMode}}};
// latch code table, source mode -> target mode
static const aztec_code_t aztec_latch_codes[MODE_COUNT - 1][MODE_COUNT] = {
/* NoMode Upper Lower Mixed Punct Digit Binary */
{{0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}, {0, NoMode}},
{{0, NoMode}, {0, NoMode}, {28, Lower}, {29, Mixed}, {29, Mixed}, {30, Digit}, {31, Binary}},
{{0, NoMode}, {30, Digit}, {0, NoMode}, {29, Mixed}, {29, Mixed}, {30, Digit}, {31, Binary}},
{{0, NoMode}, {29, Upper}, {28, Lower}, {0, NoMode}, {30, Punct}, {28, Lower}, {31, Binary}},
{{0, NoMode}, {31, Upper}, {31, Upper}, {31, Upper}, {0, NoMode}, {31, Upper}, {31, Upper}},
{{0, NoMode}, {14, Upper}, {14, Upper}, {14, Upper}, {14, Upper}, {0, NoMode}, {14, Upper}}};
static Mode aztecCodeLatchTo(Mode currentMode, Mode targetMode, BitVector *v)
{
if (currentMode == targetMode) {
return targetMode;
}
const auto latchCode = aztec_latch_codes[currentMode][targetMode];
qCDebug(Log) << "latch" << latchCode.code << aztec_code_size[currentMode];
v->appendMSB(latchCode.code, aztec_code_size[currentMode]);
return static_cast<Mode>(latchCode.mode);
}
static void aztecEncodeBinary(std::vector<aztec_code_t>::iterator &it, const std::vector<aztec_code_t>::iterator &end, BitVector *v)
{
// determine length of the binary sequence
const auto binEndIt = std::find_if(it, end, [](aztec_code_t sym) {
return sym.mode != Binary;
});
const auto length = std::distance(it, binEndIt);
// write length field
qCDebug(Log) << "binary length" << length;
if (length < 32) {
v->appendMSB(length, 5);
} else {
v->appendMSB(0, 5);
v->appendMSB(length - 31, 11);
}
// write data
for (; it != binEndIt; ++it) {
qCDebug(Log) << "binary data" << (*it).code;
v->appendMSB((*it).code, 8);
}
}
static void aztecEncodeResolveAmbigious(Mode currentMode, const std::vector<aztec_code_t>::iterator &begin, const std::vector<aztec_code_t>::iterator &end)
{
Q_ASSERT(begin != end);
Q_ASSERT(currentMode != (*begin).mode);
Q_ASSERT((*begin).mode == Special);
// forward search
auto it = begin;
for (; it != end && (*it).mode == Special; ++it) {
if (aztec_special_chars[(*it).code][currentMode].mode == currentMode) {
qCDebug(Log) << "special resolved to current mode by forward search";
(*it).mode = aztec_special_chars[(*it).code][currentMode].mode;
(*it).code = aztec_special_chars[(*it).code][currentMode].code;
}
}
// backward search
auto backIt = it;
while (std::distance(begin, backIt) >= 1 && it != end) {
--backIt;
if ((*backIt).mode == Special && aztec_special_chars[(*backIt).code][(*it).mode].mode == (*it).mode) {
qCDebug(Log) << "special resolved by backward search";
(*backIt).mode = aztec_special_chars[(*backIt).code][(*it).mode].mode;
(*backIt).code = aztec_special_chars[(*backIt).code][(*it).mode].code;
} else {
break;
}
}
// pick one if we still have an ambiguous symbol
if ((*begin).mode != Special) {
return;
}
(*begin).mode = aztec_special_chars[(*begin).code][currentMode].mode;
(*begin).code = aztec_special_chars[(*begin).code][currentMode].code;
it = begin + 1;
if (it != end && (*it).mode == Special) {
aztecEncodeResolveAmbigious(static_cast<Mode>((*begin).mode), it, end);
}
}
static Mode aztecNextMode(Mode currentMode, const std::vector<aztec_code_t>::iterator &nextSym, const std::vector<aztec_code_t>::iterator &end, bool &tryShift)
{
Q_ASSERT(currentMode != (*nextSym).mode);
Q_ASSERT(nextSym != end);
Q_ASSERT((*nextSym).mode != Special);
auto it = nextSym;
++it;
if (it != end && (*it).mode == Special) {
aztecEncodeResolveAmbigious(static_cast<Mode>((*nextSym).mode), it, end);
}
if ((it == end || (*it).mode == currentMode) && std::distance(nextSym, it) == 1) {
tryShift = true;
}
qCDebug(Log) << currentMode << (*nextSym).mode << tryShift << std::distance(nextSym, it);
return static_cast<Mode>((*nextSym).mode);
}
BitVector AztecBarcode::aztecEncode(const QByteArray &data) const
{
// phase one: translate single and double chars to code points
std::vector<aztec_code_t> codes;
codes.reserve(data.size());
for (int i = 0; i < data.size(); ++i) {
const uint8_t c1 = data.at(i);
// double char codes
if (i < data.size() - 1) {
const uint8_t c2 = data.at(i + 1);
bool found = false;
for (const auto &dblCode : aztec_code_double_symbols) {
if (dblCode.c1 != c1 || dblCode.c2 != c2) {
continue;
}
codes.push_back(dblCode.sym);
++i;
found = true;
}
if (found) {
continue;
}
}
// > 127 binary-only range
if (c1 > 127) {
codes.push_back({c1, Binary});
// encodable single ASCII character
} else {
codes.push_back(aztec_code_table[c1]);
}
}
// phase two: insert shift and latch codes, translate to bit stream
Mode currentMode = Upper;
BitVector result;
for (auto it = codes.begin(); it != codes.end();) {
if ((*it).mode == Binary) {
auto newMode = aztecCodeLatchTo(currentMode, Binary, &result);
while (newMode != Binary) {
currentMode = newMode;
newMode = aztecCodeLatchTo(currentMode, Binary, &result);
}
aztecEncodeBinary(it, codes.end(), &result);
continue;
}
// resolve special codes
if ((*it).mode == Special) {
aztecEncodeResolveAmbigious(currentMode, it, codes.end());
}
// deal with mode changes
Mode nextMode = currentMode;
if ((*it).mode != currentMode) {
bool tryShift = false;
const auto newMode = aztecNextMode(currentMode, it, codes.end(), tryShift);
// shift to new mode if desired and possible
if (tryShift && aztec_shift_codes[currentMode][newMode].mode != NoMode) {
qCDebug(Log) << "shift" << aztec_shift_codes[currentMode][newMode].code << aztec_code_size[currentMode];
result.appendMSB(aztec_shift_codes[currentMode][newMode].code, aztec_code_size[currentMode]);
currentMode = newMode;
}
// latch to new mode
while (currentMode != newMode && newMode != NoMode && currentMode != NoMode) {
currentMode = aztecCodeLatchTo(currentMode, newMode, &result);
nextMode = currentMode;
}
}
qCDebug(Log) << (*it).code << aztec_code_size[currentMode];
result.appendMSB((*it).code, aztec_code_size[currentMode]);
++it;
currentMode = nextMode;
}
return result;
}
BitVector AztecBarcode::bitStuffAndPad(const BitVector &input, int codeWordSize) const
{
BitVector res;
res.reserve(input.size());
// bit stuff codewords with leading codeWordSize 0/1 bits
int i = 0;
while (i < input.size() - (codeWordSize - 1)) {
int v = input.valueAtMSB(i, codeWordSize - 1);
res.appendMSB(v, codeWordSize - 1);
i += codeWordSize - 1;
if (v == 0) {
res.appendBit(true);
} else if (v == (1 << (codeWordSize - 1)) - 1) {
res.appendBit(false);
} else {
res.appendBit(input.at(i++));
}
}
while (i < input.size()) {
res.appendBit(input.at(i++));
}
// check if we are code word aligned already
const auto trailingBits = res.size() % codeWordSize;
if (!trailingBits) { // nothing to pad
return res;
}
// pad with ones to nearest code word boundary
// last bit has to be zero if we'd otherwise would have all ones though
bool allOnes = true;
for (int i = res.size() - trailingBits; i < res.size(); ++i) {
allOnes &= res.at(i);
}
while (res.size() % codeWordSize) {
if ((res.size() % codeWordSize) == (codeWordSize - 1)) {
res.appendBit(allOnes ? false : true);
} else {
res.appendBit(true);
}
}
return res;
}
void AztecBarcode::paintFullGrid(QImage *img) const
{
QPainter p(img);
p.translate(img->width() / 2, img->height() / 2);
// alignment grids
QPen pen(m_foreground);
pen.setDashPattern({1, 1});
p.setPen(pen);
for (int i = 0; i < img->width() / 2; i += FullGridInterval) {
p.drawLine(-i, -FullRadius, -i, FullRadius);
p.drawLine(i, -FullRadius, i, FullRadius);
p.drawLine(-FullRadius, -i, FullRadius, -i);
p.drawLine(-FullRadius, i, FullRadius, i);
}
// bullseye background
p.setBrush(m_background);
p.setPen(Qt::NoPen);
p.drawRect(-7, -7, 14, 14);
// bullseye
p.setBrush(Qt::NoBrush);
p.setPen(m_foreground);
p.drawPoint(0, 0);
p.drawRect(-2, -2, 4, 4);
p.drawRect(-4, -4, 8, 8);
p.drawRect(-6, -6, 12, 12);
// bullseye orientation marker
p.drawRect(-7, -7, 1, 1);
p.drawRect(7, -7, 0, 1);
p.drawPoint(7, 6);
}
static const int aztecFullLayerOffset[] = {
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
66, 64, 62, 60, 57, 55, 53, 51, 49, 47, 45, 42, 40, 38, 36, 34, 32, 30, 28, 25, 23, 21, 19, 17, 15, 13, 10, 8, 6, 4, 2, 0};
void AztecBarcode::paintFullData(QImage *img, const BitVector &data, int layerCount) const
{
QPainter p(img);
p.setPen(m_foreground);
auto it = data.begin();
for (int layer = layerCount - 1; layer >= 0; --layer) {
const auto x1 = aztecFullLayerOffset[layer];
const auto y1 = x1;
const auto gridInMiddle = (x1 - FullRadius) % FullGridInterval == 0;
const auto x2 = gridInMiddle ? x1 + 2 : x1 + 1;
const auto segmentLength = FullMaxSize - 2 * y1 - 2 - (gridInMiddle ? 1 : 0);
for (int rotation = 0; rotation < 4; ++rotation) {
p.resetTransform();
p.translate(img->width() / 2, img->height() / 2);
p.rotate(-90 * rotation);
p.translate(-img->width() / 2, -img->height() / 2);
for (int i = 0; it != data.end(); ++i, ++it) {
const auto x = (i % 2 == 0) ? x1 : x2;
auto y = i / 2 + y1;
if (((y - FullRadius - 1) % FullGridInterval) == 0) { // skip grid lines
++y;
i += 2;
}
if (y >= y1 + segmentLength) {
break;
}
if (*it) {
p.drawPoint(x, y);
}
}
}
}
}
void AztecBarcode::paintFullModeMessage(QImage *img, const BitVector &modeData) const
{
Q_ASSERT(modeData.size() == FullModeMessageSize);
QPainter p(img);
p.setPen(m_foreground);
auto it = modeData.begin();
for (int rotation = 0; rotation < 4; ++rotation) {
p.resetTransform();
p.translate(img->width() / 2, img->height() / 2);
p.rotate(90 * rotation);
for (int i = -5; i <= 5; ++i) {
if (i == 0) { // skip grid line
continue;
}
if (*it) {
p.drawPoint(i, -7);
}
++it;
}
}
}
QImage AztecBarcode::cropAndScaleFull(QImage *img, int layerCount)
{
const auto offset = aztecFullLayerOffset[layerCount - 1];
const auto minSize = FullMaxSize - 2 * offset;
QImage out(minSize, minSize, img->format());
QPainter p(&out);
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
const auto srcRect = img->rect().adjusted(offset, offset, -offset, -offset);
p.drawImage(out.rect(), *img, srcRect);
return out;
}
void AztecBarcode::paintCompactGrid(QImage *img) const
{
QPainter p(img);
p.translate(img->width() / 2, img->height() / 2);
// bullseye
p.setPen(m_foreground);
p.drawPoint(0, 0);
p.drawRect(-2, -2, 4, 4);
p.drawRect(-4, -4, 8, 8);
// bullseye orientation marker
p.drawRect(-5, -5, 1, 1);
p.drawRect(5, -5, 0, 1);
p.drawPoint(5, 4);
}
static const int aztecCompactLayerOffset[] = {6, 4, 2, 0};
void AztecBarcode::paintCompactData(QImage *img, const BitVector &data, int layerCount) const
{
QPainter p(img);
p.setPen(m_foreground);
auto it = data.begin();
for (int layer = layerCount - 1; layer >= 0; --layer) {
const auto x1 = aztecCompactLayerOffset[layer];
const auto y1 = x1;
const auto x2 = x1 + 1;
const auto segmentLength = CompactMaxSize - 2 * y1 - 2;
for (int rotation = 0; rotation < 4; ++rotation) {
p.resetTransform();
p.translate(img->width() / 2, img->height() / 2);
p.rotate(-90 * rotation);
p.translate(-img->width() / 2, -img->height() / 2);
for (int i = 0; it != data.end(); ++i, ++it) {
const auto x = (i % 2 == 0) ? x1 : x2;
auto y = i / 2 + y1;
if (y >= y1 + segmentLength) {
break;
}
if (*it) {
p.drawPoint(x, y);
}
}
}
}
}
void AztecBarcode::paintCompactModeMessage(QImage *img, const BitVector &modeData) const
{
Q_ASSERT(modeData.size() == CompactModeMessageSize);
QPainter p(img);
p.setPen(m_foreground);
auto it = modeData.begin();
for (int rotation = 0; rotation < 4; ++rotation) {
p.resetTransform();
p.translate(img->width() / 2, img->height() / 2);
p.rotate(90 * rotation);
for (int i = -3; i <= 3; ++i) {
if (*it) {
p.drawPoint(i, -5);
}
++it;
}
}
}
QImage AztecBarcode::cropAndScaleCompact(QImage *img, int layerCount)
{
const auto offset = aztecCompactLayerOffset[layerCount - 1];
const auto minSize = CompactMaxSize - 2 * offset;
QImage out(minSize, minSize, img->format());
QPainter p(&out);
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
const auto srcRect = img->rect().adjusted(offset, offset, -offset, -offset);
p.drawImage(out.rect(), *img, srcRect);
return out;
}
@@ -0,0 +1,47 @@
/*
SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_AZTECBARCODE_H
#define PRISON_AZTECBARCODE_H
#include "abstractbarcode_p.h"
class AztecBarcodeTest;
namespace Prison
{
class BitVector;
/** Aztec code generator. */
class AztecBarcode : public AbstractBarcodePrivate
{
public:
AztecBarcode();
~AztecBarcode() override;
protected:
QImage paintImage() override;
private:
friend class ::AztecBarcodeTest;
BitVector aztecEncode(const QByteArray &data) const;
BitVector bitStuffAndPad(const BitVector &input, int codeWordSize) const;
void paintFullGrid(QImage *img) const;
void paintFullData(QImage *img, const BitVector &data, int layerCount) const;
void paintFullModeMessage(QImage *img, const BitVector &modeData) const;
QImage cropAndScaleFull(QImage *img, int layerCount);
void paintCompactGrid(QImage *img) const;
void paintCompactData(QImage *img, const BitVector &data, int layerCount) const;
void paintCompactModeMessage(QImage *img, const BitVector &modeData) const;
QImage cropAndScaleCompact(QImage *img, int layerCount);
};
}
#endif // PRISON_AZTECCODE_H
@@ -0,0 +1,172 @@
/*
SPDX-FileCopyrightText: 2010-2016 Sune Vuorela <sune@vuorela.dk>
SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include <config-prison.h>
#include "barcode.h"
#include "abstractbarcode_p.h"
#include "aztecbarcode_p.h"
#include "code128barcode_p.h"
#include "code39barcode_p.h"
#include "code93barcode_p.h"
#include "datamatrixbarcode_p.h"
#include "pdf417barcode_p.h"
#include "qrcodebarcode_p.h"
#if HAVE_ZXING
#include "zxingonedbarcode_p.h"
#endif
#include <QColor>
#include <QPainter>
#include <QVariant>
using namespace Prison;
std::optional<Barcode> Barcode::create(Prison::BarcodeType format)
{
std::unique_ptr<AbstractBarcodePrivate> d;
switch (format) {
case Prison::QRCode:
d = std::make_unique<QRCodeBarcode>();
break;
case Prison::DataMatrix:
#if HAVE_DMTX
d = std::make_unique<DataMatrixBarcode>();
#endif
break;
case Prison::Aztec:
d = std::make_unique<AztecBarcode>();
break;
case Prison::Code39:
d = std::make_unique<Code39Barcode>();
break;
case Prison::Code93:
d = std::make_unique<Code93Barcode>();
break;
case Prison::Code128:
d = std::make_unique<Code128Barcode>();
break;
case Prison::PDF417:
#if HAVE_ZXING
d = std::make_unique<Pdf417Barcode>();
#endif
break;
case Prison::EAN13:
#if HAVE_ZXING
d = std::make_unique<ZXingOneDBarcode<ZXing::BarcodeFormat::EAN13>>();
#endif
break;
}
if (d) {
d->m_format = format;
return Barcode(std::move(d));
}
return std::nullopt;
}
Barcode::Barcode(std::unique_ptr<AbstractBarcodePrivate> &&dd)
: d(std::move(dd))
{
}
Barcode::Barcode(Barcode &&) = default;
Barcode::~Barcode() = default;
Barcode &Barcode::operator=(Barcode &&) = default;
Prison::BarcodeType Barcode::format() const
{
return d->m_format;
}
QString Barcode::data() const
{
return d->m_data.userType() == QMetaType::QString ? d->m_data.toString() : QString();
}
QByteArray Barcode::byteArrayData() const
{
return d->m_data.userType() == QMetaType::QByteArray ? d->m_data.toByteArray() : QByteArray();
}
QImage Barcode::toImage(const QSizeF &size)
{
d->recompute();
if (d->m_cache.isNull() || d->sizeTooSmall(size)) {
return QImage();
}
// scale to the requested size, using only full integer factors to keep the code readable
int scaleX = std::max<int>(1, size.width() / d->m_cache.width());
int scaleY = std::max<int>(1, size.height() / d->m_cache.height());
if (dimensions() == TwoDimensions) {
scaleX = scaleY = std::min(scaleX, scaleY);
}
QImage out(d->m_cache.width() * scaleX, d->m_cache.height() * scaleY, d->m_cache.format());
QPainter p(&out);
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
p.drawImage(out.rect(), d->m_cache, d->m_cache.rect());
return out;
}
void Barcode::setData(const QString &data)
{
if (d) {
d->m_data = data;
d->m_cache = QImage();
}
}
void Barcode::setData(const QByteArray &data)
{
d->m_data = data;
d->m_cache = QImage();
}
QSizeF Barcode::minimumSize() const
{
d->recompute();
return d->m_cache.size();
}
QSizeF Barcode::preferredSize(qreal devicePixelRatio) const
{
d->recompute();
return d->preferredSize(devicePixelRatio);
}
QColor Barcode::backgroundColor() const
{
return d->m_background;
}
QColor Barcode::foregroundColor() const
{
return d->m_foreground;
}
void Barcode::setBackgroundColor(const QColor &backgroundcolor)
{
if (backgroundcolor != backgroundColor()) {
d->m_background = backgroundcolor;
d->m_cache = QImage();
}
}
void Barcode::setForegroundColor(const QColor &foregroundcolor)
{
if (foregroundcolor != foregroundColor()) {
d->m_foreground = foregroundcolor;
d->m_cache = QImage();
}
}
Barcode::Dimensions Barcode::dimensions() const
{
return d->m_dimension;
}
@@ -0,0 +1,155 @@
/*
SPDX-FileCopyrightText: 2010-2016 Sune Vuorela <sune@vuorela.dk>
SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_BARCODE_H
#define PRISON_BARCODE_H
#include "prison_export.h"
#include "prison.h"
#include <qglobal.h>
#include <memory>
class QByteArray;
class QColor;
class QImage;
class QSizeF;
class QString;
namespace Prison
{
class AbstractBarcodePrivate;
/**
* A barcode generator for a fixed barcode format.
*
* @note This replaces Prison::createBarcode and AbstractBarcode* from KF5.
* You can create Barcode instances directly now and specify the format in its
* constructor.
* Rather than checking for createBarcode returning a @c nullptr, check whether
* the format is not Prison::Null.
*
* @since 6.0
*/
class PRISON_EXPORT Barcode
{
public:
Barcode(Barcode &&);
~Barcode();
Barcode &operator=(Barcode &&);
/** Barcode format of this barcode generator. */
Prison::BarcodeType format() const;
/**
* Textual content encoded in this barcode.
* This returns an empty QString if binary content is set.
* @see byteArrayData()
*/
QString data() const;
/**
* Binary data encoded in this barcode.
* This returns an empty QByteArray if textual content is set.
* @see data()
* @since 5.85
*/
QByteArray byteArrayData() const;
/**
* Sets textual data to be drawn as a barcode.
* Only use this function if your content is textual, use the QByteArray overload
* when your content contains non-textual binary content.
* Calling this function does not do any repaints of anything, they are
* your own responsibility.
* @param data textual barcode content
*/
void setData(const QString &data);
/**
* Sets binary data to be drawn as a barcode.
* Prefer the QString overload if your content is purely textual, to reduce
* the risk of encoding issues for non-ASCII content.
* Calling this function does not do any repaints of anything, they are
* your own responsibility.
* @param data binary barcode content
* @since 5.85
*/
void setData(const QByteArray &data);
/**
* Creates a image with a barcode on
* @return QImage with a barcode on, trying to match the requested \param size
*
* If one of the dimensions of @param size is smaller than the matching dimension in \ref minimumSize,
* a null QImage will be returned
*/
QImage toImage(const QSizeF &size);
/**
* The minimal amount of pixels needed to represent this barcode without loss of information.
* That is, the size of the barcode image if each line or dot is just one pixel wide.
* On normal screens that is not enough for barcode scanners to reliably detect the barcode
* though.
* @see preferredSize
*/
QSizeF minimumSize() const;
/**
* The recommended size for this barcode when shown on a screen.
* This is typically significantly larger than trueMinimumSize() so that
* barcode scanners tend to reliably detect the code. As this depends
* on the physical resolution of the output, you have to pass the device
* pixel ration of the output screen here.
* @param devicePixelRatio The device pixel ratio of the screen this is shown on.
* @see trueMinimumSize
* @since 5.69
*/
QSizeF preferredSize(qreal devicePixelRatio) const;
/**
* @return the foreground color (by default black) to be used for the barcode.
*/
QColor foregroundColor() const;
/**
* @return the background color (by default white) to be used for the barcode.
*/
QColor backgroundColor() const;
/**
* sets the foreground color
* @param foregroundcolor - the new foreground color
*/
void setForegroundColor(const QColor &foregroundcolor);
/**
* sets the background color
* @param backgroundcolor - the new background color
*/
void setBackgroundColor(const QColor &backgroundcolor);
/** Dimensions of the barcode. */
enum Dimensions : uint8_t {
NoDimensions, ///< Null barcode.
OneDimension, ///< One-dimensional barcode.
TwoDimensions, ///< 2D matrix code.
};
/** Returns the amount of dimensions of the barcode. */
Dimensions dimensions() const;
/** Create a new barcode generator.
*
* If a format is requested that is not supported by the current build
* due to missing/disabled optional dependencies, Barcode::format() will
* return Prison::Null.
*/
static std::optional<Prison::Barcode> create(Prison::BarcodeType type);
private:
friend class AbstractBarcodePrivate;
explicit Barcode(std::unique_ptr<AbstractBarcodePrivate> &&d);
std::unique_ptr<class AbstractBarcodePrivate> d;
};
}
#endif
@@ -0,0 +1,29 @@
/*
SPDX-FileCopyrightText: 2020 Laurent Montel <montel@kde.org>
SPDX-License-Identifier: MIT
*/
#include "barcodeutil_p.h"
#include <QVariant>
using namespace Prison;
QList<bool> BarCodeUtil::barSequence(const char *str)
{
Q_ASSERT(strlen(str) == 9); // this is a internal helper tool, only called with fixed strings in here, all 9 chars long
QList<bool> ret;
for (int i = 0; i < 9; i++) {
ret.append(str[i] == '1');
Q_ASSERT(str[i] == '0' || str[i] == '1');
}
return ret;
}
QByteArray BarCodeUtil::asLatin1ByteArray(const QVariant &data)
{
if (data.typeId() == QMetaType::QString) {
return data.toString().toLatin1();
}
return data.toByteArray();
}
@@ -0,0 +1,20 @@
/*
SPDX-FileCopyrightText: 2020 Laurent Montel <montel@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef BARCODEUTIL_H
#define BARCODEUTIL_H
#include <QList>
namespace Prison
{
namespace BarCodeUtil
{
QList<bool> barSequence(const char *str);
QByteArray asLatin1ByteArray(const QVariant &data);
}
}
#endif // BARCODEUTIL_H
@@ -0,0 +1,110 @@
/*
SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include "bitvector_p.h"
using namespace Prison;
BitVector::BitVector() = default;
BitVector::~BitVector() = default;
void BitVector::appendLSB(int data, int bits)
{
for (int i = 0; i < bits; ++i) {
appendBit(data & (1 << i));
}
}
void BitVector::appendMSB(int data, int bits)
{
for (int i = bits - 1; i >= 0; --i) {
appendBit(data & (1 << i));
}
}
void BitVector::appendBit(bool bit)
{
const auto subIdx = m_size % 8;
if (subIdx == 0) {
m_data.append('\0');
}
if (bit) {
m_data.data()[m_data.size() - 1] |= (1 << subIdx);
}
++m_size;
}
void BitVector::append(const BitVector &other)
{
for (int i = 0; i < other.size(); ++i) {
appendBit(other.at(i));
}
}
bool BitVector::at(int index) const
{
const auto majIdx = index / 8;
const auto minIdx = index % 8;
return (m_data.at(majIdx) & (1 << minIdx)) >> minIdx;
}
void BitVector::clear()
{
m_data.clear();
m_size = 0;
}
void BitVector::reserve(int size)
{
m_data.reserve((size / 8) + 1);
}
int BitVector::size() const
{
return m_size;
}
int BitVector::valueAtMSB(int index, int size) const
{
int res = 0;
for (int i = 0; i < size; ++i) {
res = res << 1;
res |= (at(index + i) ? 1 : 0);
}
return res;
}
BitVector::iterator BitVector::begin() const
{
iterator it;
it.m_index = 0;
it.m_vector = this;
return it;
}
BitVector::iterator BitVector::end() const
{
iterator it;
it.m_index = m_size;
it.m_vector = this;
return it;
}
bool BitVector::operator==(const BitVector &other) const
{
return m_size == other.m_size && m_data == other.m_data;
}
bool BitVector::operator!=(const Prison::BitVector &other) const
{
return m_size != other.m_size || m_data != other.m_data;
}
QDebug operator<<(QDebug dbg, const Prison::BitVector &v)
{
dbg << v.m_data.toHex();
return dbg;
}
@@ -0,0 +1,78 @@
/*
SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_BITVECTOR_P_H
#define PRISON_BITVECTOR_P_H
#include <QByteArray>
#include <QDebug>
namespace Prison
{
class BitVector;
}
QDebug operator<<(QDebug dbg, const Prison::BitVector &v);
namespace Prison
{
/** Vector for working with a set of bits without byte alignment. */
class BitVector
{
public:
BitVector();
~BitVector();
class iterator
{
public:
inline bool operator!=(const iterator &other)
{
return m_index != other.m_index;
}
inline bool operator*() const
{
return m_vector->at(m_index);
}
inline iterator operator++()
{
++m_index;
return *this;
}
private:
friend class BitVector;
const BitVector *m_vector;
int m_index;
};
/** Append the lowest @p bits of @p data with the least significant bit first. */
void appendLSB(int data, int bits);
/** Append the lowest @p bits of @p data with the most significant bit first. */
void appendMSB(int data, int bits);
void appendBit(bool bit);
void append(const BitVector &other);
/** Returns the bit at index @p index. */
bool at(int index) const;
void clear();
void reserve(int size);
int size() const;
/** Returns the value starting at @p index of size @p size. */
int valueAtMSB(int index, int size) const;
iterator begin() const;
iterator end() const;
bool operator==(const BitVector &other) const;
bool operator!=(const BitVector &other) const;
private:
friend QDebug(::operator<<)(QDebug dbg, const Prison::BitVector &v);
QByteArray m_data;
int m_size = 0;
};
}
#endif // PRISON_BITVECTOR_P_H
@@ -0,0 +1,336 @@
/*
SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include "code128barcode_p.h"
#include "barcodeutil_p.h"
#include "bitvector_p.h"
#include "prison_debug.h"
#include <QImage>
#include <QPainter>
using namespace Prison;
enum {
SymbolSize = 11,
StopPatternSize = 13,
StopPattern = 108,
QuietZone = 10,
};
enum CodeSet : uint8_t {
CodeSetA = 0,
CodeSetB = 1,
CodeSetC = 2,
CodeSetUnknown = 3,
};
enum CodeSetOp : uint8_t {
None = 255,
StartA = 103,
StartB = 104,
StartC = 105,
Shift = 98,
LatchA = 101,
LatchB = 100,
LatchC = 99,
};
Code128Barcode::Code128Barcode()
: AbstractBarcodePrivate(Barcode::OneDimension)
{
}
Code128Barcode::~Code128Barcode() = default;
QImage Code128Barcode::paintImage()
{
const auto bits = encode(BarCodeUtil::asLatin1ByteArray(m_data));
const auto width = bits.size() + 2 * QuietZone;
QImage img(width, 1, QImage::Format_ARGB32);
img.fill(m_background);
QPainter p(&img);
for (int i = 0; i < bits.size(); ++i) {
if (bits.at(i)) {
img.setPixel(QuietZone + i, 0, m_foreground.rgb());
}
}
return img;
}
// Code 128 symbol table
static const uint16_t code128_symbols[] = {
0b11011001100, // 0
0b11001101100,
0b11001100110,
0b10010011000,
0b10010001100,
0b10001001100,
0b10011001000,
0b10011000100,
0b10001100100,
0b11001001000,
0b11001000100, // 10
0b11000100100,
0b10110011100,
0b10011011100,
0b10011001110,
0b10111001100,
0b10011101100,
0b10011100110,
0b11001110010,
0b11001011100,
0b11001001110, // 20
0b11011100100,
0b11001110100,
0b11101101110,
0b11101001100,
0b11100101100,
0b11100100110,
0b11101100100,
0b11100110100,
0b11100110010,
0b11011011000, // 30
0b11011000110,
0b11000110110,
0b10100011000,
0b10001011000,
0b10001000110,
0b10110001000,
0b10001101000,
0b10001100010,
0b11010001000,
0b11000101000, // 40
0b11000100010,
0b10110111000,
0b10110001110,
0b10001101110,
0b10111011000,
0b10111000110,
0b10001110110,
0b11101110110,
0b11010001110,
0b11000101110, // 50
0b11011101000,
0b11011100010,
0b11011101110,
0b11101011000,
0b11101000110,
0b11100010110,
0b11101101000,
0b11101100010,
0b11100011010,
0b11101111010, // 60
0b11001000010,
0b11110001010,
0b10100110000,
0b10100001100,
0b10010110000,
0b10010000110,
0b10000101100,
0b10000100110,
0b10110010000,
0b10110000100, // 70
0b10011010000,
0b10011000010,
0b10000110100,
0b10000110010,
0b11000010010,
0b11001010000,
0b11110111010,
0b11000010100,
0b10001111010,
0b10100111100, // 80
0b10010111100,
0b10010011110,
0b10111100100,
0b10011110100,
0b10011110010,
0b11110100100,
0b11110010100,
0b11110010010,
0b11011011110,
0b11011110110, // 90
0b11110110110,
0b10101111000,
0b10100011110,
0b10001011110,
0b10111101000,
0b10111100010,
0b11110101000,
0b11110100010,
0b10111011110,
0b10111101110, // 100
0b11101011110,
0b11110101110,
0b11010000100,
0b11010010000,
0b11010011100,
0b11000111010,
0b11010111000,
0b1100011101011,
};
static uint8_t symbolForCharacter(const QByteArray &data, int index, CodeSet set)
{
const auto c1 = data.at(index);
switch (set) {
case CodeSetA:
return (c1 < ' ') ? c1 + 64 : c1 - ' ';
case CodeSetB:
return c1 - ' ';
case CodeSetC: {
const auto c2 = data.at(index + 1);
return ((c1 - '0') * 10) + c2 - '0';
}
case CodeSetUnknown:
Q_UNREACHABLE();
}
Q_UNREACHABLE();
return {};
}
struct CodeSetChange {
CodeSet set;
CodeSetOp symbol;
};
static bool isInCodeSetA(char c)
{
return c <= 95;
}
static bool isInCodeSetB(char c)
{
// ### this does not consider FNC4 high byte encoding
return c >= 32;
}
static CodeSetChange opForData(const QByteArray &data, int index, CodeSet currentSet)
{
// determine if Code C makes sense at this point
int codeC = 0;
for (int i = index; i < data.size(); ++i, ++codeC) {
if (data.at(i) < '0' || data.at(i) > '9') {
break;
}
}
if (currentSet == CodeSetC && codeC >= 2) { // already in C
return {CodeSetC, None};
}
if (codeC >= 6 // that's always good enough
|| (index == 0 && codeC >= 4) // beginning of data
|| (index + codeC == data.size() && codeC >= 4) // end of data
|| (codeC == data.size() && codeC == 2) // 2 ...
|| (codeC == data.size() && codeC == 4)) // ... or 4 as the entire data
{
return currentSet == CodeSetUnknown ? CodeSetChange{CodeSetC, StartC} : CodeSetChange{CodeSetC, LatchC};
}
// if we are in Code A or Code B, check if we need to switch for the next char
// this is a shortcut to prevent the below more extensive search from making this O(n²) in the common case
if ((currentSet == CodeSetA && isInCodeSetA(data.at(index))) || (currentSet == CodeSetB && isInCodeSetB(data.at(index)))) {
return {currentSet, None};
}
// we need to switch to A or B, select which one, and select whether to use start, shift or latch
const auto nextA = isInCodeSetA(data.at(index));
const auto nextB = isInCodeSetB(data.at(index));
// count how many following characters we could encode in A or B
int countA = 0;
for (int i = index + 1; i < data.size(); ++i, ++countA) {
if (!isInCodeSetA(data.at(i))) {
break;
}
}
int countB = 0;
for (int i = index + 1; i < data.size(); ++i, ++countB) {
if (!isInCodeSetB(data.at(i))) {
break;
}
}
// select how we want to switch to Code A or Code B, biased to B as that's the more useful one in general
switch (currentSet) {
case CodeSetUnknown:
// if we are at the start, take whichever code will get us further, or the only one that works
if (nextA && nextB) {
return countA > countB ? CodeSetChange{CodeSetA, StartA} : CodeSetChange{CodeSetB, StartB};
}
return nextA ? CodeSetChange{CodeSetA, StartA} : CodeSetChange{CodeSetB, StartB};
case CodeSetC:
// same for Code C
if (nextA && nextB) {
return countA > countB ? CodeSetChange{CodeSetA, LatchA} : CodeSetChange{CodeSetB, LatchB};
}
return nextA ? CodeSetChange{CodeSetA, LatchA} : CodeSetChange{CodeSetB, LatchB};
case CodeSetA:
// switch or latch to B?
return CodeSetChange{CodeSetB, countB >= countA ? LatchB : Shift};
case CodeSetB:
// switch or latch to A?
return CodeSetChange{CodeSetA, countA > countB ? LatchA : Shift};
}
Q_UNREACHABLE();
return CodeSetChange{currentSet, None};
}
BitVector Code128Barcode::encode(const QByteArray &data) const
{
BitVector v;
if (data.isEmpty()) {
return v;
}
// determine code set for start
const auto op = opForData(data, 0, CodeSetUnknown);
auto currentSet = op.set;
// write start code
qCDebug(Log) << "start symbol:" << op.symbol << code128_symbols[op.symbol];
v.appendMSB(code128_symbols[op.symbol], SymbolSize);
uint32_t checksum = op.symbol;
uint32_t checksumWeight = 1;
for (int i = 0; i < data.size(); i += currentSet == CodeSetC ? 2 : 1) {
if (static_cast<uint8_t>(data.at(i)) > 127) { // FNC4 encoding not implemented yet
continue;
}
// perform code switch if needed
const auto op = opForData(data, i, currentSet);
if (op.symbol != None) {
qCDebug(Log) << "op symbol:" << op.symbol << code128_symbols[op.symbol];
v.appendMSB(code128_symbols[op.symbol], SymbolSize);
checksum += op.symbol * checksumWeight++;
}
// encode current symbol
const auto symbol = symbolForCharacter(data, i, op.set);
qCDebug(Log) << "data symbol:" << symbol << code128_symbols[symbol];
v.appendMSB(code128_symbols[symbol], SymbolSize);
checksum += symbol * checksumWeight++;
// update current code set
if (op.symbol != Shift) {
currentSet = op.set;
}
}
// encode checksum
qCDebug(Log) << "checksum:" << checksum << code128_symbols[checksum % 103];
v.appendMSB(code128_symbols[checksum % 103], SymbolSize);
// add stop pattern
v.appendMSB(code128_symbols[StopPattern], StopPatternSize);
return v;
}
@@ -0,0 +1,37 @@
/*
SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_CODE128BARCODE_H
#define PRISON_CODE128BARCODE_H
#include "abstractbarcode_p.h"
class Code128BarcodeTest;
namespace Prison
{
class BitVector;
/** Code 128 barcode
* @see https://en.wikipedia.org/wiki/Code_128
*/
class Code128Barcode : public AbstractBarcodePrivate
{
public:
Code128Barcode();
~Code128Barcode() override;
protected:
QImage paintImage() override;
private:
friend class ::Code128BarcodeTest;
BitVector encode(const QByteArray &data) const;
};
}
#endif // PRISON_CODE128BARCODE_H
@@ -0,0 +1,171 @@
/*
SPDX-FileCopyrightText: 2011 Geoffry Song <goffrie@gmail.com>
SPDX-License-Identifier: MIT
*/
#include "barcodeutil_p.h"
#include "code39barcode_p.h"
#include <QChar>
using namespace Prison;
static QList<bool> sequenceForChar(ushort c)
{
switch (QChar::toUpper(c)) {
case '0':
return BarCodeUtil::barSequence("000110100");
case '1':
return BarCodeUtil::barSequence("100100001");
case '2':
return BarCodeUtil::barSequence("001100001");
case '3':
return BarCodeUtil::barSequence("101100000");
case '4':
return BarCodeUtil::barSequence("000110001");
case '5':
return BarCodeUtil::barSequence("100110000");
case '6':
return BarCodeUtil::barSequence("001110000");
case '7':
return BarCodeUtil::barSequence("000100101");
case '8':
return BarCodeUtil::barSequence("100100100");
case '9':
return BarCodeUtil::barSequence("001100100");
case 'A':
return BarCodeUtil::barSequence("100001001");
case 'B':
return BarCodeUtil::barSequence("001001001");
case 'C':
return BarCodeUtil::barSequence("101001000");
case 'D':
return BarCodeUtil::barSequence("000011001");
case 'E':
return BarCodeUtil::barSequence("100011000");
case 'F':
return BarCodeUtil::barSequence("001011000");
case 'G':
return BarCodeUtil::barSequence("000001101");
case 'H':
return BarCodeUtil::barSequence("100001100");
case 'I':
return BarCodeUtil::barSequence("001001100");
case 'J':
return BarCodeUtil::barSequence("000011100");
case 'K':
return BarCodeUtil::barSequence("100000011");
case 'L':
return BarCodeUtil::barSequence("001000011");
case 'M':
return BarCodeUtil::barSequence("101000010");
case 'N':
return BarCodeUtil::barSequence("000010011");
case 'O':
return BarCodeUtil::barSequence("100010010");
case 'P':
return BarCodeUtil::barSequence("001010010");
case 'Q':
return BarCodeUtil::barSequence("000000111");
case 'R':
return BarCodeUtil::barSequence("100000110");
case 'S':
return BarCodeUtil::barSequence("001000110");
case 'T':
return BarCodeUtil::barSequence("000010110");
case 'U':
return BarCodeUtil::barSequence("110000001");
case 'V':
return BarCodeUtil::barSequence("011000001");
case 'W':
return BarCodeUtil::barSequence("111000000");
case 'X':
return BarCodeUtil::barSequence("010010001");
case 'Y':
return BarCodeUtil::barSequence("110010000");
case 'Z':
return BarCodeUtil::barSequence("011010000");
case '-':
return BarCodeUtil::barSequence("010000101");
case '.':
return BarCodeUtil::barSequence("110000100");
case ' ':
return BarCodeUtil::barSequence("011000100");
case '$':
return BarCodeUtil::barSequence("010101000");
case '/':
return BarCodeUtil::barSequence("010100010");
case '+':
return BarCodeUtil::barSequence("010001010");
case '%':
return BarCodeUtil::barSequence("000101010");
default:
return QList<bool>(); // unknown character
}
}
Code39Barcode::Code39Barcode()
: AbstractBarcodePrivate(Barcode::OneDimension)
{
}
Code39Barcode::~Code39Barcode() = default;
QImage Code39Barcode::paintImage()
{
QList<bool> barcode;
// convert text into sequences of wide/narrow bars
{
// the guard sequence that goes on each end
const QList<bool> endSequence = BarCodeUtil::barSequence("010010100");
barcode += endSequence;
barcode += false;
// translate the string
const auto str = BarCodeUtil::asLatin1ByteArray(m_data);
for (int i = 0; i < str.size(); i++) {
QList<bool> b = sequenceForChar(str.at(i));
if (!b.empty()) {
barcode += b;
barcode += false; // add a narrow space between each character
}
}
// ending guard
barcode += endSequence;
}
/*
calculate integer bar widths that fit inside `size'
each character has 6 narrow bars and 3 wide bars and there is a narrow bar between characters
restrictions:
*) smallWidth * 2 <= largeWidth <= smallWidth * 3
- in other words, the ratio largeWidth:smallWidth is between 3:1 and 2:1
*) wide * largeWidth + narrow * smallWidth <= size.width()
- the barcode has to fit within the given size
*/
const int wide = barcode.count(true);
const int narrow = barcode.count(false);
// wide bar width
const int largeWidth = 2;
// narrow bar width
const int smallWidth = 1;
Q_ASSERT(largeWidth > smallWidth);
const int quietZoneWidth = 10 * smallWidth;
// one line of the result image
QList<QRgb> line;
line.reserve(wide * largeWidth + narrow * smallWidth + 2 * quietZoneWidth);
line.insert(0, quietZoneWidth, m_background.rgba());
for (int i = 0; i < barcode.size(); i++) {
const QRgb color = (((i & 1) == 0) ? m_foreground : m_background).rgba(); // alternate between foreground and background color
const int width = barcode.at(i) ? largeWidth : smallWidth;
for (int j = 0; j < width; j++) {
line.append(color);
}
}
line.insert(line.size(), quietZoneWidth, m_background.rgba());
// build the complete barcode
QImage ret(line.size(), 1, QImage::Format_ARGB32);
memcpy(ret.scanLine(0), line.data(), line.size() * sizeof(QRgb));
return ret;
}
@@ -0,0 +1,35 @@
/*
SPDX-FileCopyrightText: 2011 Geoffry Song <goffrie@gmail.com>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_CODE39BARCODE_H
#define PRISON_CODE39BARCODE_H
#include "abstractbarcode_p.h"
namespace Prison
{
/**
* Code 39 Barcode generator
*/
class Code39Barcode : public Prison::AbstractBarcodePrivate
{
public:
/**
* creates a Code 39 generator
*/
Code39Barcode();
~Code39Barcode() override;
protected:
/**
* This function generates the barcode
* @return QImage containing a barcode, trying to approximate the requested sizes, or a null QImage if it can't be painted within requested size
*/
QImage paintImage() override;
};
} // namespace
#endif // PRISON_CODE39BARCODE_H
@@ -0,0 +1,665 @@
/*
SPDX-FileCopyrightText: 2011 Geoffry Song <goffrie@gmail.com>
SPDX-License-Identifier: MIT
*/
#include "barcodeutil_p.h"
#include "code93barcode_p.h"
#include <QChar>
using namespace Prison;
// returns a list of 9 bar colors, where `true' means foreground and `false' means background color
static QList<bool> sequenceForID(int id)
{
switch (id) {
case 0:
return BarCodeUtil::barSequence("100010100"); // 0-9
case 1:
return BarCodeUtil::barSequence("101001000");
case 2:
return BarCodeUtil::barSequence("101000100");
case 3:
return BarCodeUtil::barSequence("101000010");
case 4:
return BarCodeUtil::barSequence("100101000");
case 5:
return BarCodeUtil::barSequence("100100100");
case 6:
return BarCodeUtil::barSequence("100100010");
case 7:
return BarCodeUtil::barSequence("101010000");
case 8:
return BarCodeUtil::barSequence("100010010");
case 9:
return BarCodeUtil::barSequence("100001010");
case 10:
return BarCodeUtil::barSequence("110101000"); // A-Z
case 11:
return BarCodeUtil::barSequence("110100100");
case 12:
return BarCodeUtil::barSequence("110100010");
case 13:
return BarCodeUtil::barSequence("110010100");
case 14:
return BarCodeUtil::barSequence("110010010");
case 15:
return BarCodeUtil::barSequence("110001010");
case 16:
return BarCodeUtil::barSequence("101101000");
case 17:
return BarCodeUtil::barSequence("101100100");
case 18:
return BarCodeUtil::barSequence("101100010");
case 19:
return BarCodeUtil::barSequence("100110100");
case 20:
return BarCodeUtil::barSequence("100011010");
case 21:
return BarCodeUtil::barSequence("101011000");
case 22:
return BarCodeUtil::barSequence("101001100");
case 23:
return BarCodeUtil::barSequence("101000110");
case 24:
return BarCodeUtil::barSequence("100101100");
case 25:
return BarCodeUtil::barSequence("100010110");
case 26:
return BarCodeUtil::barSequence("110110100");
case 27:
return BarCodeUtil::barSequence("110110010");
case 28:
return BarCodeUtil::barSequence("110101100");
case 29:
return BarCodeUtil::barSequence("110100110");
case 30:
return BarCodeUtil::barSequence("110010110");
case 31:
return BarCodeUtil::barSequence("110011010");
case 32:
return BarCodeUtil::barSequence("101101100");
case 33:
return BarCodeUtil::barSequence("101100110");
case 34:
return BarCodeUtil::barSequence("100110110");
case 35:
return BarCodeUtil::barSequence("100111010");
case 36:
return BarCodeUtil::barSequence("100101110"); // -
case 37:
return BarCodeUtil::barSequence("111010100"); // .
case 38:
return BarCodeUtil::barSequence("111010010"); // space
case 39:
return BarCodeUtil::barSequence("111001010"); // $
case 40:
return BarCodeUtil::barSequence("101101110"); // /
case 41:
return BarCodeUtil::barSequence("101110110"); // +
case 42:
return BarCodeUtil::barSequence("110101110"); // $
case 43:
return BarCodeUtil::barSequence("100100110"); // ($)
case 44:
return BarCodeUtil::barSequence("111011010"); // (%)
case 45:
return BarCodeUtil::barSequence("111010110"); // (/)
case 46:
return BarCodeUtil::barSequence("100110010"); // (+)
case 47:
return BarCodeUtil::barSequence("101011110"); // stop sequence
default:
// unknown ID... shouldn't happen
qWarning("Code93Barcode::sequenceForID called with unknown ID");
return QList<bool>();
}
}
// returns the list of IDs that represent a character
static QList<int> codesForChar(uint c)
{
QList<int> ret;
switch (c) {
case 0:
ret += 44;
ret += 30;
break;
case 1:
ret += 43;
ret += 10;
break;
case 2:
ret += 43;
ret += 11;
break;
case 3:
ret += 43;
ret += 12;
break;
case 4:
ret += 43;
ret += 13;
break;
case 5:
ret += 43;
ret += 14;
break;
case 6:
ret += 43;
ret += 15;
break;
case 7:
ret += 43;
ret += 16;
break;
case 8:
ret += 43;
ret += 17;
break;
case 9:
ret += 43;
ret += 18;
break;
case 10:
ret += 43;
ret += 19;
break;
case 11:
ret += 43;
ret += 20;
break;
case 12:
ret += 43;
ret += 21;
break;
case 13:
ret += 43;
ret += 22;
break;
case 14:
ret += 43;
ret += 23;
break;
case 15:
ret += 43;
ret += 24;
break;
case 16:
ret += 43;
ret += 25;
break;
case 17:
ret += 43;
ret += 26;
break;
case 18:
ret += 43;
ret += 27;
break;
case 19:
ret += 43;
ret += 28;
break;
case 20:
ret += 43;
ret += 29;
break;
case 21:
ret += 43;
ret += 30;
break;
case 22:
ret += 43;
ret += 31;
break;
case 23:
ret += 43;
ret += 32;
break;
case 24:
ret += 43;
ret += 33;
break;
case 25:
ret += 43;
ret += 34;
break;
case 26:
ret += 43;
ret += 35;
break;
case 27:
ret += 44;
ret += 10;
break;
case 28:
ret += 44;
ret += 11;
break;
case 29:
ret += 44;
ret += 12;
break;
case 30:
ret += 44;
ret += 13;
break;
case 31:
ret += 44;
ret += 14;
break;
case 32:
ret += 38;
break;
case 33:
ret += 45;
ret += 10;
break;
case 34:
ret += 45;
ret += 11;
break;
case 35:
ret += 45;
ret += 12;
break;
case 36:
ret += 39;
break;
case 37:
ret += 42;
break;
case 38:
ret += 45;
ret += 15;
break;
case 39:
ret += 45;
ret += 16;
break;
case 40:
ret += 45;
ret += 17;
break;
case 41:
ret += 45;
ret += 18;
break;
case 42:
ret += 45;
ret += 19;
break;
case 43:
ret += 41;
break;
case 44:
ret += 45;
ret += 21;
break;
case 45:
ret += 36;
break;
case 46:
ret += 37;
break;
case 47:
ret += 40;
break;
case 48:
ret += 0;
break;
case 49:
ret += 1;
break;
case 50:
ret += 2;
break;
case 51:
ret += 3;
break;
case 52:
ret += 4;
break;
case 53:
ret += 5;
break;
case 54:
ret += 6;
break;
case 55:
ret += 7;
break;
case 56:
ret += 8;
break;
case 57:
ret += 9;
break;
case 58:
ret += 45;
ret += 35;
break;
case 59:
ret += 44;
ret += 15;
break;
case 60:
ret += 44;
ret += 16;
break;
case 61:
ret += 44;
ret += 17;
break;
case 62:
ret += 44;
ret += 18;
break;
case 63:
ret += 44;
ret += 19;
break;
case 64:
ret += 44;
ret += 31;
break;
case 65:
ret += 10;
break;
case 66:
ret += 11;
break;
case 67:
ret += 12;
break;
case 68:
ret += 13;
break;
case 69:
ret += 14;
break;
case 70:
ret += 15;
break;
case 71:
ret += 16;
break;
case 72:
ret += 17;
break;
case 73:
ret += 18;
break;
case 74:
ret += 19;
break;
case 75:
ret += 20;
break;
case 76:
ret += 21;
break;
case 77:
ret += 22;
break;
case 78:
ret += 23;
break;
case 79:
ret += 24;
break;
case 80:
ret += 25;
break;
case 81:
ret += 26;
break;
case 82:
ret += 27;
break;
case 83:
ret += 28;
break;
case 84:
ret += 29;
break;
case 85:
ret += 30;
break;
case 86:
ret += 31;
break;
case 87:
ret += 32;
break;
case 88:
ret += 33;
break;
case 89:
ret += 34;
break;
case 90:
ret += 35;
break;
case 91:
ret += 44;
ret += 20;
break;
case 92:
ret += 44;
ret += 21;
break;
case 93:
ret += 44;
ret += 22;
break;
case 94:
ret += 44;
ret += 23;
break;
case 95:
ret += 44;
ret += 24;
break;
case 96:
ret += 44;
ret += 32;
break;
case 97:
ret += 46;
ret += 10;
break;
case 98:
ret += 46;
ret += 11;
break;
case 99:
ret += 46;
ret += 12;
break;
case 100:
ret += 46;
ret += 13;
break;
case 101:
ret += 46;
ret += 14;
break;
case 102:
ret += 46;
ret += 15;
break;
case 103:
ret += 46;
ret += 16;
break;
case 104:
ret += 46;
ret += 17;
break;
case 105:
ret += 46;
ret += 18;
break;
case 106:
ret += 46;
ret += 19;
break;
case 107:
ret += 46;
ret += 20;
break;
case 108:
ret += 46;
ret += 21;
break;
case 109:
ret += 46;
ret += 22;
break;
case 110:
ret += 46;
ret += 23;
break;
case 111:
ret += 46;
ret += 24;
break;
case 112:
ret += 46;
ret += 25;
break;
case 113:
ret += 46;
ret += 26;
break;
case 114:
ret += 46;
ret += 27;
break;
case 115:
ret += 46;
ret += 28;
break;
case 116:
ret += 46;
ret += 29;
break;
case 117:
ret += 46;
ret += 30;
break;
case 118:
ret += 46;
ret += 31;
break;
case 119:
ret += 46;
ret += 32;
break;
case 120:
ret += 46;
ret += 33;
break;
case 121:
ret += 46;
ret += 34;
break;
case 122:
ret += 46;
ret += 35;
break;
case 123:
ret += 44;
ret += 25;
break;
case 124:
ret += 44;
ret += 26;
break;
case 125:
ret += 44;
ret += 27;
break;
case 126:
ret += 44;
ret += 28;
break;
case 127:
ret += 44;
ret += 29;
break;
}
return ret; // return an empty list for a non-ascii character code
}
// calculate a checksum
static int checksum(const QList<int> &codes, int wrap)
{
int check = 0;
for (int i = 0; i < codes.size(); i++) {
// weight goes from 1 to wrap, right-to-left, then repeats
const int weight = (codes.size() - i - 1) % wrap + 1;
check += codes.at(i) * weight;
}
return check % 47;
}
Code93Barcode::Code93Barcode()
: AbstractBarcodePrivate(Barcode::OneDimension)
{
}
Code93Barcode::~Code93Barcode() = default;
QImage Code93Barcode::paintImage()
{
QList<bool> barcode;
// convert text into sequences of fg/bg bars
{
// translate the string into a code sequence
QList<int> codes;
const auto str = BarCodeUtil::asLatin1ByteArray(m_data);
for (int i = 0; i < str.size(); i++) {
codes += codesForChar(str.at(i));
}
// calculate checksums
codes.append(checksum(codes, 20)); // "C" checksum
codes.append(checksum(codes, 15)); // "K" checksum: includes previous checksum
// now generate the barcode
// the guard sequence that goes on each end
const QList<bool> endSequence = sequenceForID(47);
barcode += endSequence;
// translate codes into bars
for (int i = 0; i < codes.size(); i++) {
barcode += sequenceForID(codes.at(i));
}
// ending guard
barcode += endSequence;
// termination bar
barcode += true;
}
const int barWidth = 1;
const int quietZoneWidth = 10 * barWidth;
// build one line of the result image
QList<QRgb> line;
line.reserve(barWidth * barcode.size() + 2 * quietZoneWidth);
line.insert(0, quietZoneWidth, m_background.rgba());
for (int i = 0; i < barcode.size(); i++) {
const QRgb color = (barcode.at(i) ? m_foreground : m_background).rgba();
for (int j = 0; j < barWidth; j++) {
line.append(color);
}
}
line.insert(line.size(), quietZoneWidth, m_background.rgba());
// build the complete barcode
QImage ret(line.size(), 1, QImage::Format_ARGB32);
memcpy(ret.scanLine(0), line.data(), line.size() * sizeof(QRgb));
return ret;
}
@@ -0,0 +1,33 @@
/*
SPDX-FileCopyrightText: 2011 Geoffry Song <goffrie@gmail.com>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_CODE93BARCODE_H
#define PRISON_CODE93BARCODE_H
#include "abstractbarcode_p.h"
namespace Prison
{
/**
* Code 93 Barcode generator
*/
class Code93Barcode : public Prison::AbstractBarcodePrivate
{
public:
/**
* creates a Code 93 generator
*/
Code93Barcode();
~Code93Barcode() override;
/**
* This function generates the barcode
* @return QImage containing a barcode, trying to approximate the requested sizes
*/
QImage paintImage() override;
};
} // namespace
#endif // PRISON_CODE39BARCODE_H
@@ -0,0 +1,13 @@
/*
SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_CONFIG_H
#define PRISON_CONFIG_H
#cmakedefine01 HAVE_DMTX
#cmakedefine01 HAVE_ZXING
#endif
@@ -0,0 +1,78 @@
/*
SPDX-FileCopyrightText: 2010-2014 Sune Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: MIT
*/
#include "datamatrixbarcode_p.h"
#include <dmtx.h>
using namespace Prison;
DataMatrixBarcode::DataMatrixBarcode()
: AbstractBarcodePrivate(Barcode::TwoDimensions)
{
}
DataMatrixBarcode::~DataMatrixBarcode() = default;
QImage DataMatrixBarcode::paintImage()
{
const auto data = m_data.toString();
if (data.size() > 1200) {
return QImage();
}
DmtxEncode *enc = dmtxEncodeCreate();
dmtxEncodeSetProp(enc, DmtxPropPixelPacking, DmtxPack32bppRGBX);
dmtxEncodeSetProp(enc, DmtxPropModuleSize, 1);
dmtxEncodeSetProp(enc, DmtxPropMarginSize, 2);
QByteArray trimmedData(data.trimmed().toUtf8());
DmtxPassFail result = dmtxEncodeDataMatrix(enc, trimmedData.length(), reinterpret_cast<unsigned char *>(trimmedData.data()));
if (result == DmtxFail) {
dmtxEncodeDestroy(&enc);
return QImage();
}
Q_ASSERT(enc->image->width == enc->image->height);
QImage ret;
if (m_foreground == Qt::black && m_background == Qt::white) {
QImage tmp(enc->image->pxl, enc->image->width, enc->image->height, QImage::Format_ARGB32);
// we need to copy, because QImage generated from a char pointer requires the
// char pointer to be kept around forever, and manually deleted.
ret = tmp.copy();
} else {
if (enc->image->width > 0) {
int size = enc->image->width * enc->image->height * 4;
uchar *img = new uchar[size];
QByteArray background(4, '\0');
background[3] = qAlpha(m_background.rgba());
background[2] = qRed(m_background.rgba());
background[1] = qGreen(m_background.rgba());
background[0] = qBlue(m_background.rgba());
QByteArray foreground(4, '\0');
foreground[3] = qAlpha(m_foreground.rgba());
foreground[2] = qRed(m_foreground.rgba());
foreground[1] = qGreen(m_foreground.rgba());
foreground[0] = qBlue(m_foreground.rgba());
for (int i = 1; i < size; i += 4) {
QByteArray color;
if (enc->image->pxl[i] == 0x00) {
color = foreground;
} else {
color = background;
}
for (int j = 0; j < 4; j++) {
img[i - 1 + j] = color[j];
}
}
QImage tmp(img, enc->image->width, enc->image->height, QImage::Format_ARGB32);
// we need to copy, because QImage generated from a char pointer requires the
// char pointer to be kept around forever, and manually deleted.
ret = tmp.copy();
delete[] img;
}
}
dmtxEncodeDestroy(&enc);
return ret;
}
@@ -0,0 +1,37 @@
/*
SPDX-FileCopyrightText: 2010-2016 Sune Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_DATAMATRIXBARCODE_H
#define PRISON_DATAMATRIXBARCODE_H
#include "abstractbarcode_p.h"
#include "prison_export.h"
namespace Prison
{
/**
* This is a Datamatrix barcode generator that uses libdmtx
* for the actual generation of barcodes.
*/
class DataMatrixBarcode : public Prison::AbstractBarcodePrivate
{
public:
/**
* creates a datamatrixbarcode generator
*/
DataMatrixBarcode();
~DataMatrixBarcode() override;
protected:
/**
* This is the function doing the actual work in generating the barcode
* @return QImage containing a DataMatrix, trying to approximate the requested sizes
*/
QImage paintImage() override;
};
}
#endif // PRISON_DATAMATRIXBARCODE_H
@@ -0,0 +1,148 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: MIT
*/
#include "mecard.h"
#include <vector>
using namespace Prison;
class Prison::MeCardPrivate
{
public:
QStringView header;
struct Element {
QStringView key;
QStringList values;
bool operator<(QStringView other) const;
};
std::vector<Element> elements;
};
bool MeCardPrivate::Element::operator<(QStringView other) const
{
return key < other;
}
MeCard::MeCard()
: d(new MeCardPrivate())
{
}
MeCard::MeCard(MeCard &&other) noexcept
: d(std::move(other.d))
{
other.d.reset();
}
MeCard &MeCard::operator=(MeCard &&other) noexcept
{
std::swap(d, other.d);
return *this;
}
MeCard::~MeCard() = default;
std::optional<MeCard> MeCard::parse(const QString &data)
{
const auto idx = data.indexOf(QLatin1Char(':'));
if (idx <= 0 || idx >= data.size() - 1) {
return std::nullopt;
}
MeCard m;
m.d->header = QStringView(data).left(idx);
auto remaining = QStringView(data).mid(idx + 1);
while (remaining.size() > 0) {
const auto keyIdx = remaining.indexOf(QLatin1Char(':'));
if (keyIdx <= 0 || keyIdx + 2 >= remaining.size()) {
break;
}
QString value;
auto elemIdx = keyIdx + 1;
bool inQuote = false;
for (; elemIdx < remaining.size() - 1; ++elemIdx) {
auto c = remaining.at(elemIdx);
if (elemIdx == (keyIdx + 1) && c == QLatin1Char('"')) { // leading quote
inQuote = true;
continue;
}
if (inQuote && c == QLatin1Char('"') && remaining.at(elemIdx + 1) == QLatin1Char(';')) { // trailing quote
break;
}
if (c == QLatin1Char(';')) { // end of element
break;
}
if (c == QLatin1Char('\\')) { // quoted character
++elemIdx;
c = remaining.at(elemIdx);
}
value.push_back(c);
}
const auto key = remaining.left(keyIdx);
auto it = std::lower_bound(m.d->elements.begin(), m.d->elements.end(), key);
if (it == m.d->elements.end()) {
m.d->elements.push_back(MeCardPrivate::Element());
it = std::prev(m.d->elements.end());
} else if ((*it).key != key) {
it = m.d->elements.insert(it, MeCardPrivate::Element());
}
(*it).key = key;
(*it).values.push_back(value);
remaining = remaining.mid(elemIdx + 1);
}
if (m.d->elements.empty()) {
return std::nullopt;
}
return m;
}
QString MeCard::header() const
{
return d->header.toString();
}
QStringView MeCard::headerView() const
{
return d->header;
}
QString MeCard::value(QStringView key) const
{
const auto it = std::lower_bound(d->elements.begin(), d->elements.end(), key);
if (it != d->elements.end() && (*it).key == key && (*it).values.size() == 1) {
return (*it).values.at(0);
}
return {};
}
QStringList MeCard::values(QStringView key) const
{
const auto it = std::lower_bound(d->elements.begin(), d->elements.end(), key);
if (it != d->elements.end() && (*it).key == key) {
return (*it).values;
}
return {};
}
QVariantMap MeCard::toVariantMap() const
{
QVariantMap map;
for (const auto &element : std::as_const(d->elements)) {
if (element.values.size() > 1) {
map.insert(element.key.toString(), element.values);
} else {
map.insert(element.key.toString(), element.values.at(0));
}
}
return map;
}
@@ -0,0 +1,92 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_MECARD_H
#define PRISON_MECARD_H
#include <QString>
#include <QStringList>
#include <QVariantMap>
#include "prison_export.h"
#include <memory>
#include <optional>
namespace Prison
{
/** Parser for the MeCard format.
* This was originally used for a more compact vCard representation, but today
* is mostly relevant for Wifi configuration QR codes.
* @see https://en.wikipedia.org/wiki/MeCard_(QR_code)
* @see https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11
* @since 5.101
*/
class PRISON_EXPORT MeCard
{
public:
/**
* Move constructor
*/
MeCard(MeCard &&other) noexcept;
/**
* Move assignment
*/
MeCard &operator=(MeCard &&other) noexcept;
/**
* Destructor
*/
~MeCard();
/**
* Parse the given string
* @param data The string to parse
* @return A MeCard, if parsing was successful, a nullopt otherwise.
*/
static std::optional<MeCard> parse(const QString &data);
/**
* Get the MeCard header.
*
* If you just want to identify the card,
* use headerView() instead.
*/
QString header() const;
/**
* Get the MeCard header as a string view.
*
* Useful for identifying the type of card.
*/
QStringView headerView() const;
/**
* Get the value for a given key.
*
* Convenience method for getting the first value
* if only one value is expected.
*/
QString value(QStringView key) const;
/**
* Get the list of values for a given key.
* @return The list of values for the given key
*/
QStringList values(QStringView key) const;
/**
* Get the parsed data as QVariantMap.
*/
QVariantMap toVariantMap() const;
private:
explicit MeCard();
friend class MeCardPrivate;
std::unique_ptr<class MeCardPrivate> d;
};
} // namespace Prison
#endif // PRISON_MECARD_H
@@ -0,0 +1,42 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include "pdf417barcode_p.h"
#include "zxingutil_p.h"
#include <ZXing/BitMatrix.h>
#include <ZXing/MultiFormatWriter.h>
#include <stdexcept>
using namespace Prison;
Pdf417Barcode::Pdf417Barcode()
: AbstractBarcodePrivate(Barcode::TwoDimensions)
{
}
QImage Pdf417Barcode::paintImage()
{
try {
ZXing::MultiFormatWriter writer(ZXing::BarcodeFormat::PDF417);
// ISO/IEC 15438:2006(E) §5.8.3 Quiet Zone
writer.setMargin(2);
if (m_data.userType() == QMetaType::QByteArray) {
writer.setEncoding(ZXing::CharacterSet::BINARY);
}
// aspect ratio 4 is hard-coded in ZXing
const auto matrix = writer.encode(ZXingUtil::toStdWString(m_data), 4, 1);
return ZXingUtil::toImage(matrix, m_foreground, m_background);
} catch (const std::invalid_argument &e) {
}; // input too large
return {};
}
QSizeF Pdf417Barcode::preferredSize(qreal devicePixelRatio) const
{
return m_cache.size() * (devicePixelRatio < 2 ? 2 : 1);
}

Some files were not shown because too many files have changed in this diff Show More