README

#pdfio

Low-level I/O primitives for reading and writing PDF byte streams.

#Overview

The pdfio package provides the fundamental I/O abstractions used throughout the PDF library. It defines:

  • MutableBytes: The primary byte buffer type
  • Input: Seekable input stream abstraction
  • Output: Seekable output stream abstraction
  • Bitstream: MSB-first bit-level reading and writing

#Byte Buffer Types

///|
pub type MutableBytes = Array[Byte]

///|
pub type CoreBytes = Bytes

///|
pub type RawBytes = MutableBytes

#Creating Buffers

///|
test "mkbytes creates zero-filled buffer" {
let bytes = @pdfio.mkbytes(10)
inspect(bytes.length(), content="10")
inspect(@pdfio.bget(bytes, 0), content="0")
}

#Byte Access

///|
test "bget and bset" {
let bytes = @pdfio.mkbytes(4)
@pdfio.bset(bytes, 0, 65)
@pdfio.bset(bytes, 1, 66)
inspect(@pdfio.bget(bytes, 0), content="65")
inspect(@pdfio.bget(bytes, 1), content="66")
}

#Conversions

///|
test "bytes_of_string and string_of_bytes" {
let bytes = @pdfio.bytes_of_string("ABC")
inspect(@pdfio.bytes_size(bytes), content="3")
let s = @pdfio.string_of_bytes(bytes)
inspect(s, content="ABC")
}

///|
test "bytes_of_list" {
let bytes = @pdfio.bytes_of_list([65, 66, 67])
inspect(@pdfio.string_of_bytes(bytes), content="ABC")
}

///|
test "int_array_of_bytes" {
let bytes = @pdfio.bytes_of_string("Hi")
let ints = @pdfio.int_array_of_bytes(bytes)
inspect(ints, content="[72, 105]")
}

#Copying

///|
test "copybytes creates independent copy" {
let original = @pdfio.bytes_of_string("test")
let copy = @pdfio.copybytes(original)
@pdfio.bset(copy, 0, 88)
inspect(@pdfio.bget(original, 0), content="116") // unchanged
inspect(@pdfio.bget(copy, 0), content="88")
}

#Input Streams

The Input struct provides a seekable byte stream:

///|
pub struct Input {
pos_in : () -> Int // Current position
seek_in : (Int) -> Unit // Seek to position
input_char : () -> Char? // Read next char
input_byte : () -> Int // Read next byte
in_channel_length : Int // Total length
set_offset : (Int) -> Unit // Set base offset
source : String // Source description
}

#Creating Input from Bytes

///|
test "input_of_bytes" {
let bytes = @pdfio.bytes_of_string("Hello")
let input = @pdfio.Input::of_bytes(bytes)
inspect(input.in_channel_length, content="5")
inspect((input.input_byte)(), content="72") // 'H'
inspect((input.input_byte)(), content="101") // 'e'
}

#Creating Input from String

///|
test "input_of_string" {
let input = @pdfio.Input::of_string("ABC")
inspect((input.input_char)(), content="Some('A')")
inspect((input.input_char)(), content="Some('B')")
}

#Peeking and Rewinding

///|
test "peek_byte does not advance" {
let input = @pdfio.Input::of_string("XY")
let first = input.peek_byte()
let second = input.peek_byte()
inspect(first, content="88") // 'X'
inspect(second, content="88") // still 'X'
}

#Reading Lines

///|
test "read_line" {
let input = @pdfio.Input::of_string("line1\nline2\n")
inspect(input.read_line(), content="line1")
inspect(input.read_line(), content="line2")
}

#Extracting Bytes from Input

///|
test "bytes_of_input extracts range" {
let input = @pdfio.Input::of_string("Hello World")
let bytes = input.bytes_of_input(0, 5)
inspect(@pdfio.string_of_bytes(bytes), content="Hello")
}

#Output Streams

The Output struct provides a seekable output stream:

///|
pub struct Output {
pos_out : () -> Int // Current position
seek_out : (Int) -> Unit // Seek to position
output_char : (Char) -> Unit // Write char
output_byte : (Int) -> Unit // Write byte
output_string : (String) -> Unit // Write string
out_channel_length : () -> Int // Written length
flush : async () -> Unit // Flush buffer
}

#Creating Output Buffers

///|
test "input_output_of_bytes" {
let (output, data) = @pdfio.Output::of_bytes(16)
(output.output_string)("test")
let bytes = output.extract_bytes(data)
inspect(@pdfio.string_of_bytes(bytes), content="test")
}

#Native File/Channel IO

core/pdfio is intentionally focused on in-memory Input/Output and byte utilities.

For native @fs.File helpers (read whole file/channel into memory, or create an Output backed by a channel), use io/pdfiofs.

#Bitstreams

For reading data at the bit level (MSB-first order).

#Creating a Bitstream

///|
test "bitstream reading" {
// Byte 0xA5 = 10100101 in binary
let input = @pdfio.Input::of_bytes(@pdfio.bytes_of_list([0xA5]))
let bits = @pdfio.Bitstream::of_input(input)

// Read first 4 bits: 1010 = 10
let val = getval_32(bits, 4) // internal function
inspect(val, content="10")
}

