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.
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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"
|
||||
"""
|
||||
|
||||
@@ -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"
|
||||
"""
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)); }
|
||||
}
|
||||
|
||||
@@ -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].")
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 230 B |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 246 B |
|
After Width: | Height: | Size: 315 B |
|
After Width: | Height: | Size: 369 B |
|
After Width: | Height: | Size: 434 B |
|
After Width: | Height: | Size: 160 B |
|
After Width: | Height: | Size: 219 B |
|
After Width: | Height: | Size: 308 B |
|
After Width: | Height: | Size: 205 B |
|
After Width: | Height: | Size: 141 B |
|
After Width: | Height: | Size: 190 B |
|
After Width: | Height: | Size: 527 B |
|
After Width: | Height: | Size: 528 B |
|
After Width: | Height: | Size: 527 B |
|
After Width: | Height: | Size: 582 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 580 B |
|
After Width: | Height: | Size: 767 B |
|
After Width: | Height: | Size: 526 B |
|
After Width: | Height: | Size: 537 B |
|
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"
|
||||
|
After Width: | Height: | Size: 155 B |
|
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"
|
||||
|
After Width: | Height: | Size: 488 B |
|
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"
|
||||
|
After Width: | Height: | Size: 229 B |
|
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);
|
||||
}
|
||||