A comprehensive MoonBit library for generating standards-compliant PDF documents with support for PDF objects, graphics operators, Markdown to PDF conversion, and binary data parsing.
- PDF Generation: Create complete PDF files with text, images, fonts, and graphics
- Markdown to PDF: Convert Markdown documents to formatted PDF files
- Graphics Operators: Full support for PDF graphics state and drawing operations
- Binary Data Parsing: Efficient parsing of various data types from byte streams
- Standards Compliance: Generate PDFs following ISO 32000-2 (PDF 2.0) specification
///|
test "create basic PDF" {
// Create a simple PDF with text
let canvas = @cmark_pdf.Canvas::new()
canvas.begin_text()
canvas.set_font(b"/F0", 12.0)
canvas.show_text(b"Hello, PDF!")
canvas.end_text()
let ops = canvas.to_array()
inspect(ops.length(), content="4")
}///|
test "markdown to PDF conversion" {
let markdown =
#|# My Document
#|This is a **bold** text and this is *italic*.
#|
#|- First item
#|- Second item
#|
#|```moonbit
#|let x = 42
#|println(x)
#|```
#|
let pdf_bytes = @cmark_pdf.markdown_to_bytes(markdown)
inspect(pdf_bytes.length() > 0, content="true")
// The PDF bytes can be written to a file or used directly
}The core package provides low-level PDF object model and file generation capabilities.
///|
test "PDF objects" {
// Create basic PDF objects
let int_obj = @pdf.Object::Integer(42)
let string_obj = @pdf.Object::String(b"Hello")
// Arrays and dictionaries
let _array_obj = @pdf.Object::Array([int_obj, string_obj])
let dict_obj = @pdf.Object::Dictionary([
(b"/Type", Name(b"/Page")),
(b"/Contents", Integer(1)),
])
json_inspect(dict_obj, content=[
"Dictionary",
[["/Type", ["Name", "/Page"]], ["/Contents", ["Integer", 1]]],
])
}///|
test "graphics operators" {
// Create graphics operators for drawing
let line_width = @pdf.GraphicOperator::Op_w(2.0)
let move_to = @pdf.GraphicOperator::Op_m(100.0, 100.0)
let line_to = @pdf.GraphicOperator::Op_l(200.0, 200.0)
let stroke = @pdf.GraphicOperator::Op_S
let content_stream = @pdf.ContentStream([line_width, move_to, line_to, stroke])
inspect(content_stream.0.length(), content="4")
}///|
test "standard fonts" {
// Use PDF standard fonts
let times_roman = @pdf.StandardFont::TimesRoman
// Convert to PDF objects
let font_dict = times_roman.to_pdf_dictionary()
json_inspect(font_dict, content=[
"Dictionary",
[
["/Type", ["Name", "/Font"]],
["/Subtype", ["Name", "/Type1"]],
["/BaseFont", ["Name", "/Times-Roman"]],
],
])
}High-level API for converting Markdown to formatted PDF documents.
///|
test "document structure" {
let markdown =
#|# Chapter 1
#|## Section 1.1
#|Some text here.
#|
#|## Section 1.2
#|More content.
#|
let cmark_doc = @cmark.Doc::from_string(markdown)
let doc = @cmark_pdf.Doc::from_cmark(cmark_doc)
// Generate table of contents
let flat_headings = doc.to_flatheadings()
inspect(flat_headings.length() > 0, content="true")
}///|
test "canvas API" {
let canvas = @cmark_pdf.Canvas::new()
// Graphics state
canvas.save_state()
canvas.set_line_width(1.5)
canvas.set_rgb_stroke_color(1.0, 0.0, 0.0) // Red
// Draw shapes
canvas.rectangle(50.0, 50.0, 100.0, 100.0)
canvas.stroke()
// Text
canvas.begin_text()
canvas.set_font(b"/Helvetica", 12.0)
canvas.move_text(60.0, 90.0)
canvas.show_text(b"Rectangle")
canvas.end_text()
canvas.restore_state()
let operators = canvas.to_array()
inspect(operators.length() > 0, content="true")
}Efficient parsing of various data types from byte streams.
///|
test "basic parsing" {
// Parse integers
let (num, len) = @lexer_bytes.tokenize_int(b"42")
inspect(num, content="42")
inspect(len, content="2")
// Parse floats
let (pi, len) = @lexer_bytes.tokenize_double(b"3.14159")
json_inspect(pi, content=3.14159)
inspect(len, content="7")
// Parse booleans
let (flag, len) = @lexer_bytes.tokenize_bool(b"true")
json_inspect(flag, content=true)
inspect(len, content="4")
}///|
test "number bases" {
// Hexadecimal
let (hex_val, _) = @lexer_bytes.tokenize_int(b"ff", base=16)
inspect(hex_val, content="255")
// Binary
let (bin_val, _) = @lexer_bytes.tokenize_int(b"1010", base=2)
inspect(bin_val, content="10")
// Octal
let (oct_val, _) = @lexer_bytes.tokenize_int(b"77", base=8)
inspect(oct_val, content="63")
}Low-level formatting utilities used internally for PDF generation.
///|
test "memory formatting" {
let buffer = @fmt.Memory::make(1024, Byte::default())
// Write formatted data
let written = @fmt.format_write(b"{} + {} = {}", [1, 2, 3], buffer, 0)
inspect(written > 0, content="true")
// Extract string
let result = @pdf.string_of_memory(buffer, written)
inspect(result, content="1 + 2 = 3")
}///|
test "custom PDF document" {
// Create content stream with graphics operators
let transform = @pdf.TransformMatrix::{
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
e: 50.0,
f: 750.0,
}
let content = @pdf.ContentStream([
Op_cm(transform),
Op_BT,
Op_Tf(b"/F0", 24.0),
Op_Tj(b"Custom PDF Content"),
Op_ET,
])
// Create PDF structure
let pages = @pdf.define_pages(pages=[Indirect(4)])
let catalog = @pdf.define_catalog(pages=Indirect(2))
// Create complete PDF
let pdf_file = @pdf.make_pdf(catalog, [content.to_stream_object(), pages])
json_inspect(pdf_file.major, content=1)
json_inspect(pdf_file.minor, content=4)
}///|
test "error handling examples" {
// Handle parsing errors
let result = try @lexer_bytes.tokenize_int(b"not_a_number") catch {
error => Err(error)
} noraise {
value => Ok(value)
}
match result {
Ok(_) => inspect("unexpected success", content="unexpected success")
Err(LexError(msg)) => inspect(msg, content="invalid syntax")
}
// Safe parsing with fallback
let safe_parse = @lexer_bytes.tokenize_double(b"invalid") catch {
LexError(_) => (0.0, 0)
}
json_inspect(safe_parse.0, content=0.0)
}///|
test "image handling concept" {
// This demonstrates the API structure for image handling
// (Note: actual image files not available in test environment)
// Define image object structure
let image_dict = @pdf.Object::Dictionary([
(b"/Type", Name(b"/XObject")),
(b"/Subtype", Name(b"/Image")),
(b"/Width", Integer(100)),
(b"/Height", Integer(100)),
(b"/BitsPerComponent", Integer(8)),
(b"/ColorSpace", Name(b"/DeviceRGB")),
])
json_inspect(image_dict, content=[
"Dictionary",
[
["/Type", ["Name", "/XObject"]],
["/Subtype", ["Name", "/Image"]],
["/Width", ["Integer", 100]],
["/Height", ["Integer", 100]],
["/BitsPerComponent", ["Integer", 8]],
["/ColorSpace", ["Name", "/DeviceRGB"]],
],
])
}While the library focuses on PDF generation, it integrates well with file operations:
///|
test "file integration pattern" {
// Generate PDF content
let simple_markdown = "# Test\nSimple document."
let pdf_bytes = @cmark_pdf.markdown_to_bytes(simple_markdown)
// Verify output is generated
inspect(pdf_bytes.length() > 100, content="true") // PDF header + content
// Check PDF signature bytes (%PDF)
inspect(pdf_bytes[0], content="b'\\x25'") // '%'
inspect(pdf_bytes[1], content="b'\\x50'") // 'P'
inspect(pdf_bytes[2], content="b'\\x44'") // 'D'
inspect(pdf_bytes[3], content="b'\\x46'") // 'F'
}The library is organized into several focused packages:
pdf: Core PDF object model, graphics operators, and file structurecmark_pdf: High-level Markdown to PDF conversion using CommonMarklexer_bytes: Binary data parsing and type conversion utilitiesfmt: Low-level formatting and memory management utilities
Each package provides both low-level control and high-level convenience APIs to suit different use cases.
This library generates PDFs following the ISO 32000-2:2020 (PDF 2.0) specification. Key compliance features:
- Proper PDF file structure (header, body, xref table, trailer)
- Standard graphics operators and coordinate systems
- Type 1 font support with standard 14 fonts
- Correct string literal escaping and encoding
- Valid object references and indirect objects
- Uses
BytesViewfor zero-copy parsing operations - Memory-efficient formatting with pre-allocated buffers
- Minimal object allocations in hot paths
- Optimized for both small documents and large batch processing
For deeper understanding of PDF internals, consult the ISO standard: https://www.pdfa-inc.org/product/iso-32000-2-pdf-2-0-bundle-sponsored-access/