🖨️ esc/pos implementation in gleam
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}