🖨️ esc/pos implementation in gleam
at justify-between 198 lines 5.3 kB view raw
1//// A low-level imperative API for building ESC/POS printer commands. 2//// 3//// This module provides a builder-style interface where each function takes 4//// a `CommandBuffer` and returns a modified buffer. For a declarative 5//// alternative, see `escpos/document`. 6//// 7//// ## Example 8//// 9//// ```gleam 10//// import escpos.{Center, Left} 11//// import escpos/printer 12//// 13//// escpos.new() 14//// |> escpos.reset() 15//// |> escpos.align(Center) 16//// |> escpos.bold(True) 17//// |> escpos.writeln("Receipt") 18//// |> escpos.bold(False) 19//// |> escpos.align(Left) 20//// |> escpos.writeln("Item 1 ... $5.00") 21//// |> escpos.cut() 22//// |> printer.print(my_printer) 23//// ``` 24 25import escpos/image 26import escpos/printer.{type CommandBuffer, CommandBuffer} 27import escpos/protocol 28import gleam/bit_array 29import gleam/bool 30 31/// Text justification. Re-exported from `escpos/protocol`. 32pub type Justify = 33 protocol.Justify 34 35/// Printer font. Re-exported from `escpos/protocol`. 36pub type Font = 37 protocol.Font 38 39/// Creates a new empty command buffer. 40pub fn new() -> CommandBuffer { 41 CommandBuffer(<<>>) 42} 43 44/// Writes text to the buffer. 45pub fn write(cb: CommandBuffer, text: String) -> CommandBuffer { 46 append(cb, bit_array.from_string(text)) 47} 48 49/// Writes text to the buffer followed by a newline. 50pub fn writeln(cb: CommandBuffer, text: String) -> CommandBuffer { 51 append(cb, bit_array.from_string(text)) 52 |> append(protocol.lf) 53} 54 55/// Enables or disables bold text. 56pub fn bold(cb: CommandBuffer, b: Bool) -> CommandBuffer { 57 append(cb, protocol.bold(b)) 58} 59 60/// Enables or disables underlined text. 61pub fn underline(cb: CommandBuffer, b: Bool) -> CommandBuffer { 62 append(cb, protocol.underline(b)) 63} 64 65/// Enables or disables double-strike text. 66pub fn double_strike(cb: CommandBuffer, b: Bool) -> CommandBuffer { 67 append(cb, protocol.double_strike(b)) 68} 69 70/// Enables or disables reverse (white on black) text. 71pub fn reverse(cb: CommandBuffer, b: Bool) -> CommandBuffer { 72 append(cb, protocol.reverse(b)) 73} 74 75/// Enables or disables upside-down text. 76pub fn upside_down(cb: CommandBuffer, b: Bool) -> CommandBuffer { 77 append(cb, protocol.upside_down(b)) 78} 79 80/// Enables or disables character smoothing. 81pub fn smooth(cb: CommandBuffer, b: Bool) -> CommandBuffer { 82 append(cb, protocol.smooth(b)) 83} 84 85/// Enables or disables 180-degree rotation. 86pub fn flip(cb: CommandBuffer, b: Bool) -> CommandBuffer { 87 append(cb, protocol.flip(b)) 88} 89 90/// Sets the printer font. 91pub fn font(cb: CommandBuffer, font: Font) -> CommandBuffer { 92 append(cb, protocol.font(font)) 93} 94 95/// Resets the font to the default (FontA). 96pub fn reset_font(cb: CommandBuffer) -> CommandBuffer { 97 append(cb, protocol.font(protocol.FontA)) 98} 99 100/// Sets text alignment (Left, Center, or Right). 101pub fn align(cb: CommandBuffer, justify: Justify) -> CommandBuffer { 102 ensure_new_line(cb) 103 |> append(protocol.justify(justify)) 104} 105 106/// Sets text size multiplier (1-8 for width and height). 107pub fn text_size( 108 cb: CommandBuffer, 109 width: Int, 110 height: Int, 111) -> CommandBuffer { 112 append(cb, protocol.character_size(width, height)) 113} 114 115/// Resets text size to normal (1x1). 116pub fn reset_text_size(cb: CommandBuffer) -> CommandBuffer { 117 append(cb, protocol.character_size(1, 1)) 118} 119 120/// Performs a full paper cut. 121pub fn cut(cb: CommandBuffer) -> CommandBuffer { 122 append(cb, protocol.cut(protocol.Full)) 123} 124 125/// Performs a partial paper cut, leaving a small portion connected. 126pub fn partial_cut(cb: CommandBuffer) -> CommandBuffer { 127 append(cb, protocol.cut(protocol.Partial)) 128} 129 130/// Appends a single newline. 131pub fn new_line(cb: CommandBuffer) -> CommandBuffer { 132 append(cb, protocol.lf) 133} 134 135/// Feeds the specified number of lines. 136pub fn line_feed(cb: CommandBuffer, lines: Int) -> CommandBuffer { 137 append(cb, protocol.line_feed(lines)) 138} 139 140/// Resets the printer to its initial state. 141pub fn reset(cb: CommandBuffer) -> CommandBuffer { 142 append(cb, protocol.init) 143} 144 145/// Prints a monochrome image prepared by the `image` module. 146/// 147/// ## Example 148/// 149/// ```gleam 150/// let assert Ok(pgm) = simplifile.read_bits(from: "./lucy.pgm") 151/// let assert Ok(img) = image.from_pgm(raw_pgm) 152/// let img = image.dither_bayer4x4(img, 0) 153/// 154/// escpos.new() 155/// |> escpos.image(img) 156/// |> printer.print(printer) 157/// ``` 158pub fn image(cb: CommandBuffer, image: image.PrintableImage) -> CommandBuffer { 159 ensure_new_line(cb) 160 |> append(protocol.image_to_graphics_buffer( 161 image.pixels(image), 162 image.width(image), 163 image.height(image), 164 protocol.Monochrome, 165 protocol.Scale1x, 166 protocol.Scale1x, 167 protocol.Color1, 168 )) 169 |> append(protocol.print_graphics_buffer()) 170} 171 172/// Appends raw bytes to the buffer. 173pub fn raw(cb: CommandBuffer, data: BitArray) -> CommandBuffer { 174 append(cb, data) 175} 176 177fn append(cb: CommandBuffer, data: BitArray) -> CommandBuffer { 178 bit_array.append(cb.data, data) 179 |> CommandBuffer 180} 181 182fn ensure_new_line(cb: CommandBuffer) -> CommandBuffer { 183 case is_new_line(cb) { 184 True -> cb 185 False -> new_line(cb) 186 } 187} 188 189fn is_new_line(cb: CommandBuffer) -> Bool { 190 use <- bool.guard(when: cb.data == <<>>, return: True) 191 case 192 bit_array.slice(from: cb.data, at: bit_array.byte_size(cb.data), take: -3) 193 { 194 Ok(<<27, "d", _>>) -> True 195 Ok(<<_, _, 10>>) -> True 196 _ -> False 197 } 198}