Skip to content

Conversation

@ye
Copy link

@ye ye commented Nov 30, 2025

(copied from ./csharp/PORTING.md)

Example demo run:
IMG_0540L

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:

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

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
    }
}

@ye ye changed the title Csharp port C# port Nov 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant