+129
-40
src/escpos.gleam
+129
-40
src/escpos.gleam
···
1
1
import escpos/protocol
2
2
import gleam/bit_array
3
-
import gleam/result
4
-
import mug
3
+
import gleam/bool
4
+
5
+
pub opaque type CommandBuffer {
6
+
CommandBuffer(data: BitArray)
7
+
}
8
+
9
+
pub fn new() -> CommandBuffer {
10
+
CommandBuffer(<<>>)
11
+
}
12
+
13
+
pub fn write(cb: CommandBuffer, text: String) -> CommandBuffer {
14
+
append(cb, bit_array.from_string(text))
15
+
}
16
+
17
+
pub fn writeln(cb: CommandBuffer, text: String) -> CommandBuffer {
18
+
append(cb, bit_array.from_string(text))
19
+
|> append(protocol.lf)
20
+
}
21
+
22
+
pub fn bold(
23
+
cb: CommandBuffer,
24
+
commands: fn(CommandBuffer) -> CommandBuffer,
25
+
) -> CommandBuffer {
26
+
append(cb, protocol.bold(True))
27
+
|> commands
28
+
|> append(protocol.bold(False))
29
+
}
30
+
31
+
pub fn underline(
32
+
cb: CommandBuffer,
33
+
commands: fn(CommandBuffer) -> CommandBuffer,
34
+
) -> CommandBuffer {
35
+
append(cb, protocol.underline(True))
36
+
|> commands
37
+
|> append(protocol.underline(False))
38
+
}
39
+
40
+
pub fn double_strike(
41
+
cb: CommandBuffer,
42
+
commands: fn(CommandBuffer) -> CommandBuffer,
43
+
) -> CommandBuffer {
44
+
append(cb, protocol.double_strike(True))
45
+
|> commands
46
+
|> append(protocol.double_strike(False))
47
+
}
5
48
6
-
pub opaque type Printer {
7
-
Printer(socket: mug.Socket)
49
+
pub fn reverse(
50
+
cb: CommandBuffer,
51
+
commands: fn(CommandBuffer) -> CommandBuffer,
52
+
) -> CommandBuffer {
53
+
append(cb, protocol.reverse(True))
54
+
|> commands
55
+
|> append(protocol.reverse(False))
8
56
}
9
57
10
-
pub opaque type CommandBuffer {
11
-
CommandBuffer(data: BitArray)
58
+
pub fn upside_down(
59
+
cb: CommandBuffer,
60
+
commands: fn(CommandBuffer) -> CommandBuffer,
61
+
) -> CommandBuffer {
62
+
append(cb, protocol.upside_down(True))
63
+
|> commands
64
+
|> ensure_new_line
65
+
|> append(protocol.upside_down(False))
66
+
}
67
+
68
+
pub fn smooth(
69
+
cb: CommandBuffer,
70
+
commands: fn(CommandBuffer) -> CommandBuffer,
71
+
) -> CommandBuffer {
72
+
append(cb, protocol.smooth(True))
73
+
|> commands
74
+
|> append(protocol.smooth(False))
12
75
}
13
76
14
-
pub opaque type EscposError {
15
-
ConnectionFailed(mug.ConnectError)
16
-
DisconnectionFailed(mug.Error)
17
-
TransmissionError(mug.Error)
18
-
PrintError(mug.Error)
77
+
pub fn flip(
78
+
cb: CommandBuffer,
79
+
commands: fn(CommandBuffer) -> CommandBuffer,
80
+
) -> CommandBuffer {
81
+
append(cb, protocol.flip(True))
82
+
|> commands
83
+
|> append(protocol.flip(False))
19
84
}
20
85
21
-
pub fn connect(ip: String, port: Int) -> Result(Printer, EscposError) {
22
-
use socket <- result.try(
23
-
mug.new(ip, port)
24
-
|> mug.timeout(milliseconds: 500)
25
-
|> mug.connect()
26
-
|> result.map_error(ConnectionFailed),
27
-
)
86
+
pub fn set_font(cb: CommandBuffer, font: protocol.Font) -> CommandBuffer {
87
+
append(cb, protocol.font(font))
88
+
}
28
89
29
-
use _ <- result.try(
30
-
mug.send(socket, protocol.init)
31
-
|> result.map_error(TransmissionError),
32
-
)
90
+
pub fn reset_font(cb: CommandBuffer) -> CommandBuffer {
91
+
append(cb, protocol.font(protocol.FontA))
92
+
}
33
93
34
-
Ok(Printer(socket))
94
+
pub fn set_align(cb: CommandBuffer, justify: protocol.Justify) -> CommandBuffer {
95
+
ensure_new_line(cb)
96
+
|> append(protocol.justify(justify))
35
97
}
36
98
37
-
pub fn new() -> CommandBuffer {
38
-
CommandBuffer(<<>>)
99
+
pub fn set_text_size(
100
+
cb: CommandBuffer,
101
+
width: Int,
102
+
height: Int,
103
+
) -> CommandBuffer {
104
+
append(cb, protocol.character_size(width, height))
39
105
}
40
106
41
-
pub fn write(cb: CommandBuffer, text: String) -> CommandBuffer {
42
-
bit_array.append(cb.data, bit_array.from_string(text))
43
-
|> CommandBuffer
107
+
pub fn reset_text_size(cb: CommandBuffer) -> CommandBuffer {
108
+
append(cb, protocol.character_size(1, 1))
44
109
}
45
110
46
111
pub fn cut(cb: CommandBuffer) -> CommandBuffer {
47
-
bit_array.append(cb.data, protocol.full_cut)
48
-
|> CommandBuffer
112
+
line_feed(cb, 3)
113
+
|> append(protocol.full_cut)
49
114
}
50
115
51
116
pub fn partial_cut(cb: CommandBuffer) -> CommandBuffer {
52
-
bit_array.append(cb.data, protocol.partial_cut)
53
-
|> CommandBuffer
117
+
line_feed(cb, 3)
118
+
|> append(protocol.partial_cut)
119
+
}
120
+
121
+
pub fn new_line(cb: CommandBuffer) -> CommandBuffer {
122
+
append(cb, protocol.lf)
54
123
}
55
124
56
125
pub fn line_feed(cb: CommandBuffer, lines: Int) -> CommandBuffer {
57
-
bit_array.append(cb.data, protocol.line_feed(lines))
126
+
append(cb, protocol.line_feed(lines))
127
+
}
128
+
129
+
pub fn reset(cb: CommandBuffer) -> CommandBuffer {
130
+
append(cb, protocol.init)
131
+
}
132
+
133
+
pub fn custom(cb: CommandBuffer, data: BitArray) -> CommandBuffer {
134
+
append(cb, data)
135
+
}
136
+
137
+
fn append(cb: CommandBuffer, data: BitArray) -> CommandBuffer {
138
+
bit_array.append(cb.data, data)
58
139
|> CommandBuffer
59
140
}
60
141
61
-
pub fn print(cb: CommandBuffer, printer: Printer) -> Result(Nil, EscposError) {
62
-
let data = bit_array.append(cb.data, protocol.cr)
142
+
pub fn get_payload(cb: CommandBuffer) -> BitArray {
143
+
ensure_new_line(cb).data
144
+
}
63
145
64
-
mug.send(printer.socket, data)
65
-
|> result.map_error(PrintError)
146
+
fn ensure_new_line(cb: CommandBuffer) -> CommandBuffer {
147
+
case is_new_line(cb) {
148
+
True -> cb
149
+
False -> new_line(cb)
150
+
}
66
151
}
67
152
68
-
pub fn disconnect(printer: Printer) -> Result(Nil, EscposError) {
69
-
mug.shutdown(printer.socket)
70
-
|> result.map_error(DisconnectionFailed)
153
+
fn is_new_line(cb: CommandBuffer) -> Bool {
154
+
use <- bool.guard(cb.data == <<>>, True)
155
+
case bit_array.slice(cb.data, -1, 3) {
156
+
Ok(<<27, "d", _>>) -> True
157
+
Ok(<<_, _, 10>>) -> True
158
+
_ -> False
159
+
}
71
160
}
+44
src/escpos/printer.gleam
+44
src/escpos/printer.gleam
···
1
+
import escpos.{type CommandBuffer}
2
+
import escpos/protocol
3
+
import gleam/result
4
+
import mug
5
+
6
+
pub opaque type Printer {
7
+
Printer(socket: mug.Socket)
8
+
}
9
+
10
+
pub opaque type PrinterError {
11
+
ConnectionFailed(mug.ConnectError)
12
+
DisconnectionFailed(mug.Error)
13
+
TransmissionError(mug.Error)
14
+
PrintError(mug.Error)
15
+
}
16
+
17
+
pub fn connect(ip: String, port: Int) -> Result(Printer, PrinterError) {
18
+
use socket <- result.try(
19
+
mug.new(ip, port)
20
+
|> mug.timeout(milliseconds: 500)
21
+
|> mug.connect()
22
+
|> result.map_error(ConnectionFailed),
23
+
)
24
+
25
+
use _ <- result.try(
26
+
mug.send(socket, protocol.init)
27
+
|> result.map_error(TransmissionError),
28
+
)
29
+
30
+
Ok(Printer(socket))
31
+
}
32
+
33
+
/// Sends the CommandBuffer to the printer
34
+
pub fn print(cb: CommandBuffer, printer: Printer) -> Result(Nil, PrinterError) {
35
+
let data = escpos.get_payload(cb)
36
+
37
+
mug.send(printer.socket, data)
38
+
|> result.map_error(PrintError)
39
+
}
40
+
41
+
pub fn disconnect(printer: Printer) -> Result(Nil, PrinterError) {
42
+
mug.shutdown(printer.socket)
43
+
|> result.map_error(DisconnectionFailed)
44
+
}
+96
-4
src/escpos/protocol.gleam
+96
-4
src/escpos/protocol.gleam
···
1
+
import gleam/int
2
+
3
+
pub type Justify {
4
+
Left
5
+
Center
6
+
Right
7
+
}
8
+
9
+
pub type Font {
10
+
FontA
11
+
FontB
12
+
FontC
13
+
FontD
14
+
FontE
15
+
SpecialFontA
16
+
SpecialFontB
17
+
}
18
+
1
19
const esc = 27
2
20
3
21
const gs = 29
···
6
24
7
25
pub const lf = <<10>>
8
26
9
-
pub const cr = <<13>>
10
-
11
27
pub const full_cut = <<gs, "V", 0>>
12
28
13
29
pub const partial_cut = <<gs, "V", 1>>
14
30
15
31
pub fn line_feed(lines: Int) -> BitArray {
16
32
case lines {
17
-
l if l < 2 -> lf
18
-
l if l > 255 -> <<esc, "d", 255>>
33
+
l if l < 2 -> <<esc, "d", 1>>
34
+
l if l > 254 -> <<esc, "d", 255>>
19
35
_ -> <<esc, "d", lines>>
20
36
}
21
37
}
38
+
39
+
/// requires to be on a new line to take effect
40
+
pub fn justify(justify: Justify) -> BitArray {
41
+
case justify {
42
+
Left -> <<esc, "a", 0>>
43
+
Center -> <<esc, "a", 1>>
44
+
Right -> <<esc, "a", 2>>
45
+
}
46
+
}
47
+
48
+
pub fn bold(on: Bool) -> BitArray {
49
+
case on {
50
+
True -> <<esc, "E", 1>>
51
+
False -> <<esc, "E", 0>>
52
+
}
53
+
}
54
+
55
+
pub fn underline(on: Bool) -> BitArray {
56
+
case on {
57
+
True -> <<esc, "-", 1>>
58
+
False -> <<esc, "-", 0>>
59
+
}
60
+
}
61
+
62
+
pub fn double_strike(on: Bool) -> BitArray {
63
+
case on {
64
+
True -> <<esc, "G", 1>>
65
+
False -> <<esc, "G", 0>>
66
+
}
67
+
}
68
+
69
+
pub fn reverse(on: Bool) -> BitArray {
70
+
case on {
71
+
True -> <<gs, "B", 1>>
72
+
False -> <<gs, "B", 0>>
73
+
}
74
+
}
75
+
76
+
pub fn upside_down(on: Bool) -> BitArray {
77
+
case on {
78
+
True -> <<esc, "{", 1>>
79
+
False -> <<esc, "{", 0>>
80
+
}
81
+
}
82
+
83
+
pub fn smooth(on: Bool) -> BitArray {
84
+
case on {
85
+
True -> <<gs, "b", 1>>
86
+
False -> <<gs, "b", 0>>
87
+
}
88
+
}
89
+
90
+
pub fn flip(on: Bool) -> BitArray {
91
+
case on {
92
+
True -> <<esc, "V", 1>>
93
+
False -> <<esc, "V", 0>>
94
+
}
95
+
}
96
+
97
+
pub fn font(font: Font) -> BitArray {
98
+
case font {
99
+
FontA -> <<esc, "M", 0>>
100
+
FontB -> <<esc, "M", 1>>
101
+
FontC -> <<esc, "M", 2>>
102
+
FontD -> <<esc, "M", 3>>
103
+
FontE -> <<esc, "M", 4>>
104
+
SpecialFontA -> <<esc, "M", 97>>
105
+
SpecialFontB -> <<esc, "M", 98>>
106
+
}
107
+
}
108
+
109
+
pub fn character_size(width: Int, height: Int) -> BitArray {
110
+
let w = int.clamp(width, min: 1, max: 8) |> int.subtract(1)
111
+
let h = int.clamp(height, min: 1, max: 8) |> int.subtract(1)
112
+
<<gs, "!", 0:1, w:3, 0:1, h:3>>
113
+
}
+101
-7
test/escpos_test.gleam
+101
-7
test/escpos_test.gleam
···
1
1
import escpos
2
+
import escpos/printer
3
+
import escpos/protocol
2
4
import gleeunit
3
5
4
6
pub fn main() -> Nil {
5
7
gleeunit.main()
6
8
}
7
9
10
+
fn setup_printer() -> Result(printer.Printer, printer.PrinterError) {
11
+
printer.connect("10.255.8.62", 9100)
12
+
}
13
+
14
+
fn test_print(
15
+
printer: printer.Printer,
16
+
name: String,
17
+
commands: fn(escpos.CommandBuffer) -> escpos.CommandBuffer,
18
+
) {
19
+
escpos.new()
20
+
|> escpos.writeln("----- " <> name)
21
+
|> commands
22
+
|> escpos.new_line
23
+
|> printer.print(printer)
24
+
}
25
+
8
26
pub fn print_test() {
9
-
let assert Ok(printer) = escpos.connect("localhost", 9100)
27
+
let assert Ok(printer) = setup_printer()
10
28
11
29
let assert Ok(Nil) =
12
-
escpos.new()
13
-
|> escpos.write("Hello, World!")
14
-
|> escpos.line_feed(2)
15
-
|> escpos.partial_cut()
16
-
|> escpos.print(printer)
30
+
test_print(printer, "writeln", escpos.write(_, "Hello, World!"))
17
31
18
-
escpos.disconnect(printer)
32
+
let assert Ok(Nil) =
33
+
test_print(
34
+
printer,
35
+
"write bold",
36
+
escpos.bold(_, escpos.write(_, "Hello, World!")),
37
+
)
38
+
39
+
let assert Ok(Nil) =
40
+
test_print(
41
+
printer,
42
+
"write underline",
43
+
escpos.underline(_, escpos.write(_, "Hello, World!")),
44
+
)
45
+
46
+
let assert Ok(Nil) =
47
+
test_print(
48
+
printer,
49
+
"write double strike",
50
+
escpos.double_strike(_, escpos.write(_, "Hello, World!")),
51
+
)
52
+
53
+
let assert Ok(Nil) =
54
+
test_print(
55
+
printer,
56
+
"write reverse",
57
+
escpos.reverse(_, escpos.write(_, "Hello, World!")),
58
+
)
59
+
60
+
let assert Ok(Nil) =
61
+
test_print(
62
+
printer,
63
+
"write upside down",
64
+
escpos.upside_down(_, escpos.write(_, "Hello, World!")),
65
+
)
66
+
67
+
let assert Ok(Nil) =
68
+
test_print(printer, "font B", fn(b) {
69
+
escpos.set_font(b, protocol.FontB)
70
+
|> escpos.write("Hello, World!")
71
+
|> escpos.reset_font
72
+
})
73
+
74
+
let assert Ok(Nil) =
75
+
test_print(printer, "font C", fn(b) {
76
+
escpos.set_font(b, protocol.FontC)
77
+
|> escpos.write("Hello, World!")
78
+
|> escpos.reset_font
79
+
})
80
+
81
+
let assert Ok(Nil) =
82
+
test_print(printer, "high text size", fn(b) {
83
+
escpos.set_text_size(b, 1, 8)
84
+
|> escpos.write("Hello, World!")
85
+
|> escpos.reset_text_size
86
+
})
87
+
88
+
let assert Ok(Nil) =
89
+
test_print(printer, "wide text size", fn(b) {
90
+
escpos.set_text_size(b, 8, 1)
91
+
|> escpos.write("Hello, World!")
92
+
|> escpos.reset_text_size
93
+
})
94
+
95
+
let assert Ok(Nil) =
96
+
test_print(printer, "large text size", fn(b) {
97
+
escpos.set_text_size(b, 3, 3)
98
+
|> escpos.write("Hello, World!")
99
+
|> escpos.reset_text_size
100
+
})
101
+
102
+
let assert Ok(Nil) =
103
+
test_print(printer, "line feed 5", escpos.line_feed(_, 1))
104
+
105
+
let assert Ok(Nil) = test_print(printer, "partial cut", escpos.partial_cut)
106
+
107
+
printer.disconnect(printer)
108
+
}
109
+
110
+
pub fn protocol_character_size_test() {
111
+
assert protocol.character_size(2, 2) == <<29, "!", 0:1, 1:3, 0:1, 1:3>>
112
+
assert protocol.character_size(20, 20) == <<29, "!", 0:1, 7:3, 0:1, 7:3>>
19
113
}