You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+60-2Lines changed: 60 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,19 @@
2
2
3
3
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
4
5
+
## Purpose
6
+
7
+
QrCodeGenerator is library to generate QR code. It is designed to be easy to use and performant.
8
+
9
+
The library only supports limited graphics types and options in order to work without
10
+
graphics libraries, which might not run all platforms.
11
+
The library can provide the QR code as a two-dimensional array of pixels (called modules by the QR code standard).
12
+
It is then up to the application to display the QR code based on this information.
13
+
Many demo projects show how to use this approach for different graphics libraries and UI frameworks.
14
+
15
+
The main target of the library is .NET Standard 2.0 so it runs in virtually any current .NET environment.
16
+
17
+
5
18
## Commands
6
19
7
20
```bash
@@ -22,12 +35,57 @@ dotnet test --filter "FullyQualifiedName=Net.Codecrete.QrCodeGenerator.Test.QrCo
22
35
dotnet pack --no-build
23
36
```
24
37
38
+
## Build targets
39
+
40
+
-**`QrCodeGenerator/`** (the library) targets `netstandard2.0;net6.0`. The `net6.0` target exists only to enable trimming (`IsTrimmable`). Keep public API and language usage compatible with netstandard2.0 — don't reach for newer BCL/`Span` APIs that aren't available there.
41
+
-**`QrCodeGeneratorTest/`** targets `net8.0;net10.0`, plus `net481` on Windows. `dotnet test` runs every target framework.
-**`QrCodeAnalyzer/`** is a separate WPF tool in its own solution (`QrCodeAnalyzer/QrCodeAnalyzer.sln`), not part of `QrCodeGenerator.sln`.
44
+
45
+
Version 3 is a complete rewrite (≈10x faster, more standard-compliant) of what began as a port of Project Nayuki's Java library. The previous public type `QrSegment` is now `DataSegment`.
46
+
25
47
## Architecture
26
48
27
-
The library (`QrCodeGenerator/`) targets `netstandard2.0`. Additionally, there is a `net6.0` target to enable trimming. The test project (`QrCodeGeneratorTest/`) and the profiling project (`QrCodeGeneratorProfiling/`) target `net10.0`.
49
+
`QrCode` is the only substantial public surface: immutable factory methods (`EncodeText`, `EncodeTextAdvanced`, `EncodeBinary`, `EncodeSegments`, `EncodeTextInMultipleCodes`) plus rendering (`ToSvgString`, `ToGraphicsPath`, `ToPngBitmap`, `ToBmpBitmap`, `GetModule`). It holds a single `BitMatrix` of modules. Almost all real work lives in `internal` types.
50
+
51
+
### Encoding pipeline
52
+
53
+
Text/bytes → segments → codewords → matrix, in this order:
54
+
55
+
1.**`DataSegment.FromText` / `FromBinaryData`** — chooses the text encoding. For automatic ECI: tries Latin-1 (ISO-8859-1) and adds no ECI; falls back to UTF-8 with an ECI designator if Latin-1 is lossy. Segments retain the *original unencoded bytes* as an `ArraySegment` over the caller's array until the bit stream is built — **the source array must not be mutated** in the meantime.
56
+
2.**`SegmentCompaction`** — picks the cheapest per-byte mode (numeric > alphanumeric > Kanji > binary), groups consecutive bytes into blocks, then greedily *merges* adjacent blocks when the mode-switch overhead outweighs the savings. Merge cost depends on `version` (count-indicator width changes at versions 1/10/27). This is what produces the "smallest possible QR code" claim.
57
+
3.**`QrCodeBuilder.Build`** (the heart of the library) does the rest:
58
+
-`FindVersionAndEcc` — smallest version that fits, then boost ECC level for free if it doesn't grow the version.
-`AddErrorCorrection` — splits into blocks, computes Reed-Solomon ECC (`ReedSolomon`), interleaves data and ECC codewords per spec.
61
+
- Matrix layout — `CreateWithFixedPatterns` (finder/timing/alignment/format/version) then `FillPayload` zig-zags the codewords into the free modules.
62
+
-`ApplyBestPattern` — XORs each of the 8 mask patterns, scores it (`Penalty`), keeps the lowest. `EncodingInfo.ForcedDataMask` can override the choice.
63
+
64
+
### `BitMatrix` and the transpose trick
65
+
66
+
`BitMatrix` (`internal readonly struct`) is the central data structure: a square bit grid stored as 4 `ulong`s per row (256-bit rows regardless of size), in row-major order. It exposes fast whole-matrix `And`/`Xor`/`Invert`/`PopCount` and an in-place **64×64 block transpose**.
67
+
68
+
The transpose is load-bearing, not a convenience: every penalty/format rule that operates on *columns* is implemented by transposing the matrix and reusing the *row*-wise algorithm. `ApplyBestPattern` keeps a `modules` and a `transposed` copy in lock-step, and `Penalty` takes both. `Penalty` itself is bit-parallel (operates on whole `ulong` words, not per-module) and has an early-stop path that bails once the running score exceeds the best-so-far.
69
+
70
+
### Performance-tuned constants
71
+
72
+
Two orderings in `QrCodeBuilder`/`Penalty` are deliberately ordered by profiling data (see `QrCodeGeneratorProfiling/README.md`), not arbitrary: `PatternEvaluationOrder` (evaluate likely-best masks first to tighten the early-stop bound) and the rule order inside `Penalty.CalculatePenalty`. Reordering them changes performance, not output. `QrCodeBuilder` also caches fixed patterns and data masks per version in `ConcurrentDictionary` instances; cached `BitMatrix` instances are shared and must not be mutated (callers `Copy()` first).
73
+
74
+
Everything is keyed by `version` (1–40) and `ecc` (0–3 = L/M/Q/H). The large `static readonly` lookup tables in `QrCodeBuilder` (codeword capacity, block counts, alignment positions, format/version info bits) come straight from ISO/IEC 18004 tables — comments cite the table numbers.
75
+
76
+
### Rendering and diagnostics
77
+
78
+
-`Graphics` (SVG/XAML path, BMP) and `PngBuilder` (PNG) take the finished modules; `QrCode` delegates to them. The SVG path merges adjacent dark modules into the largest rectangles to shrink output.
79
+
-`StructuredAppend` splits long text across up to 16 linked QR codes (used by `EncodeTextInMultipleCodes`).
80
+
-`EncodingInfo` / `PenaltyScore` are opt-in diagnostics: pass an `EncodingInfo` to capture per-mask penalty breakdowns and the chosen segments. This forces *full* penalty evaluation (disables early-stop), so it is slower — it exists for the `QrCodeAnalyzer` tool, not normal use.
28
81
29
82
## Tests
30
83
31
84
Uses **xUnit v3** with **Verify.XunitV3** for snapshot/approval testing and **Xunit.Combinatorial** for parameterized matrices.
32
85
33
-
Snapshot files live alongside the test source in `QrCodeGeneratorTest/`. When a snapshot changes intentionally, run `dotnet test` once — Verify will fail and write the new `.verified.*` file; accept it by deleting the old one or using the Verify tooling.
86
+
The test strategy is characterization + cross-validation, not just unit tests:
87
+
88
+
-**`QrCodeDataProvider.cs`** is a large generated `[ClassData]` source feeding `QrCodeTest`. `TestQrCode` asserts the exact module layout, version, ECC, and mask for each case (golden tests).
89
+
-**`VerifyWithZXing`** decodes every generated QR code with **ZXing.Net** and asserts the text round-trips with *zero* errors corrected. `CorruptedModule_IsCorrected_AndReportsErrorsCorrected` is a negative control that flips one module to prove that assertion has teeth. (Some ECI+Kanji combinations are skipped due to a ZXing 0.16.x decode bug — the QR is still valid.)
90
+
-**`ReedSolomonTest`** cross-checks ECC against the **STH1123.ReedSolomon** package.
91
+
-**Verify** snapshot tests cover rendered output (SVG/PNG/BMP). Snapshot `.verified.*` files live alongside the test source in `QrCodeGeneratorTest/`. When a snapshot changes intentionally, run `dotnet test` once — Verify fails and writes the new `.verified.*` file; accept it by replacing the old one (or via the Verify tooling).
0 commit comments