Skip to content

Latest commit

 

History

History
240 lines (194 loc) · 11 KB

File metadata and controls

240 lines (194 loc) · 11 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Goal

Public Go SDK for generating Ncode-overlaid PDFs that the NeoLAB Neo smartpen can read, on Linux, macOS, and Windows. Community complement to the official NeoSmartpen/Ncode-SDK2.0.

The two contributions over upstream are load-bearing for the project's identity:

  1. Linux / macOS support via Mono. NeoLAB's NeoLABNcodeSDK.dll ships only as a Windows-targeted .NET Framework 4.5 binary, and its GetImage() / GetPostscript() paths concatenate `workingFolder + "\"

    • filenamewith a literal backslash that breaks under Mono. The C# wrapper intools/ncode-cli patches around this by passing only the basename to the SDK call and then relocating the produced file. The Go side (pattern/neolab) prefixes the wrapper with monoon non-Windows hosts. **Do not** "simplify" either branch without re-verifying on the Linux side — seedocs/linux-support.mdandtools/ncode-cli/MONO_PATH_WORKAROUND.md` for the disassembly evidence.
  2. 3-pixel triangle and 4-pixel custom dilation kernels for the dot ink footprint. The official SDK only exposes isBold ∈ {false, true}; pattern/kernels adds Tri3Up, Tri3Down, Tri4Up, Tri4Down, Diamond4, plus the ApplyKernel / ApplyAlternating / ApplySquare / ApplyDisc / ReduceClusterTopLefts transforms that shape the bitmap before it is rendered into a PDF. Every kernel is centroid-anchored on the original SDK pixel position so the pen IR camera reads the same Ncode coordinate regardless of kernel choice (see docs/kernels.md).

If a contribution to this repo doesn't move one of those two needles or the supporting Go API, ask before adding it — the SDK is intentionally narrow.

Architecture

ncode/                  Pure domain types: NcodeType, Ticket, PageID,
                        Page, PaperSize, DotMode. No I/O, no SDK calls.
                        Mirrors the public surface of NcodeSDK 2.0.

pattern/                Generator interface + 1-bpp Bitmap (0 = dot, 1 =
                        background, MSB-first byte packing).
  ├── neolab/           Production Generator. Spawns ncode-cli once per
  │                     page; prefixes "mono" on non-Windows; decodes the
  │                     produced PNG into a Bitmap.
  ├── stub/             Deterministic non-pen-readable Generator
  │                     used by the SDK's own test suite (and exported
  │                     so downstream test suites can use it too) to
  │                     exercise the PDF pipeline without NeoLAB
  │                     credentials. Logs slog.Warn on every Generate.
  │                     NEVER call from an example or production path.
  └── kernels/          Tri3 / Tri4 / Diamond4 kernels and dilation
                        transforms. All consume a pattern.Bitmap and
                        return a new pattern.Bitmap.

pdf/                    K-removal compositor + two-layer PDF assembly.
                        Publish() ties everything together via
                        go-fitz (MuPDF) for source rendering, pdfcpu
                        for assembly, and hhrutter/tiff for the
                        lossless artwork layer.

tools/ncode-cli/        C# console (.NET Framework 4.5) wrapping
                        NeoLABNcodeSDK.dll. Builds on Windows
                        (msbuild) and on Linux/macOS (mono-csc). Holds
                        the path-handling workaround.

examples/publish/       Minimal end-to-end Go program: env-var
                        configured, drives Publish() with the
                        production Tri3 alternating kernel.

docs/                   linux-support.md (Mono workaround), kernels.md
                        (kernel rationale + density measurements),
                        pipeline.md (two-layer PDF structure).

