From f9e64ba41e7c88c5ab3db25195a8eca6ce02a73d Mon Sep 17 00:00:00 2001 From: Ye Wang Date: Sun, 30 Nov 2025 04:08:51 -0500 Subject: [PATCH] Added porting documentation for code review --- csharp/PORTING.md | 173 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 csharp/PORTING.md diff --git a/csharp/PORTING.md b/csharp/PORTING.md new file mode 100644 index 0000000..4841c66 --- /dev/null +++ b/csharp/PORTING.md @@ -0,0 +1,173 @@ +### Porting Summary: Java → C# QR Code Generator + +#### Scope +- Ported the core QR Code generator from the Java implementation (`java/src/main/java/io/nayuki/qrcodegen/`) to C#. +- Implemented a class library `QrCodeGen` and a minimal console demo `QrCodeGen.Demo`. + +#### Goals +- Maintain functional parity with the Java reference (encoding pipeline, EC levels, masking, penalties, Reed–Solomon ECC, layout drawing). +- Provide idiomatic and safe C# while preserving algorithmic behavior and outputs. + +#### Note +- This PR is 100% generated by JetBrains Junie Pro AI code assistant. +- QR Code Generate Demo console app is a minimal port of the Java demo and working. +--- + +### Repository Layout (Relevant to this PR) +- `csharp/QR-Code-generator.sln` — Solution file containing two projects. +- `csharp/QrCodeGen/` — Class library (C# port of the Java library): + - `BitBuffer.cs` + - `DataTooLongException.cs` + - `QrSegment.cs` + - `QrSegmentAdvanced.cs` (simplified vs. Java; see Feature Parity section) + - `QrCode.cs` +- `csharp/QrCodeGen.Demo/` — Console demo app (depends on `QrCodeGen`). + +--- + +### Class-by-Class Mapping +- Java `BitBuffer` → C# `BitBuffer` + - API preserved: append bits, append buffer, length, get bit, cloning. + - Implementation uses `List` and explicit bounds checks like Java’s `BitSet`/length pair. + +- Java `DataTooLongException` → C# `DataTooLongException` + - Extends `ArgumentException` (close analog to Java’s `IllegalArgumentException`). + +- Java `QrSegment` → C# `QrSegment` + - Static factories: `MakeBytes`, `MakeNumeric`, `MakeAlphanumeric`, `MakeEci`. + - Helpers: `IsNumeric`, `IsAlphanumeric` via regexes mirroring Java patterns. + - Mode enum and character-count bits mapping provided via extension methods. + - `GetTotalBits()` faithfully ported. + +- Java `QrSegmentAdvanced` → C# `QrSegmentAdvanced` + - Included as an optional module. Current implementation mirrors the default segmentation path (numeric/alphanumeric/byte) and returns reasonably optimal segments for common inputs, but does not implement full DP-based optimal segmentation and Kanji mode. See Feature Parity and Future Work. + +- Java `QrCode` → C# `QrCode` + - High-level APIs: `EncodeText`, `EncodeBinary`. + - Mid-level API: `EncodeSegments` with `minVersion`, `maxVersion`, `mask`, and `boostEcl` parameters. + - Constructor path builds function patterns, computes ECC, interleaves, draws codewords, applies mask, and computes penalties identically. + - Tables `ECC_CODEWORDS_PER_BLOCK` and `NUM_ERROR_CORRECTION_BLOCKS` ported exactly. Signed `sbyte` used to allow `-1` sentinels. + - All penalty and mask rules implemented verbatim. + +--- + +### Feature Parity +- Achieved parity: + - Version selection by capacity. + - Error correction level boosting (when it doesn’t increase version). + - Data stream building, terminator and padding, interleaving across ECC blocks. + - Drawing: finder, timing, alignment patterns; format and version information. + - Mask application and penalty scoring (N1–N4 rules) with identical logic. + - Reed–Solomon divisor/remainder algorithms and multiplication over GF(2^8/0x11D). + +- Intentional differences / limitations: + - `QrSegmentAdvanced` does not implement: + - Full dynamic-programming optimal segmentation across all modes. + - Kanji segment encoding. + - The default path still matches Java’s `QrSegment.makeSegments()` selection (numeric/alphanumeric/byte) and therefore preserves functional outputs for typical text. + +- Behavioral equivalence expectations: + - For inputs limited to numeric/alphanumeric/byte, outputs should match Java for the same chosen mask (or mask-agnostic when mask=-1) and ECL. + +--- + +### Notable C#-Specific Decisions +- `sbyte[][]` tables for ECC metadata to accommodate `-1` sentinel (Java byte is signed; C# byte is unsigned). +- Enum helpers via extension methods for mapping to mode bits and character-count bits. +- `BitBuffer` cloning implemented to maintain immutability of exposed data as in Java. +- Exceptions aligned to .NET conventions (e.g., `ArgumentException`, `ArgumentOutOfRangeException`). + +--- + +### Build and Run Instructions +- Target frameworks: `net8.0` (both projects). If your machine only has .NET 9, either install .NET 8 or retarget both projects to `net9.0`. + +Commands from repo root: +```sh +dotnet restore csharp/QR-Code-generator.sln +dotnet build csharp/QR-Code-generator.sln -c Debug +# Run demo +dotnet run --project csharp/QrCodeGen.Demo +``` + +Rider/VS: +- Open `csharp/QR-Code-generator.sln` and build. Ensure your IDE uses the Microsoft .NET SDK (not Homebrew’s) and that the installed SDK matches `net8.0` or update TFM to `net9.0`. + +--- + +### Verification Performed +- Build succeeded locally after addressing initial compile issues (sbyte tables, enum constructor, finder pattern drawing, bit-buffer bounds). +- Demo run prints QR metadata and ASCII rendering for “Hello, world!”. +- Corrected a runtime bug where data bytes were read beyond `BitBuffer` length; now we read `numDataBytes = bb.BitLength / 8` and then apply 0xEC/0x11 padding. + +--- + +### Reviewer Checklist +- API Parity: + - QrCode: factory methods, mid-level encode, mask selection, ECC boosting. + - QrSegment: mode mapping, regex correctness, bit-length calculations. + - BitBuffer: bounds checks, append semantics, cloning. +- Tables: Verify ECC and block count tables match Java constants exactly. +- Masking/Penalties: Confirm penalty `N1..N4` and zigzag codeword placement logic mirror Java. +- Error Paths: Proper exceptions for out-of-range versions/masks/lengths. +- Padding: Verify 0xEC/0x11 alternating pad applied after real bytes from `BitBuffer`. +- Public API surface: Namespaces and visibility appropriate for a library; no leaking internals. + +--- + +### Known Gaps / Future Work +- Implement full `QrSegmentAdvanced`: + - DP-based optimal segmentation across modes. + - Kanji mode encoding per the Java version. +- Add unit tests: + - Cross-verify bitwise outputs vs. Java for a fixed mask across sample inputs. + - Randomized round-trips using known test vectors. +- Add simple image renderer (PNG/SVG) for easier visual verification. +- Performance: Consider bit-packing in `BitBuffer` to reduce allocations for very large payloads. + +--- + +### Migration Risks and Mitigations +- SDK Mismatch (net8.0 vs installed SDK): Documented; either install .NET 8 or retarget to `net9.0` in both `.csproj` files. +- Environment: Ensure the IDE uses Microsoft’s SDK at `/usr/local/share/dotnet`; avoid Homebrew’s SDK confusion. +- Behavior Drift: The simplified `QrSegmentAdvanced` may produce larger bitstreams for mixed-content strings vs. Java’s optimal splitter. This does not affect correctness, only compactness. + +--- + +### File List (Added/Modified) +- Added: + - `csharp/QR-Code-generator.sln` + - `csharp/QrCodeGen/QrCodeGen.csproj` + - `csharp/QrCodeGen/BitBuffer.cs` + - `csharp/QrCodeGen/DataTooLongException.cs` + - `csharp/QrCodeGen/QrSegment.cs` + - `csharp/QrCodeGen/QrSegmentAdvanced.cs` + - `csharp/QrCodeGen/QrCode.cs` + - `csharp/QrCodeGen.Demo/QrCodeGen.Demo.csproj` + - `csharp/QrCodeGen.Demo/Program.cs` + +--- + +### How Reviewers Can Reproduce +- Build and run demo as above. +- Optionally compare module patterns vs. Java output with a fixed mask (pass `mask` in `EncodeSegments`) over several sample strings. + +--- + +### License Alignment +- The C# port continues to follow the MIT License consistent with the Java version and includes the original copyright attribution. + +--- + +### Appendix: Usage Example +```csharp +using Io.Nayuki.QrCodeGen; + +var qr = QrCode.EncodeText("Hello, world!", QrCode.Ecc.MEDIUM); +for (int y = 0; y < qr.Size; y++) { + for (int x = 0; x < qr.Size; x++) { + bool dark = qr.GetModule(x, y); + // render module + } +} +```