tangled
alpha
login
or
join now
okk.moe
/
escpos
2
fork
atom
🖨️ esc/pos implementation in gleam
2
fork
atom
overview
issues
pulls
pipelines
feat: document api
okk.moe
5 months ago
dc1231a4
cefa8edd
verified
This commit was signed with the committer's
known signature
.
okk.moe
SSH Key Fingerprint:
SHA256:cVofqXFhnCrIkqo1ixFFvbU8h8MTjljdVwg5pBQ7wxY=
+387
-103
5 changed files
expand all
collapse all
unified
split
src
escpos
document.gleam
printer.gleam
protocol.gleam
escpos.gleam
test
escpos_test.gleam
+17
-60
src/escpos.gleam
···
1
1
+
import escpos/printer.{type CommandBuffer, CommandBuffer}
1
2
import escpos/protocol
2
3
import gleam/bit_array
3
4
import gleam/bool
4
4
-
5
5
-
pub opaque type CommandBuffer {
6
6
-
CommandBuffer(data: BitArray)
7
7
-
}
8
5
9
6
pub fn new() -> CommandBuffer {
10
7
CommandBuffer(<<>>)
···
19
16
|> append(protocol.lf)
20
17
}
21
18
22
22
-
pub fn bold(
23
23
-
cb: CommandBuffer,
24
24
-
commands: fn(CommandBuffer) -> CommandBuffer,
25
25
-
) -> CommandBuffer {
26
26
-
append(cb, protocol.bold(True))
27
27
-
|> commands
28
28
-
|> append(protocol.bold(False))
19
19
+
pub fn bold(cb: CommandBuffer, b: Bool) -> CommandBuffer {
20
20
+
append(cb, protocol.bold(b))
29
21
}
30
22
31
31
-
pub fn underline(
32
32
-
cb: CommandBuffer,
33
33
-
commands: fn(CommandBuffer) -> CommandBuffer,
34
34
-
) -> CommandBuffer {
35
35
-
append(cb, protocol.underline(True))
36
36
-
|> commands
37
37
-
|> append(protocol.underline(False))
23
23
+
pub fn underline(cb: CommandBuffer, b: Bool) -> CommandBuffer {
24
24
+
append(cb, protocol.underline(b))
38
25
}
39
26
40
40
-
pub fn double_strike(
41
41
-
cb: CommandBuffer,
42
42
-
commands: fn(CommandBuffer) -> CommandBuffer,
43
43
-
) -> CommandBuffer {
44
44
-
append(cb, protocol.double_strike(True))
45
45
-
|> commands
46
46
-
|> append(protocol.double_strike(False))
27
27
+
pub fn double_strike(cb: CommandBuffer, b: Bool) -> CommandBuffer {
28
28
+
append(cb, protocol.double_strike(b))
47
29
}
48
30
49
49
-
pub fn reverse(
50
50
-
cb: CommandBuffer,
51
51
-
commands: fn(CommandBuffer) -> CommandBuffer,
52
52
-
) -> CommandBuffer {
53
53
-
append(cb, protocol.reverse(True))
54
54
-
|> commands
55
55
-
|> append(protocol.reverse(False))
31
31
+
pub fn reverse(cb: CommandBuffer, b: Bool) -> CommandBuffer {
32
32
+
append(cb, protocol.reverse(b))
56
33
}
57
34
58
58
-
pub fn upside_down(
59
59
-
cb: CommandBuffer,
60
60
-
commands: fn(CommandBuffer) -> CommandBuffer,
61
61
-
) -> CommandBuffer {
62
62
-
append(cb, protocol.upside_down(True))
63
63
-
|> commands
64
64
-
|> ensure_new_line
65
65
-
|> append(protocol.upside_down(False))
35
35
+
pub fn upside_down(cb: CommandBuffer, b: Bool) -> CommandBuffer {
36
36
+
append(cb, protocol.upside_down(b))
66
37
}
67
38
68
68
-
pub fn smooth(
69
69
-
cb: CommandBuffer,
70
70
-
commands: fn(CommandBuffer) -> CommandBuffer,
71
71
-
) -> CommandBuffer {
72
72
-
append(cb, protocol.smooth(True))
73
73
-
|> commands
74
74
-
|> append(protocol.smooth(False))
39
39
+
pub fn smooth(cb: CommandBuffer, b: Bool) -> CommandBuffer {
40
40
+
append(cb, protocol.smooth(b))
75
41
}
76
42
77
77
-
pub fn flip(
78
78
-
cb: CommandBuffer,
79
79
-
commands: fn(CommandBuffer) -> CommandBuffer,
80
80
-
) -> CommandBuffer {
81
81
-
append(cb, protocol.flip(True))
82
82
-
|> commands
83
83
-
|> append(protocol.flip(False))
43
43
+
pub fn flip(cb: CommandBuffer, b: Bool) -> CommandBuffer {
44
44
+
append(cb, protocol.flip(b))
84
45
}
85
46
86
47
pub fn set_font(cb: CommandBuffer, font: protocol.Font) -> CommandBuffer {
···
110
71
111
72
pub fn cut(cb: CommandBuffer) -> CommandBuffer {
112
73
line_feed(cb, 3)
113
113
-
|> append(protocol.full_cut)
74
74
+
|> append(protocol.cut(protocol.Full))
114
75
}
115
76
116
77
pub fn partial_cut(cb: CommandBuffer) -> CommandBuffer {
117
78
line_feed(cb, 3)
118
118
-
|> append(protocol.partial_cut)
79
79
+
|> append(protocol.cut(protocol.Partial))
119
80
}
120
81
121
82
pub fn new_line(cb: CommandBuffer) -> CommandBuffer {
···
137
98
fn append(cb: CommandBuffer, data: BitArray) -> CommandBuffer {
138
99
bit_array.append(cb.data, data)
139
100
|> CommandBuffer
140
140
-
}
141
141
-
142
142
-
pub fn get_payload(cb: CommandBuffer) -> BitArray {
143
143
-
ensure_new_line(cb).data
144
101
}
145
102
146
103
fn ensure_new_line(cb: CommandBuffer) -> CommandBuffer {
+352
src/escpos/document.gleam
···
1
1
+
import escpos/printer.{type CommandBuffer, CommandBuffer}
2
2
+
import escpos/protocol
3
3
+
import gleam/bit_array
4
4
+
import gleam/list
5
5
+
import gleam/option.{type Option, None, Some}
6
6
+
import gleam/set.{type Set}
7
7
+
8
8
+
pub opaque type Command {
9
9
+
Write(String)
10
10
+
Writeln(String)
11
11
+
LineFeed(Int)
12
12
+
Cut(protocol.Cut)
13
13
+
Styled(modifiers: Set(Modifier), commands: List(Command))
14
14
+
Custom(BitArray)
15
15
+
}
16
16
+
17
17
+
pub opaque type Modifier {
18
18
+
Bold
19
19
+
Underline
20
20
+
DoubleStrike
21
21
+
Reverse
22
22
+
UpsideDown
23
23
+
Justify(protocol.Justify)
24
24
+
Font(protocol.Font)
25
25
+
}
26
26
+
27
27
+
pub opaque type AST {
28
28
+
Init
29
29
+
DoWrite(String)
30
30
+
DoLineFeed(Int)
31
31
+
DoCut(protocol.Cut)
32
32
+
DoCustom(BitArray)
33
33
+
SetBold(Bool)
34
34
+
SetUnderline(Bool)
35
35
+
SetDoubleStrike(Bool)
36
36
+
SetReverse(Bool)
37
37
+
SetUpsideDown(Bool)
38
38
+
SetJustify(protocol.Justify)
39
39
+
SetFont(protocol.Font)
40
40
+
}
41
41
+
42
42
+
type State {
43
43
+
State(
44
44
+
new_line: Bool,
45
45
+
bold: Bool,
46
46
+
underline: Bool,
47
47
+
double_strike: Bool,
48
48
+
reverse: Bool,
49
49
+
upside_down: Bool,
50
50
+
justify: protocol.Justify,
51
51
+
font: protocol.Font,
52
52
+
)
53
53
+
}
54
54
+
55
55
+
fn default_state() -> State {
56
56
+
State(
57
57
+
new_line: True,
58
58
+
bold: False,
59
59
+
underline: False,
60
60
+
double_strike: False,
61
61
+
reverse: False,
62
62
+
upside_down: False,
63
63
+
justify: protocol.Left,
64
64
+
font: protocol.FontA,
65
65
+
)
66
66
+
}
67
67
+
68
68
+
pub fn build(document: List(Command)) -> CommandBuffer {
69
69
+
build_ast(document)
70
70
+
|> compile_ast
71
71
+
|> CommandBuffer
72
72
+
}
73
73
+
74
74
+
pub fn styled(modifiers: List(Modifier), commands: List(Command)) -> Command {
75
75
+
Styled(set.from_list(modifiers), commands)
76
76
+
}
77
77
+
78
78
+
pub fn write(text: String) -> Command {
79
79
+
Write(text)
80
80
+
}
81
81
+
82
82
+
pub fn writeln(text: String) -> Command {
83
83
+
Writeln(text)
84
84
+
}
85
85
+
86
86
+
pub fn line_feed(lines: Int) -> Command {
87
87
+
LineFeed(lines)
88
88
+
}
89
89
+
90
90
+
pub fn cut() -> Command {
91
91
+
Cut(protocol.Full)
92
92
+
}
93
93
+
94
94
+
pub fn partial_cut() -> Command {
95
95
+
Cut(protocol.Partial)
96
96
+
}
97
97
+
98
98
+
pub fn bold() -> Modifier {
99
99
+
Bold
100
100
+
}
101
101
+
102
102
+
pub fn underline() -> Modifier {
103
103
+
Underline
104
104
+
}
105
105
+
106
106
+
pub fn double_strike() -> Modifier {
107
107
+
DoubleStrike
108
108
+
}
109
109
+
110
110
+
pub fn reverse() -> Modifier {
111
111
+
Reverse
112
112
+
}
113
113
+
114
114
+
pub fn upside_down() -> Modifier {
115
115
+
UpsideDown
116
116
+
}
117
117
+
118
118
+
pub fn justify(j: protocol.Justify) -> Modifier {
119
119
+
Justify(j)
120
120
+
}
121
121
+
122
122
+
pub fn font(f: protocol.Font) -> Modifier {
123
123
+
Font(f)
124
124
+
}
125
125
+
126
126
+
fn build_ast(commands: List(Command)) -> List(AST) {
127
127
+
do_build_ast(commands, [Init], default_state())
128
128
+
|> list.reverse
129
129
+
}
130
130
+
131
131
+
fn do_build_ast(
132
132
+
commands: List(Command),
133
133
+
acc: List(AST),
134
134
+
state: State,
135
135
+
) -> List(AST) {
136
136
+
case commands {
137
137
+
[] -> acc
138
138
+
[cmd, ..rest] -> {
139
139
+
case cmd {
140
140
+
Styled(modifiers, commands) -> {
141
141
+
let #(acc_with_mods, nested_state) =
142
142
+
do_apply_modifiers(set.to_list(modifiers), acc, state)
143
143
+
let acc_with_commands =
144
144
+
do_build_ast(commands, acc_with_mods, nested_state)
145
145
+
let #(acc_with_reset_styles, reset_state) =
146
146
+
revert_styles(state, nested_state, acc_with_commands)
147
147
+
do_build_ast(
148
148
+
rest,
149
149
+
acc_with_reset_styles,
150
150
+
State(..state, new_line: reset_state.new_line),
151
151
+
)
152
152
+
}
153
153
+
Cut(c) -> {
154
154
+
let new_acc =
155
155
+
list.prepend(acc, DoLineFeed(3)) |> list.prepend(DoCut(c))
156
156
+
do_build_ast(rest, new_acc, State(..state, new_line: True))
157
157
+
}
158
158
+
LineFeed(n) ->
159
159
+
do_build_ast(
160
160
+
rest,
161
161
+
[DoLineFeed(n), ..acc],
162
162
+
State(..state, new_line: True),
163
163
+
)
164
164
+
Write(x) ->
165
165
+
do_build_ast(
166
166
+
rest,
167
167
+
[DoWrite(x), ..acc],
168
168
+
State(..state, new_line: False),
169
169
+
)
170
170
+
Writeln(x) -> {
171
171
+
let new_acc =
172
172
+
list.prepend(acc, DoWrite(x)) |> list.prepend(DoLineFeed(1))
173
173
+
do_build_ast(rest, new_acc, State(..state, new_line: True))
174
174
+
}
175
175
+
Custom(b) ->
176
176
+
do_build_ast(
177
177
+
rest,
178
178
+
[DoCustom(b), ..acc],
179
179
+
State(..state, new_line: False),
180
180
+
)
181
181
+
}
182
182
+
}
183
183
+
}
184
184
+
}
185
185
+
186
186
+
fn revert_styles(
187
187
+
state: State,
188
188
+
nested_state: State,
189
189
+
acc: List(AST),
190
190
+
) -> #(List(AST), State) {
191
191
+
let changes =
192
192
+
[
193
193
+
changed(state.bold != nested_state.bold, SetBold(state.bold)),
194
194
+
changed(
195
195
+
state.underline != nested_state.underline,
196
196
+
SetUnderline(state.underline),
197
197
+
),
198
198
+
changed(
199
199
+
state.double_strike != nested_state.double_strike,
200
200
+
SetDoubleStrike(state.double_strike),
201
201
+
),
202
202
+
changed(state.reverse != nested_state.reverse, SetReverse(state.reverse)),
203
203
+
changed(
204
204
+
state.upside_down != nested_state.upside_down,
205
205
+
SetUpsideDown(state.upside_down),
206
206
+
),
207
207
+
changed(state.justify != nested_state.justify, SetJustify(state.justify)),
208
208
+
changed(state.font != nested_state.font, SetFont(state.font)),
209
209
+
]
210
210
+
|> option.values
211
211
+
212
212
+
do_revert_styles(changes, acc, state)
213
213
+
}
214
214
+
215
215
+
fn do_revert_styles(
216
216
+
changes: List(AST),
217
217
+
acc: List(AST),
218
218
+
state: State,
219
219
+
) -> #(List(AST), State) {
220
220
+
case changes {
221
221
+
[] -> #(acc, state)
222
222
+
[style, ..rest] ->
223
223
+
case style {
224
224
+
SetJustify(_) | SetUpsideDown(_) ->
225
225
+
case state.new_line {
226
226
+
True -> do_revert_styles(rest, [style, ..acc], state)
227
227
+
False ->
228
228
+
do_revert_styles(
229
229
+
rest,
230
230
+
list.prepend(acc, DoLineFeed(1)) |> list.prepend(style),
231
231
+
State(..state, new_line: True),
232
232
+
)
233
233
+
}
234
234
+
_ -> do_revert_styles(rest, [style, ..acc], state)
235
235
+
}
236
236
+
}
237
237
+
}
238
238
+
239
239
+
fn changed(change: Bool, ast: AST) -> Option(AST) {
240
240
+
case change {
241
241
+
True -> Some(ast)
242
242
+
False -> None
243
243
+
}
244
244
+
}
245
245
+
246
246
+
fn do_apply_modifiers(
247
247
+
modifiers: List(Modifier),
248
248
+
acc: List(AST),
249
249
+
state: State,
250
250
+
) -> #(List(AST), State) {
251
251
+
case modifiers {
252
252
+
[] -> #(acc, state)
253
253
+
[mod, ..rest] -> {
254
254
+
case mod {
255
255
+
Bold ->
256
256
+
do_apply_modifiers(
257
257
+
rest,
258
258
+
[SetBold(True), ..acc],
259
259
+
State(..state, bold: True),
260
260
+
)
261
261
+
DoubleStrike ->
262
262
+
do_apply_modifiers(
263
263
+
rest,
264
264
+
[SetDoubleStrike(True), ..acc],
265
265
+
State(..state, double_strike: True),
266
266
+
)
267
267
+
Underline ->
268
268
+
do_apply_modifiers(
269
269
+
rest,
270
270
+
[SetUnderline(True), ..acc],
271
271
+
State(..state, underline: True),
272
272
+
)
273
273
+
Reverse ->
274
274
+
do_apply_modifiers(
275
275
+
rest,
276
276
+
[SetReverse(True), ..acc],
277
277
+
State(..state, reverse: True),
278
278
+
)
279
279
+
UpsideDown ->
280
280
+
case state.new_line {
281
281
+
True ->
282
282
+
do_apply_modifiers(
283
283
+
rest,
284
284
+
[SetUpsideDown(True), ..acc],
285
285
+
State(..state, upside_down: True, new_line: True),
286
286
+
)
287
287
+
False ->
288
288
+
do_apply_modifiers(
289
289
+
rest,
290
290
+
list.prepend(acc, DoLineFeed(1))
291
291
+
|> list.prepend(SetUpsideDown(True)),
292
292
+
State(..state, upside_down: True),
293
293
+
)
294
294
+
}
295
295
+
Justify(j) ->
296
296
+
case state.new_line {
297
297
+
True ->
298
298
+
do_apply_modifiers(
299
299
+
rest,
300
300
+
[SetJustify(j), ..acc],
301
301
+
State(..state, justify: j, new_line: True),
302
302
+
)
303
303
+
False ->
304
304
+
do_apply_modifiers(
305
305
+
rest,
306
306
+
list.prepend(acc, DoLineFeed(1)) |> list.prepend(SetJustify(j)),
307
307
+
State(..state, justify: j),
308
308
+
)
309
309
+
}
310
310
+
Font(f) ->
311
311
+
do_apply_modifiers(rest, [SetFont(f), ..acc], State(..state, font: f))
312
312
+
}
313
313
+
}
314
314
+
}
315
315
+
}
316
316
+
317
317
+
fn compile_ast(ast: List(AST)) -> BitArray {
318
318
+
do_compile_ast(ast, <<>>)
319
319
+
}
320
320
+
321
321
+
fn do_compile_ast(ast: List(AST), acc: BitArray) -> BitArray {
322
322
+
case ast {
323
323
+
[] -> acc
324
324
+
[cmd, ..rest] ->
325
325
+
case cmd {
326
326
+
Init -> do_compile_ast(rest, bit_array.append(acc, protocol.init))
327
327
+
DoWrite(x) ->
328
328
+
do_compile_ast(rest, bit_array.append(acc, bit_array.from_string(x)))
329
329
+
DoLineFeed(n) ->
330
330
+
do_compile_ast(rest, bit_array.append(acc, protocol.line_feed(n)))
331
331
+
DoCut(c) -> do_compile_ast(rest, bit_array.append(acc, protocol.cut(c)))
332
332
+
DoCustom(b) -> do_compile_ast(rest, bit_array.append(acc, b))
333
333
+
SetBold(on) ->
334
334
+
do_compile_ast(rest, bit_array.append(acc, protocol.bold(on)))
335
335
+
SetUnderline(on) ->
336
336
+
do_compile_ast(rest, bit_array.append(acc, protocol.underline(on)))
337
337
+
SetDoubleStrike(on) ->
338
338
+
do_compile_ast(
339
339
+
rest,
340
340
+
bit_array.append(acc, protocol.double_strike(on)),
341
341
+
)
342
342
+
SetReverse(on) ->
343
343
+
do_compile_ast(rest, bit_array.append(acc, protocol.reverse(on)))
344
344
+
SetUpsideDown(on) ->
345
345
+
do_compile_ast(rest, bit_array.append(acc, protocol.upside_down(on)))
346
346
+
SetJustify(j) ->
347
347
+
do_compile_ast(rest, bit_array.append(acc, protocol.justify(j)))
348
348
+
SetFont(f) ->
349
349
+
do_compile_ast(rest, bit_array.append(acc, protocol.font(f)))
350
350
+
}
351
351
+
}
352
352
+
}
+6
-4
src/escpos/printer.gleam
···
1
1
-
import escpos.{type CommandBuffer}
2
1
import escpos/protocol
3
2
import gleam/result
4
3
import mug
4
4
+
5
5
+
@internal
6
6
+
pub type CommandBuffer {
7
7
+
CommandBuffer(data: BitArray)
8
8
+
}
5
9
6
10
pub opaque type Printer {
7
11
Printer(socket: mug.Socket)
···
32
36
33
37
/// Sends the CommandBuffer to the printer
34
38
pub fn print(cb: CommandBuffer, printer: Printer) -> Result(Nil, PrinterError) {
35
35
-
let data = escpos.get_payload(cb)
36
36
-
37
37
-
mug.send(printer.socket, data)
39
39
+
mug.send(printer.socket, cb.data)
38
40
|> result.map_error(PrintError)
39
41
}
40
42
+11
-3
src/escpos/protocol.gleam
···
6
6
Right
7
7
}
8
8
9
9
+
pub type Cut {
10
10
+
Partial
11
11
+
Full
12
12
+
}
13
13
+
9
14
pub type Font {
10
15
FontA
11
16
FontB
···
24
29
25
30
pub const lf = <<10>>
26
31
27
27
-
pub const full_cut = <<gs, "V", 0>>
28
28
-
29
29
-
pub const partial_cut = <<gs, "V", 1>>
32
32
+
pub fn cut(cut: Cut) -> BitArray {
33
33
+
case cut {
34
34
+
Full -> <<gs, "V", 0>>
35
35
+
Partial -> <<gs, "V", 1>>
36
36
+
}
37
37
+
}
30
38
31
39
pub fn line_feed(lines: Int) -> BitArray {
32
40
case lines {
+1
-36
test/escpos_test.gleam
···
8
8
}
9
9
10
10
fn setup_printer() -> Result(printer.Printer, printer.PrinterError) {
11
11
-
printer.connect("10.255.8.62", 9100)
11
11
+
printer.connect("localhost", 9100)
12
12
}
13
13
14
14
fn test_print(
···
28
28
29
29
let assert Ok(Nil) =
30
30
test_print(printer, "writeln", escpos.write(_, "Hello, World!"))
31
31
-
32
32
-
let assert Ok(Nil) =
33
33
-
test_print(
34
34
-
printer,
35
35
-
"write bold",
36
36
-
escpos.bold(_, escpos.write(_, "Hello, World!")),
37
37
-
)
38
38
-
39
39
-
let assert Ok(Nil) =
40
40
-
test_print(
41
41
-
printer,
42
42
-
"write underline",
43
43
-
escpos.underline(_, escpos.write(_, "Hello, World!")),
44
44
-
)
45
45
-
46
46
-
let assert Ok(Nil) =
47
47
-
test_print(
48
48
-
printer,
49
49
-
"write double strike",
50
50
-
escpos.double_strike(_, escpos.write(_, "Hello, World!")),
51
51
-
)
52
52
-
53
53
-
let assert Ok(Nil) =
54
54
-
test_print(
55
55
-
printer,
56
56
-
"write reverse",
57
57
-
escpos.reverse(_, escpos.write(_, "Hello, World!")),
58
58
-
)
59
59
-
60
60
-
let assert Ok(Nil) =
61
61
-
test_print(
62
62
-
printer,
63
63
-
"write upside down",
64
64
-
escpos.upside_down(_, escpos.write(_, "Hello, World!")),
65
65
-
)
66
31
67
32
let assert Ok(Nil) =
68
33
test_print(printer, "font B", fn(b) {