#Bit-Level Reading

///|
test "getbit reads individual bits" {
let input = @pdfio.Input::of_bytes(@pdfio.bytes_of_list([0x80])) // 10000000
let bits = @pdfio.Bitstream::of_input(input)
inspect(bits.getbit(), content="true") // bit 7
inspect(bits.getbit(), content="false") // bit 6
}

#Bitstream Position

///|
test "bitstream save and restore" {
let input = @pdfio.Input::of_bytes(@pdfio.bytes_of_list([0xFF, 0x00]))
let bits = @pdfio.Bitstream::of_input(input)
let pos = bits.position()
ignore(getval_32(bits, 8)) // read 8 bits (internal function)
bits.seek(pos) // rewind
inspect(getval_32(bits, 8), content="255") // read again
}

#Alignment

///|
test "align skips to next byte" {
let input = @pdfio.Input::of_bytes(@pdfio.bytes_of_list([0xFF, 0xAB]))
let bits = @pdfio.Bitstream::of_input(input)
ignore(bits.getbit()) // read 1 bit
bits.align() // skip to byte boundary
inspect(getval_32(bits, 8), content="171") // 0xAB (internal function)
}

#Write Bitstreams

///|
test "write bitstream" {
let b = @pdfio.BitstreamWrite::new()
b.putval(4, 0b1010) // write 4 bits: 1010
b.putval(4, 0b0101) // write 4 bits: 0101
let bytes = b.bytes()
inspect(@pdfio.bget(bytes, 0), content="165") // 0xA5
}

#Constants

///|
pub let no_more : Int = -1 // Indicates end of input

#Utility Functions

#Transform Bytes In-Place (Internal)

///|
test "bytes_selfmap transforms each byte" {
let bytes = @pdfio.bytes_of_list([1, 2, 3])
bytes_selfmap(fn(x) { x * 2 }, bytes) // internal function
inspect(@pdfio.int_array_of_bytes(bytes), content="[2, 4, 6]")
}

#Fill Bytes (Internal)

///|
test "fillbytes sets all bytes" {
let bytes = @pdfio.mkbytes(3)
fillbytes(42, bytes) // internal function
inspect(@pdfio.int_array_of_bytes(bytes), content="[42, 42, 42]")
}

#
MutableBytes

type MutableBytes = Array[Byte]

Mutable byte buffer used throughout the port.

#
Bitstream

pub struct Bitstream {
input : Input
curr_byte : Int
bit : Int
bits_read : Int
}

Most-significant-bit-first bitstream over an Input.

#
Bitstream::align

fn Bitstream::align(self : Bitstream) -> Unit

Align a bitstream to the next byte boundary.

#
Bitstream::getbit

fn Bitstream::getbit(self : Bitstream) -> Bool raise

Read the next bit from a bitstream.

#
Bitstream::getbitint

fn Bitstream::getbitint(self : Bitstream) -> Int raise

Read the next bit as an integer, 0 or 1.

#
Bitstream::of_input

fn Bitstream::of_input(input : Input) -> Bitstream

Build a bitstream from an input.

#
Bitstream::position

fn Bitstream::position(self : Bitstream) -> BitstreamPosition

Get the current position of a bitstream.

#
Bitstream::seek

fn Bitstream::seek(self : Bitstream, pos : BitstreamPosition) -> Unit

Seek to a previous bitstream position.

#
BitstreamPosition

pub struct BitstreamPosition {
pos : Int
curr_byte : Int
bit : Int
bits_read : Int
}

Position token for Bitstream; details will evolve during implementation.

#
BitstreamWrite

pub struct BitstreamWrite {
bytes : Array[Byte]
bit : Int
}

Most-significant-bit-first write bitstream (placeholder).

#
BitstreamWrite::align

fn BitstreamWrite::align(self : BitstreamWrite) -> Unit

Align a write bitstream to the next byte boundary.

#
BitstreamWrite::bytes

fn BitstreamWrite::bytes(self : BitstreamWrite) -> Array[Byte]

Extract bytes from a write bitstream, padding with zeros.

#
BitstreamWrite::new

Make a new write bitstream.

#
BitstreamWrite::putbit

fn BitstreamWrite::putbit(self : BitstreamWrite, bit : Int) -> Unit

Put a single bit into a write bitstream.

#
BitstreamWrite::putval

fn BitstreamWrite::putval(self : BitstreamWrite, bits : Int, value : Int) -> Unit raise

Put a multi-bit value into a write bitstream.

#
Input

pub struct Input {
pos_in : () -> Int
seek_in : (Int) -> Unit
input_char : () -> Char?
input_byte : () -> Int
in_channel_length : Int
set_offset : (Int) -> Unit
source : String
}

Input abstraction (seekable stream of bytes).

#
Input::bytes_of_input

fn Input::bytes_of_input(self : Input, offset : Int, length : Int) -> Array[Byte] raise

Bytes with input o..o+l-1.

#
Input::nudge

