Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
481 changes: 481 additions & 0 deletions .gitignore

Large diffs are not rendered by default.

173 changes: 173 additions & 0 deletions csharp/PORTING.md
Original file line number Diff line number Diff line change
@@ -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<bool>` 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
}
}
```
24 changes: 24 additions & 0 deletions csharp/QR-Code-generator.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QrCodeGen", "QrCodeGen/QrCodeGen.csproj", "{6A9E9E3E-2B2E-4B60-9D2B-6F5F09E3A1B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QrCodeGen.Demo", "QrCodeGen.Demo/QrCodeGen.Demo.csproj", "{F7C6C6A2-3B2E-4E1B-9D7B-0F5C9C1E2F23}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6A9E9E3E-2B2E-4B60-9D2B-6F5F09E3A1B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6A9E9E3E-2B2E-4B60-9D2B-6F5F09E3A1B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6A9E9E3E-2B2E-4B60-9D2B-6F5F09E3A1B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6A9E9E3E-2B2E-4B60-9D2B-6F5F09E3A1B1}.Release|Any CPU.Build.0 = Release|Any CPU
{F7C6C6A2-3B2E-4E1B-9D7B-0F5C9C1E2F23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7C6C6A2-3B2E-4E1B-9D7B-0F5C9C1E2F23}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7C6C6A2-3B2E-4E1B-9D7B-0F5C9C1E2F23}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7C6C6A2-3B2E-4E1B-9D7B-0F5C9C1E2F23}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
21 changes: 21 additions & 0 deletions csharp/QrCodeGen.Demo/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Text;
using Io.Nayuki.QrCodeGen;

internal class Program {
private static void Main() {
// Simple demo: encode text and print size
var qr = QrCode.EncodeText("Hello, world!", QrCode.Ecc.MEDIUM);
Console.WriteLine($"QR size: {qr.Size}x{qr.Size}, mask={qr.Mask}, ecc={qr.ErrorCorrectionLevel}");

// Render to ASCII
int border = 2;
for (int y = -border; y < qr.Size + border; y++) {
for (int x = -border; x < qr.Size + border; x++) {
bool m = (x >= 0 && x < qr.Size && y >= 0 && y < qr.Size) && qr.GetModule(x, y);
Console.Write(m ? "██" : " ");
}
Console.WriteLine();
}
}
}
11 changes: 11 additions & 0 deletions csharp/QrCodeGen.Demo/QrCodeGen.Demo.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../QrCodeGen/QrCodeGen.csproj" />
</ItemGroup>
</Project>
69 changes: 69 additions & 0 deletions csharp/QrCodeGen/BitBuffer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* QR Code generator library (C# port)
*
* Ported from Java version in this repository.
* Copyright (c) Project Nayuki. (MIT License)
* https://www.nayuki.io/page/qr-code-generator-library
*/
using System;
using System.Collections.Generic;

namespace Io.Nayuki.QrCodeGen;

/// <summary>
/// An appendable sequence of bits (0s and 1s). Mainly used by <see cref="QrSegment"/>.
/// </summary>
public sealed class BitBuffer : ICloneable {
private readonly List<bool> data;
private int length;

/// <summary>Constructs an empty bit buffer (length 0).</summary>
public BitBuffer() {
data = new List<bool>(64);
length = 0;
}

/// <summary>Returns the length of this sequence, which is a non-negative value.</summary>
public int BitLength => length;

/// <summary>Returns the bit at the specified index, yielding 0 or 1.</summary>
public int GetBit(int index) {
if (index < 0 || index >= length)
throw new ArgumentOutOfRangeException(nameof(index));
return data[index] ? 1 : 0;
}

/// <summary>
/// Appends the specified number of low-order bits of the specified value to this buffer.
/// Requires 0 ≤ len ≤ 31 and 0 ≤ val &lt; 2^len.
/// </summary>
public void AppendBits(int val, int len) {
if (len < 0 || len > 31 || (val >> len) != 0)
throw new ArgumentException("Value out of range");
if (int.MaxValue - length < len)
throw new InvalidOperationException("Maximum length reached");
for (int i = len - 1; i >= 0; i--) { // Append bit by bit, from high to low
bool bit = QrCode.GetBit(val, i);
data.Add(bit);
length++;
}
}

/// <summary>Appends the content of the specified bit buffer to this buffer.</summary>
public void AppendData(BitBuffer bb) {
if (bb == null) throw new ArgumentNullException(nameof(bb));
if (int.MaxValue - length < bb.length)
throw new InvalidOperationException("Maximum length reached");
for (int i = 0; i < bb.length; i++) {
data.Add(bb.data[i]);
length++;
}
}

public object Clone() {
var copy = new BitBuffer();
copy.data.AddRange(this.data);
copy.length = this.length;
return copy;
}
}
18 changes: 18 additions & 0 deletions csharp/QrCodeGen/DataTooLongException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* QR Code generator library (C# port)
*
* Ported from Java version in this repository.
* Copyright (c) Project Nayuki. (MIT License)
* https://www.nayuki.io/page/qr-code-generator-library
*/
using System;

namespace Io.Nayuki.QrCodeGen;

/// <summary>
/// Thrown when the supplied data does not fit any QR Code version.
/// </summary>
public class DataTooLongException : ArgumentException {
public DataTooLongException() { }
public DataTooLongException(string? message) : base(message) { }
}
Loading