🖨️ esc/pos implementation in gleam
at main 220 lines 6.4 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} 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 printer.new_buffer() 42} 43 44/// Writes text to the buffer. 45pub fn write(cb: CommandBuffer, text: String) -> CommandBuffer { 46 printer.append_to_buffer(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 printer.append_to_buffer(cb, bit_array.from_string(text)) 52 |> printer.append_to_buffer(protocol.lf) 53} 54 55/// Enables or disables bold text. 56pub fn bold(cb: CommandBuffer, b: Bool) -> CommandBuffer { 57 printer.append_to_buffer(cb, protocol.bold(b)) 58} 59 60/// Enables or disables underlined text. 61pub fn underline(cb: CommandBuffer, b: Bool) -> CommandBuffer { 62 printer.append_to_buffer(cb, protocol.underline(b)) 63} 64 65/// Enables or disables double-strike text. 66pub fn double_strike(cb: CommandBuffer, b: Bool) -> CommandBuffer { 67 printer.append_to_buffer(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 printer.append_to_buffer(cb, protocol.reverse(b)) 73} 74 75/// Enables or disables upside-down text. 76pub fn upside_down(cb: CommandBuffer, b: Bool) -> CommandBuffer { 77 printer.append_to_buffer(cb, protocol.upside_down(b)) 78} 79 80/// Enables or disables character smoothing. 81pub fn smooth(cb: CommandBuffer, b: Bool) -> CommandBuffer { 82 printer.append_to_buffer(cb, protocol.smooth(b)) 83} 84 85/// Enables or disables 180-degree rotation. 86pub fn flip(cb: CommandBuffer, b: Bool) -> CommandBuffer { 87 printer.append_to_buffer(cb, protocol.flip(b)) 88} 89 90/// Sets the printer font. 91pub fn font(cb: CommandBuffer, font: Font) -> CommandBuffer { 92 printer.append_to_buffer(cb, protocol.font(font)) 93} 94 95/// Resets the font to the default (FontA). 96pub fn reset_font(cb: CommandBuffer) -> CommandBuffer { 97 printer.append_to_buffer(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 |> printer.append_to_buffer(protocol.justify(justify)) 104} 105 106/// Sets text size multiplier (1-8 for width and height). 107pub fn text_size(cb: CommandBuffer, width: Int, height: Int) -> CommandBuffer { 108 printer.append_to_buffer(cb, protocol.character_size(width, height)) 109} 110 111/// Resets text size to normal (1x1). 112pub fn reset_text_size(cb: CommandBuffer) -> CommandBuffer { 113 printer.append_to_buffer(cb, protocol.character_size(1, 1)) 114} 115 116/// Performs a full paper cut. 117pub fn cut(cb: CommandBuffer) -> CommandBuffer { 118 printer.append_to_buffer(cb, protocol.cut(protocol.Full)) 119} 120 121/// Performs a partial paper cut, leaving a small portion connected. 122pub fn partial_cut(cb: CommandBuffer) -> CommandBuffer { 123 printer.append_to_buffer(cb, protocol.cut(protocol.Partial)) 124} 125 126/// Appends a single newline. 127pub fn new_line(cb: CommandBuffer) -> CommandBuffer { 128 printer.append_to_buffer(cb, protocol.lf) 129} 130 131/// Feeds the specified number of lines. 132pub fn line_feed(cb: CommandBuffer, lines: Int) -> CommandBuffer { 133 printer.append_to_buffer(cb, protocol.line_feed(lines)) 134} 135 136/// Resets the printer to its initial state. 137pub fn reset(cb: CommandBuffer) -> CommandBuffer { 138 printer.append_to_buffer(cb, protocol.init) 139} 140 141/// Prints a monochrome image prepared by the `image` module. 142/// 143/// ## Example 144/// 145/// ```gleam 146/// let assert Ok(pgm) = simplifile.read_bits(from: "./lucy.pgm") 147/// let assert Ok(img) = image.from_pgm(raw_pgm) 148/// let img = image.dither_bayer4x4(img, 0) 149/// 150/// escpos.new() 151/// |> escpos.image(img) 152/// |> printer.print(printer) 153/// ``` 154pub fn image(cb: CommandBuffer, image: image.PrintableImage) -> CommandBuffer { 155 ensure_new_line(cb) 156 |> printer.append_to_buffer(protocol.image_to_graphics_buffer( 157 image.pixels(image), 158 image.width(image), 159 image.height(image), 160 protocol.Monochrome, 161 protocol.Scale1x, 162 protocol.Scale1x, 163 protocol.Color1, 164 )) 165 |> printer.append_to_buffer(protocol.print_graphics_buffer()) 166} 167 168/// Prints a monochrome image prepared by the `image` module. This is using an older 169/// method to print images, useful for some printers that do not understand graphic buffer 170/// commands. 171/// 172/// ## Example 173/// 174/// ```gleam 175/// let assert Ok(pgm) = simplifile.read_bits(from: "./lucy.pgm") 176/// let assert Ok(img) = image.from_pgm(raw_pgm) 177/// let img = image.dither_bayer4x4(img, 0) 178/// 179/// escpos.new() 180/// |> escpos.image_raster(img) 181/// |> printer.print(printer) 182/// ``` 183pub fn image_raster( 184 cb: CommandBuffer, 185 image: image.PrintableImage, 186) -> CommandBuffer { 187 ensure_new_line(cb) 188 |> printer.append_to_buffer(protocol.image_to_raster( 189 image.width(image), 190 image.height(image), 191 image.pixels(image), 192 )) 193} 194 195/// Appends raw bytes to the buffer. 196pub fn raw(cb: CommandBuffer, data: BitArray) -> CommandBuffer { 197 printer.append_to_buffer(cb, data) 198} 199 200fn ensure_new_line(cb: CommandBuffer) -> CommandBuffer { 201 case is_new_line(cb) { 202 True -> cb 203 False -> new_line(cb) 204 } 205} 206 207fn is_new_line(cb: CommandBuffer) -> Bool { 208 use <- bool.guard(when: printer.buffer_to_bits(cb) == <<>>, return: True) 209 case 210 bit_array.slice( 211 from: printer.buffer_to_bits(cb), 212 at: bit_array.byte_size(printer.buffer_to_bits(cb)), 213 take: -3, 214 ) 215 { 216 Ok(<<27, "d", _>>) -> True 217 Ok(<<_, _, 10>>) -> True 218 _ -> False 219 } 220}