fn Input::nudge(self : Input) -> Unit

Move forward one byte.

#
Input::of_bytes

fn Input::of_bytes(bytes : Array[Byte], source? : String) -> Input

Build an input from bytes.

#
Input::of_string

fn Input::of_string(s : String, source? : String) -> Input

Build an input from a string.

#
Input::peek_byte

fn Input::peek_byte(self : Input) -> Int

Look at the next byte without advancing the pointer.

#
Input::peek_char

fn Input::peek_char(self : Input) -> Char?

Look at the next character without advancing the pointer.

#
Input::read_char_back

fn Input::read_char_back(self : Input) -> Char?

Read the previous character, moving the pointer back one.

#
Input::read_line

fn Input::read_line(self : Input) -> String raise

Read a line from an input (newline not included).

#
Input::read_lines

fn Input::read_lines(self : Input) -> Array[String] raise

Read all lines from an input.

#
Input::rewind

fn Input::rewind(self : Input) -> Unit

Move backward one byte.

#
Input::setinit

fn Input::setinit(self : Input, bytes : Array[Byte], offset : Int, length : Int) -> Unit raise

Set bytes o..o+l-1 from input.

#
Input::to_string

fn Input::to_string(self : Input) -> String raise

String of input contents.

#
Output

pub struct Output {
pos_out : () -> Int
seek_out : (Int) -> Unit
output_char : (Char) -> Unit
output_byte : (Int) -> Unit
output_string : (String) -> Unit
out_channel_length : () -> Int
flush : async () -> Unit
}

Output abstraction (seekable stream of bytes).

#
Output::extract_bytes

fn Output::extract_bytes(self : Output, data : Ref[Array[Byte]]) -> Array[Byte]

Extract the contents of an input-output in bytes.

#
Output::getinit

fn Output::getinit(self : Output, bytes : Array[Byte], offset : Int, length : Int) -> Unit raise

Write bytes o..o+l-1 to output.

#
Output::of_bytes

fn Output::of_bytes(size : Int) -> (Output, Ref[Array[Byte]])

Build an input-output, with an initial buffer size.

#
Output::of_write_at

fn Output::of_write_at(write_at : async (BytesView, Int64) -> Unit) -> Output

Build an output backed by an external write_at function.

The output uses an internal growable buffer. flush writes the full buffer from the beginning (position 0).

#
bget

fn bget(bytes : Array[Byte], index : Int) -> Int

Get the value at a position in bytes.

#
bget_unsafe

fn bget_unsafe(bytes : Array[Byte], index : Int) -> Int

Like bget, but without bounds checking.

#
bset

fn bset(bytes : Array[Byte], index : Int, value : Int) -> Unit

Set the value at a position in bytes.

#
bset_unsafe

fn bset_unsafe(bytes : Array[Byte], index : Int, value : Int) -> Unit

Like bset, but without bounds checking.

#
bytes_of_arraylist

fn bytes_of_arraylist(values : Array[Array[Int]]) -> Array[Byte]

Make bytes from a list of integer arrays.

#
bytes_of_caml_bytes

fn bytes_of_caml_bytes(bytes : Bytes) -> Array[Byte]

Make bytes from core bytes.

#
bytes_of_charlist

fn bytes_of_charlist(values : Array[Char]) -> Array[Byte]

Make bytes from a character array.

#
bytes_of_int_array

fn bytes_of_int_array(values : Array[Int]) -> Array[Byte]

Make bytes from an integer array.

#
bytes_of_list

fn bytes_of_list(values : Array[Int]) -> Array[Byte]

Make bytes from an array of integers (each 0..255).

#
bytes_of_string

fn bytes_of_string(s : String) -> Array[Byte]

Make bytes from a string by taking the low 8 bits of each character.

#
bytes_size

fn bytes_size(bytes : Array[Byte]) -> Int

Size of bytes.

#
charlist_of_bytes

fn charlist_of_bytes(bytes : Array[Byte]) -> Array[Char]

Make a character array from bytes.

#
copybytes

fn copybytes(bytes : Array[Byte]) -> Array[Byte]

Copy bytes.

#
debug_next_n_chars

fn debug_next_n_chars(count : Int, input : Input) -> Unit

Debug the next count characters to the log and rewind.

#
int_array_of_bytes

fn int_array_of_bytes(bytes : Array[Byte]) -> Array[Int]

Integer array from bytes.

#
int_array_of_string

fn int_array_of_string(s : String) -> Array[Int]

Integer array from a string (byte-wise).

#
mkbytes

fn mkbytes(size : Int) -> Array[Byte]

Build bytes with the given size, filled with zero.

#
no_more

let no_more : Int

Distinguished value indicating "no more input".

#
string_of_bytes

fn string_of_bytes(bytes : Array[Byte]) -> String

Make a string by mapping each byte to a single character.

#
string_of_int_array

fn string_of_int_array(values : Array[Int]) -> String

String from a single int array.

#
string_of_int_arrays

fn string_of_int_arrays(values : Array[Array[Int]]) -> String

String from a list of integer arrays.

Source Files

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io