Two-layer PDF structure (don't flatten this)

pdf.Publish deliberately keeps artwork and dots in separate PDF objects:

  • A DeviceCMYK artwork image with K forced to 0 everywhere ((C, M, Y, 0) = (255-R, 255-G, 255-B, 0)).
  • A 1-bpp PDF ImageMask drawn in pure K (0 0 0 1 k) on top.

Pure K from the mask is the only K ink on the page. Flattening the dots into the artwork image lets printer colour management dilute them — that's the failure mode the two-layer split exists to prevent. If you ever feel tempted to merge them for "simplicity", read docs/pipeline.md first.

The pixel-composition rule is verbatim from cpp/sampleApp(mupdf)/main.cpp:148-176 of the upstream SDK; deviating from it produces PDFs the pen cannot decode.

Kernel anchor convention

Every dilation kernel in pattern/kernels keeps its footprint within ±1 pixel of the original SDK pixel — well inside the Anoto cell pitch (~9 px at 600 DPI) — so the pen camera reads the same Ncode coordinate regardless of kernel choice. The Tri3Up/Tri3Down alternating pair and Diamond4 (alone) additionally satisfy exact centroid invariance (average pixel position equals the original SDK pixel); the Tri4 pair does not (its alternating centroid is offset by 0.5 px in y). TestKernelFootprintWithinOneCell and TestExactCentroidInvariance in pattern/kernels/kernels_test.go pin both properties — new kernels added later must keep them passing.

Build & dev commands

go build ./...                                # build all Go packages
go vet ./...                                  # vet gate
go test ./...                                 # full test suite (no NeoLAB creds needed)
go test ./pattern/kernels -run TestApply -v   # subset by name
go test ./pdf/ -run TestPublishStub -v        # integration test against stub generator
gofmt -l . | tee /dev/stderr | (! read)       # format gate

# C# wrapper (run from tools/ncode-cli, requires NeoLABNcodeSDK.dll)
msbuild NcodeCli.csproj /p:Configuration=Release /p:Platform=x64        # Windows
mono-csc -r:NeoLABNcodeSDK.dll -target:exe -out:ncode-cli.exe Program.cs # Linux/macOS

The full go test ./... suite is self-contained — it uses pattern/stub as a Generator and pdfcpu to synthesise sample input PDFs at runtime, so no NeoLAB credentials, no DLL, and no Mono runtime are required. The pdf/ integration tests run the full Publish pipeline at 600 DPI; total wall time on a developer laptop is ~50 s.

The Go side requires CGo (go-fitz statically links MuPDF). On a fresh Linux box, install build-essential and libgl1-mesa-dev before the first go build.

go-fitz and pdfcpu are heavy dependencies; do not add or remove them without checking the impact on the cross-compile matrix the SDK targets (linux/amd64, linux/arm64, darwin/arm64, windows/amd64).

Running the examples end-to-end

Both examples are production-only (NeoLAB-required) by design. There is no example-level offline fallback — offline coverage lives in the test suite (go test ./... with stub generator). Two distinct production flows:

examples/discover-tickets — enumerate which (section, owner, book, page) ranges the app key is provisioned for. This shells out to ncode-cli tickets, parses the JSON, and prints each grant + the first PageID it resolves to. Run this before the publish example so you know what to put in NCODE_OWNER / NCODE_BOOK / NCODE_PAGE_*.

export NCODE_APPKEY=...
export NCODE_CLI_EXE=/opt/ncode-cli/ncode-cli.exe
go run ./examples/discover-tickets

examples/publish — full publishing pipeline. Pen-readable output. Requires the proprietary NeoLABNcodeSDK.dll (obtained through NeoLAB's technical agreement program), a built ncode-cli.exe, and a NeoLAB application key.

export NCODE_APPKEY=...
export NCODE_CLI_EXE=/opt/ncode-cli/ncode-cli.exe
export NCODE_INPUT_PDF=./input.pdf
export NCODE_OUTPUT_PDF=./output.pdf
export NCODE_TYPE=N3C6
export NCODE_SECTION=3
export NCODE_OWNER=100         # from discover-tickets output
export NCODE_BOOK=0            # from discover-tickets output
export NCODE_PAGE_START=0      # from discover-tickets output
export NCODE_PAGE_SIZE=64      # from discover-tickets output

go run ./examples/publish

The proprietary DLL is not committed and cannot be redistributed under NeoLAB's licensing terms — see docs/linux-support.md.

Running the test suite without NeoLAB credentials

go test ./... runs end-to-end without any NeoLAB asset, by swapping pattern.Generator to pattern/stub and synthesising a sample input PDF at runtime via pdfcpu. This is the canonical offline development loop. The stub is exported so downstream codebases can use the same trick when writing tests around their own SDK consumers.

Do not introduce stub usage into examples or production code. The package doc explicitly forbids it; slog.Warn is emitted on every Generate call as a tripwire if it ever happens.

What this SDK deliberately does not include

This SDK is intentionally narrow. The following kinds of features were considered and left out — do not add them without explicit user direction:

  • Ticket persistence: ncode.Ticket is a plain value the caller constructs. Applications choose their own storage (SQLite, KV, config file, etc.); the SDK does not impose one.
  • Application UIs / desktop shells: out of scope. SDK consumers wire their own.
  • ICC OutputIntent / PDF/X embedding: trivial to add downstream of Publish with any PDF library; not worth the dependency surface here.
  • Vector glyph dot mode (Type 3 font): the raster ImageMask path with the Tri3 kernel matches it on pen recognition in our tests while being significantly simpler to ship and audit.
  • Upload / sync clients to specific note-management backends: those belong in their own repos against their own APIs.

If a downstream user asks "where is X?", first check whether X is in this list before assuming it was an oversight.

Working conventions

  • Public API stability matters here — this is a published SDK, not an app. Treat exported names in ncode, pattern, pattern/kernels, pattern/neolab, and pdf as load-bearing. Removing or renaming an exported symbol is a breaking change; surface it explicitly.
  • Cross-platform parity matters: every Go change must build on linux/amd64, linux/arm64, darwin/arm64, and windows/amd64. The C# wrapper must build on both Windows (msbuild) and Linux (mono-csc).
  • Kernel additions must come with: a comment block showing the dot layout, a centroid calculation, and an examples/-level demonstration of how to invoke them via pdf.PublishRequest.DilationKernel.
  • The Apache 2.0 LICENSE in this repo covers only the source code. The Anoto pattern algorithm, NeoLAB SDK binary, and NeoSmartpen trademarks are not relicensed by this repo. Any documentation that could be read as relicensing them must be edited.

Environment notes

  • User's global policy (/home/dev/.claude/CLAUDE.md) prohibits invoking psql directly. Not relevant to this repo today (no database) but applies if one is ever added.