Render to HTML, plain text, or PDF
Rendering is the universal SDK operation. Every binding accepts a ChordPro string and returns rendered output in one of three formats: plain text (chords above lyrics), self-contained HTML5, or A4 PDF.
The output is byte-identical across every binding for a given
input — language choice does not change rendering semantics. PRs
that introduce per-binding output drift are caught by the
fix-propagation rule (.claude/rules/fix-propagation.md).
Input
{title: Amazing Grace}
{key: G}
[G]Amazing [G7]grace, how [C]sweet the [G]soundRust
The renderer crates take a &[Song] (parsed by
chordsketch-chordpro) plus an optional transpose offset and a
Config. Use the convenience helpers to skip the hand-wired
config / transpose:
use chordsketch_chordpro::parse;
use chordsketch_render_html::render_song;
use chordsketch_render_text::render_song as render_text;
use chordsketch_render_pdf::render_song as render_pdf;
let song = parse(input)?;
let html: String = render_song(&song);
let text: String = render_text(&song);
let pdf: Vec<u8> = render_pdf(&song);For multi-song input ({new_song} separator) parse with
chordsketch_chordpro::parse_multi(input) and pass the resulting
Vec<Song> to the render_songs family.
API reference: docs.rs/chordsketch-render-html,
-text,
-pdf.
@chordsketch/wasm (browser / Node ESM)
import init, {
render_html,
render_text,
render_pdf,
} from '@chordsketch/wasm';
await init(); // browser only — Node auto-loads via wasm-pack --target nodejs
const html: string = render_html(input);
const text: string = render_text(input);
const pdf: Uint8Array = render_pdf(input);The browser build requires await init() once before the first
call; the Node.js build auto-loads synchronously via require.
See packages/npm/README.md for
the runtime-detection details and the dual-package layout.
@chordsketch/node (Node.js native addon)
import { renderHtml, renderText, renderPdf } from '@chordsketch/node';
const html: string = renderHtml(input);
const text: string = renderText(input);
const pdf: Buffer = renderPdf(input);NAPI exposes the functions as camelCase (napi-rs converts the
Rust snake_case automatically); the WASM package keeps snake_case.
renderPdf returns a Node.js Buffer (vs. Uint8Array for the
WASM build). No init() step — the native addon is loaded
synchronously by require. Prebuilt binaries are shipped for the
five supported platforms (linux-x64-gnu, linux-arm64-gnu,
darwin-x64, darwin-arm64, win32-x64-msvc); no Rust toolchain
required to install.
CLI
The default subcommand reads ChordPro and renders to stdout (or
--output FILE):
# Plain text — chords above lyrics — is the default format
chordsketch song.cho
# HTML
chordsketch song.cho --format html
# PDF
chordsketch song.cho --format pdf --output song.pdf- reads from stdin. --help lists every option.
Python
import chordsketch
html = chordsketch.parse_and_render_html(input, None, None) # config_json, transpose
text = chordsketch.parse_and_render_text(input, None, None)
pdf = chordsketch.parse_and_render_pdf(input, None, None) # bytesThe UniFFI-backed bindings (Python / Swift / Kotlin / Ruby) do
parsing and rendering as a single parse_and_render_* call —
the AST is not exposed as a host-language object graph.
Swift
import ChordSketch
let html = try parseAndRenderHtml(input: input, configJson: nil, transpose: nil)
let text = try parseAndRenderText(input: input, configJson: nil, transpose: nil)
let pdf = try parseAndRenderPdf(input: input, configJson: nil, transpose: nil) // [UInt8]UniFFI converts the snake_case Rust function names to camelCase
Swift names automatically. PDF output is [UInt8] (the
bytes UDL type → Swift's byte sequence, per the Swift package
README).
Kotlin
import uniffi.chordsketch.*
val html: String = parseAndRenderHtml(input, null, null)
val text: String = parseAndRenderText(input, null, null)
val pdf: ByteArray = parseAndRenderPdf(input, null, null)The Maven coordinate is me.koeda:chordsketch but the package
namespace UniFFI generates is uniffi.chordsketch — that's the
import path in source.
Ruby
require 'chordsketch'
html = Chordsketch.parse_and_render_html(input, nil, nil)
text = Chordsketch.parse_and_render_text(input, nil, nil)
pdf = Chordsketch.parse_and_render_pdf(input, nil, nil) # binary stringErrors
| Binding | Error type | Notes |
|---|---|---|
| Rust | ParseError (parse) / renderer infallible after parse |
Carries Span (line + column) |
| WASM / npm | thrown JsError |
Render-path errors carry only .message. Use validate(input) for structured {line, column, message} records. |
| NAPI / Node native | thrown Error |
Same as WASM: render-path is .message only; validate(input) returns ValidationError[] with line, column, message. |
| CLI | non-zero exit + stderr | Currently 1 (ExitCode::FAILURE) on any error path; the binary does not yet differentiate parse vs. config vs. I/O failures via distinct exit codes. |
| Python | chordsketch.ChordSketchError exception |
.message |
| Swift | ChordSketchError enum (.invalidConfig(reason:), .noSongsFound, …) |
.message-equivalent on the variant payload |
| Kotlin | ChordSketchException |
.message |
| Ruby | Chordsketch::ChordSketchError |
.message |
To validate input without rendering, use the validate(input)
function exposed by the WASM, NAPI, and UniFFI bindings (returns
an array of {line, column, message} records).
Next step
Transpose chords by N semitones — same input, shifted by a configurable number of semitones.