#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:

    • Array[Byte]: 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 Array[Byte] = Array[Byte]

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

    #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)
    debug_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")
    debug_inspect((input.input_char)(), content="Some('A')")
    debug_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]")
    }

    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
    } derive(
    Debug
    )

    Position token for Bitstream; details will evolve during implementation.

    BitstreamWrite

    pub struct BitstreamWrite {
    bytes : Array[Byte]
    bit : Int
    } derive(
    Debug
    )

    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::backline

    fn Input::backline(self : Input) -> Unit raise

    Go back one line.

    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::dropwhite

    fn Input::dropwhite(self : Input) -> Unit raise

    Drop PDF whitespace.

    Input::getuntil

    fn Input::getuntil(self : Input, eof_is_delimiter : Bool, pred : (Char) -> Bool) -> Array[Char] raise

    Read chars until predicate is true.

    Input::getuntil_string

    fn Input::getuntil_string(self : Input, eof_is_delimiter : Bool, pred : (Char) -> Bool) -> String raise

    Read chars until predicate is true, returning a string.

    Input::getuntil_white_or_delimiter_string

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

    Read chars until whitespace or delimiter.

    Input::ignoreuntil

    fn Input::ignoreuntil(self : Input, eof_is_delimiter : Bool, pred : (Char) -> Bool) -> Unit raise

    Ignore chars until predicate is true.

    Input::input_line

    fn Input::input_line(self : Input) -> String

    Read a line from input (newline not included).

    Unlike Input::read_line, this returns "" at EOF instead of raising.

    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_chunk

    fn Input::read_chunk(self : Input, count : Int) -> String raise

    Read count characters from input without advancing the cursor.

    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).

    is_delimiter

    fn is_delimiter(c : Char) -> Bool

    is_newline

    fn is_newline(c : Char) -> Bool

    is_whitespace

    fn is_whitespace(c : Char) -> Bool

    PDF whitespace predicate.

    is_whitespace_or_delimiter

    fn is_whitespace_or_delimiter(c : Char) -> Bool

    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.

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io