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: image apis and pbm support
okk.moe
1 month ago
fe2f0093
753ff539
verified
This commit was signed with the committer's
known signature
.
okk.moe
SSH Key Fingerprint:
SHA256:cVofqXFhnCrIkqo1ixFFvbU8h8MTjljdVwg5pBQ7wxY=
+154
-146
4 changed files
expand all
collapse all
unified
split
dev
escpos_dev.gleam
src
escpos
document.gleam
image.gleam
escpos.gleam
-3
dev/dev.gleam
dev/escpos_dev.gleam
···
1
1
import escpos
2
2
-
import escpos/document
3
2
import escpos/image
4
3
import escpos/printer
5
5
-
import escpos/protocol
6
4
import simplifile
7
5
8
6
pub fn main() {
···
13
11
img
14
12
// |> image.dither_ign
15
13
|> image.dither_bayer4x4(0)
16
16
-
|> echo
17
14
18
15
let assert Ok(printer) = printer.connect("10.13.141.62", 9100)
19
16
+4
-4
src/escpos.gleam
···
92
92
append(cb, protocol.init)
93
93
}
94
94
95
95
-
pub fn image(cb: CommandBuffer, image: image.MonochromeImage) -> CommandBuffer {
95
95
+
pub fn image(cb: CommandBuffer, image: image.PrintableImage) -> CommandBuffer {
96
96
ensure_new_line(cb)
97
97
|> append(protocol.image_to_graphics_buffer(
98
98
-
image.pixels,
99
99
-
image.width,
100
100
-
image.height,
98
98
+
image.pixels(image),
99
99
+
image.width(image),
100
100
+
image.height(image),
101
101
protocol.Monochrome,
102
102
protocol.Scale1x,
103
103
protocol.Scale1x,
+60
-42
src/escpos/document.gleam
···
1
1
+
import escpos/image
1
2
import escpos/printer.{type CommandBuffer, CommandBuffer}
2
3
import escpos/protocol
3
4
import gleam/bit_array
···
10
11
Writeln(String)
11
12
LineFeed(Int)
12
13
Cut(protocol.Cut)
14
14
+
Image(image: image.PrintableImage)
13
15
Styled(modifiers: Set(Modifier), commands: List(Command))
14
16
Custom(BitArray)
15
17
}
···
33
35
DoWrite(String)
34
36
DoLineFeed(Int)
35
37
DoCut(protocol.Cut)
38
38
+
DoImage(image.PrintableImage)
36
39
DoCustom(BitArray)
37
40
SetBold(Bool)
38
41
SetUnderline(Bool)
···
77
80
)
78
81
}
79
82
83
83
+
fn ensure_new_line(state: State, acc: List(AST)) -> #(List(AST), State) {
84
84
+
case state.new_line {
85
85
+
True -> #(acc, state)
86
86
+
False -> #([DoLineFeed(1), ..acc], State(..state, new_line: True))
87
87
+
}
88
88
+
}
89
89
+
80
90
pub fn build(document: List(Command)) -> CommandBuffer {
81
91
upside_down_pass(document)
82
92
|> build_ast
···
100
110
LineFeed(lines)
101
111
}
102
112
113
113
+
pub fn image(image: image.PrintableImage) -> Command {
114
114
+
Image(image)
115
115
+
}
116
116
+
103
117
pub fn cut() -> Command {
104
118
Cut(protocol.Full)
105
119
}
···
125
139
}
126
140
127
141
pub fn flip() -> Modifier {
128
128
-
Reverse
142
142
+
Flip
129
143
}
130
144
131
145
pub fn upside_down() -> Modifier {
···
235
249
list.prepend(acc, DoWrite(x)) |> list.prepend(DoLineFeed(1))
236
250
do_build_ast(rest, new_acc, State(..state, new_line: True))
237
251
}
252
252
+
Image(i) -> {
253
253
+
let #(acc, state) = ensure_new_line(state, acc)
254
254
+
do_build_ast(
255
255
+
rest,
256
256
+
[DoImage(i), ..acc],
257
257
+
State(..state, new_line: True),
258
258
+
)
259
259
+
}
238
260
Custom(b) ->
239
261
do_build_ast(
240
262
rest,
···
290
312
[] -> #(acc, state)
291
313
[style, ..rest] ->
292
314
case style {
293
293
-
SetJustify(_) | SetUpsideDown(_) ->
294
294
-
case state.new_line {
295
295
-
True -> do_revert_styles(rest, [style, ..acc], state)
296
296
-
False ->
297
297
-
do_revert_styles(
298
298
-
rest,
299
299
-
list.prepend(acc, DoLineFeed(1)) |> list.prepend(style),
300
300
-
State(..state, new_line: True),
301
301
-
)
302
302
-
}
315
315
+
SetJustify(_) | SetUpsideDown(_) -> {
316
316
+
let #(acc, state) = ensure_new_line(state, acc)
317
317
+
do_revert_styles(rest, [style, ..acc], state)
318
318
+
}
303
319
_ -> do_revert_styles(rest, [style, ..acc], state)
304
320
}
305
321
}
···
351
367
[SetFlip(True), ..acc],
352
368
State(..state, flip: True),
353
369
)
354
354
-
UpsideDown ->
355
355
-
case state.new_line {
356
356
-
True ->
357
357
-
do_apply_modifiers(
358
358
-
rest,
359
359
-
[SetUpsideDown(True), ..acc],
360
360
-
State(..state, upside_down: True, new_line: True),
361
361
-
)
362
362
-
False ->
363
363
-
do_apply_modifiers(
364
364
-
rest,
365
365
-
list.prepend(acc, DoLineFeed(1))
366
366
-
|> list.prepend(SetUpsideDown(True)),
367
367
-
State(..state, upside_down: True),
368
368
-
)
369
369
-
}
370
370
+
UpsideDown -> {
371
371
+
let #(acc, state) = ensure_new_line(state, acc)
372
372
+
do_apply_modifiers(
373
373
+
rest,
374
374
+
[SetUpsideDown(True), ..acc],
375
375
+
State(..state, upside_down: True),
376
376
+
)
377
377
+
}
370
378
TextSize(size) ->
371
379
do_apply_modifiers(
372
380
rest,
···
385
393
[SetTextSize(width: state.text_width, height: size), ..acc],
386
394
State(..state, text_height: size),
387
395
)
388
388
-
Justify(j) ->
389
389
-
case state.new_line {
390
390
-
True ->
391
391
-
do_apply_modifiers(
392
392
-
rest,
393
393
-
[SetJustify(j), ..acc],
394
394
-
State(..state, justify: j, new_line: True),
395
395
-
)
396
396
-
False ->
397
397
-
do_apply_modifiers(
398
398
-
rest,
399
399
-
list.prepend(acc, DoLineFeed(1)) |> list.prepend(SetJustify(j)),
400
400
-
State(..state, justify: j),
401
401
-
)
402
402
-
}
396
396
+
Justify(j) -> {
397
397
+
let #(acc, state) = ensure_new_line(state, acc)
398
398
+
do_apply_modifiers(
399
399
+
rest,
400
400
+
[SetJustify(j), ..acc],
401
401
+
State(..state, justify: j),
402
402
+
)
403
403
+
}
403
404
Font(f) ->
404
405
do_apply_modifiers(rest, [SetFont(f), ..acc], State(..state, font: f))
405
406
}
···
419
420
Init -> do_compile_ast(rest, bit_array.append(acc, protocol.init))
420
421
DoWrite(x) ->
421
422
do_compile_ast(rest, bit_array.append(acc, bit_array.from_string(x)))
423
423
+
DoImage(i) ->
424
424
+
do_compile_ast(
425
425
+
rest,
426
426
+
bit_array.append(
427
427
+
acc,
428
428
+
protocol.image_to_graphics_buffer(
429
429
+
image.pixels(i),
430
430
+
image.width(i),
431
431
+
image.height(i),
432
432
+
protocol.Monochrome,
433
433
+
protocol.Scale1x,
434
434
+
protocol.Scale1x,
435
435
+
protocol.Color1,
436
436
+
),
437
437
+
)
438
438
+
|> bit_array.append(protocol.print_graphics_buffer()),
439
439
+
)
422
440
DoLineFeed(n) ->
423
441
do_compile_ast(rest, bit_array.append(acc, protocol.line_feed(n)))
424
442
DoCut(c) -> do_compile_ast(rest, bit_array.append(acc, protocol.cut(c)))
+90
-97
src/escpos/image.gleam
···
4
4
import gleam/int
5
5
import gleam/result
6
6
7
7
-
pub type Image {
7
7
+
pub opaque type Image {
8
8
Image(width: Int, height: Int, pixels: BitArray)
9
9
}
10
10
11
11
-
pub type MonochromeImage {
12
12
-
MonochromeImage(width: Int, height: Int, pixels: BitArray)
11
11
+
pub opaque type PrintableImage {
12
12
+
PrintableImage(width: Int, height: Int, pixels: BitArray)
13
13
}
14
14
15
15
pub opaque type ImageError {
16
16
ParseError
17
17
}
18
18
19
19
-
pub fn from_pgm(pgm: BitArray) -> Result(Image, ImageError) {
20
20
-
case pgm {
21
21
-
<<"P5\n", rest:bits>> -> pgm_width(rest, 0)
19
19
+
pub fn pixels(image: PrintableImage) -> BitArray {
20
20
+
image.pixels
21
21
+
}
22
22
+
23
23
+
pub fn width(image: PrintableImage) -> Int {
24
24
+
image.width
25
25
+
}
26
26
+
27
27
+
pub fn height(image: PrintableImage) -> Int {
28
28
+
image.height
29
29
+
}
30
30
+
31
31
+
pub fn from_pbm(pbm: BitArray) -> Result(PrintableImage, ImageError) {
32
32
+
case pbm {
33
33
+
<<"P4\n", rest:bits>> -> {
34
34
+
use #(width, rest) <- result.try(parse_number(rest, 0))
35
35
+
use #(height, pixels) <- result.try(parse_number(rest, 0))
36
36
+
Ok(PrintableImage(width:, height:, pixels:))
37
37
+
}
22
38
_ -> Error(ParseError)
23
39
}
24
40
}
25
41
26
26
-
fn pgm_width(pgm: BitArray, width: Int) -> Result(Image, ImageError) {
42
42
+
pub fn from_pgm(pgm: BitArray) -> Result(Image, ImageError) {
27
43
case pgm {
28
28
-
<<"0", rest:bits>> -> pgm_width(rest, width * 10 + 0)
29
29
-
<<"1", rest:bits>> -> pgm_width(rest, width * 10 + 1)
30
30
-
<<"2", rest:bits>> -> pgm_width(rest, width * 10 + 2)
31
31
-
<<"3", rest:bits>> -> pgm_width(rest, width * 10 + 3)
32
32
-
<<"4", rest:bits>> -> pgm_width(rest, width * 10 + 4)
33
33
-
<<"5", rest:bits>> -> pgm_width(rest, width * 10 + 5)
34
34
-
<<"6", rest:bits>> -> pgm_width(rest, width * 10 + 6)
35
35
-
<<"7", rest:bits>> -> pgm_width(rest, width * 10 + 7)
36
36
-
<<"8", rest:bits>> -> pgm_width(rest, width * 10 + 8)
37
37
-
<<"9", rest:bits>> -> pgm_width(rest, width * 10 + 9)
38
38
-
<<" ", rest:bits>> -> pgm_height(rest, width, 0)
44
44
+
<<"P5\n", rest:bits>> -> {
45
45
+
use #(width, rest) <- result.try(parse_number(rest, 0))
46
46
+
use #(height, rest) <- result.try(parse_number(rest, 0))
47
47
+
case rest {
48
48
+
<<"255\n", pixels:bytes>> -> Ok(Image(width:, height:, pixels:))
49
49
+
_ -> Error(ParseError)
50
50
+
}
51
51
+
}
39
52
_ -> Error(ParseError)
40
53
}
41
54
}
42
55
43
43
-
fn pgm_height(
44
44
-
pgm: BitArray,
45
45
-
width: Int,
46
46
-
height: Int,
47
47
-
) -> Result(Image, ImageError) {
48
48
-
case pgm {
49
49
-
<<"0", rest:bits>> -> pgm_height(rest, width, height * 10 + 0)
50
50
-
<<"1", rest:bits>> -> pgm_height(rest, width, height * 10 + 1)
51
51
-
<<"2", rest:bits>> -> pgm_height(rest, width, height * 10 + 2)
52
52
-
<<"3", rest:bits>> -> pgm_height(rest, width, height * 10 + 3)
53
53
-
<<"4", rest:bits>> -> pgm_height(rest, width, height * 10 + 4)
54
54
-
<<"5", rest:bits>> -> pgm_height(rest, width, height * 10 + 5)
55
55
-
<<"6", rest:bits>> -> pgm_height(rest, width, height * 10 + 6)
56
56
-
<<"7", rest:bits>> -> pgm_height(rest, width, height * 10 + 7)
57
57
-
<<"8", rest:bits>> -> pgm_height(rest, width, height * 10 + 8)
58
58
-
<<"9", rest:bits>> -> pgm_height(rest, width, height * 10 + 9)
59
59
-
<<"\n255\n", pixels:bytes>> -> Ok(Image(width:, height:, pixels:))
60
60
-
56
56
+
fn parse_number(
57
57
+
data: BitArray,
58
58
+
acc: Int,
59
59
+
) -> Result(#(Int, BitArray), ImageError) {
60
60
+
case data {
61
61
+
<<"0", rest:bits>> -> parse_number(rest, acc * 10 + 0)
62
62
+
<<"1", rest:bits>> -> parse_number(rest, acc * 10 + 1)
63
63
+
<<"2", rest:bits>> -> parse_number(rest, acc * 10 + 2)
64
64
+
<<"3", rest:bits>> -> parse_number(rest, acc * 10 + 3)
65
65
+
<<"4", rest:bits>> -> parse_number(rest, acc * 10 + 4)
66
66
+
<<"5", rest:bits>> -> parse_number(rest, acc * 10 + 5)
67
67
+
<<"6", rest:bits>> -> parse_number(rest, acc * 10 + 6)
68
68
+
<<"7", rest:bits>> -> parse_number(rest, acc * 10 + 7)
69
69
+
<<"8", rest:bits>> -> parse_number(rest, acc * 10 + 8)
70
70
+
<<"9", rest:bits>> -> parse_number(rest, acc * 10 + 9)
71
71
+
<<" ", rest:bits>> | <<"\n", rest:bits>> -> Ok(#(acc, rest))
61
72
_ -> Error(ParseError)
62
73
}
63
74
}
64
75
65
65
-
pub fn dither_ign(image: Image) -> MonochromeImage {
76
76
+
pub fn dither_ign(image: Image) -> PrintableImage {
66
77
use pixel, row, column <- dither_map(image)
67
78
let ign =
68
79
float.modulo(
···
84
95
}
85
96
}
86
97
87
87
-
pub fn dither_bayer2x2(image: Image, bias: Int) -> MonochromeImage {
98
98
+
pub fn dither_bayer2x2(image: Image, bias: Int) -> PrintableImage {
88
99
dither_bayer(image, bias, bayer_2x2_matrix)
89
100
}
90
101
91
91
-
pub fn dither_bayer4x4(image: Image, bias: Int) -> MonochromeImage {
102
102
+
pub fn dither_bayer4x4(image: Image, bias: Int) -> PrintableImage {
92
103
dither_bayer(image, bias, bayer_4x4_matrix)
93
104
}
94
105
···
133
144
image: Image,
134
145
bias: Int,
135
146
matrix: fn(Int, Int) -> Int,
136
136
-
) -> MonochromeImage {
147
147
+
) -> PrintableImage {
137
148
use pixel, row, column <- dither_map(image)
138
149
case pixel > matrix(row, column) + bias {
139
150
True -> 0
···
141
152
}
142
153
}
143
154
144
144
-
fn dither_map(image: Image, with fun: fn(Int, Int, Int) -> Int) {
145
145
-
MonochromeImage(
146
146
-
width: image.width,
147
147
-
height: image.height,
148
148
-
pixels: dither_loop(image.pixels, image.width, 0, 0, 0, 0, <<>>, fun),
149
149
-
)
155
155
+
fn dither_map(image: Image, fun: fn(Int, Int, Int) -> Int) -> PrintableImage {
156
156
+
let pixels =
157
157
+
dither_loop(image.pixels, image.width, 0, 0, <<>>, fun)
158
158
+
|> pack(image.width)
159
159
+
PrintableImage(width: image.width, height: image.height, pixels:)
150
160
}
151
161
152
152
-
/// TODO: refactor this
153
162
fn dither_loop(
154
163
pixels: BitArray,
155
164
width: Int,
156
165
row: Int,
157
166
column: Int,
158
158
-
current_byte: Int,
159
159
-
bit_position: Int,
160
167
acc: BitArray,
161
168
fun: fn(Int, Int, Int) -> Int,
162
169
) -> BitArray {
163
170
case pixels {
164
171
<<pixel, rest:bits>> -> {
165
165
-
// Get dithered bit (0 or 1)
166
172
let bit = fun(pixel, row, column)
167
167
-
168
168
-
// Pack bit into current byte (MSB first: bit 7 = leftmost pixel)
169
169
-
let new_byte = case bit {
170
170
-
1 ->
171
171
-
int.bitwise_or(
172
172
-
current_byte,
173
173
-
int.bitwise_shift_left(1, 7 - bit_position),
173
173
+
let next_column = column + 1
174
174
+
case next_column == width {
175
175
+
True ->
176
176
+
dither_loop(rest, width, row + 1, 0, <<acc:bits, bit:size(1)>>, fun)
177
177
+
False ->
178
178
+
dither_loop(
179
179
+
rest,
180
180
+
width,
181
181
+
row,
182
182
+
next_column,
183
183
+
<<acc:bits, bit:size(1)>>,
184
184
+
fun,
174
185
)
175
175
-
_ -> current_byte
176
186
}
187
187
+
}
188
188
+
_ -> acc
189
189
+
}
190
190
+
}
177
191
178
178
-
let next_column = column + 1
179
179
-
let next_bit_position = bit_position + 1
192
192
+
pub fn pack(pixels, width) -> BitArray {
193
193
+
let padding = { 8 - width % 8 } % 8
194
194
+
pack_rows(pixels, width, padding, <<>>)
195
195
+
}
180
196
181
181
-
case next_column == width {
182
182
-
// End of row - flush current byte (with padding) and start new row
183
183
-
True -> {
184
184
-
let new_acc = <<acc:bits, new_byte>>
185
185
-
dither_loop(rest, width, row + 1, 0, 0, 0, new_acc, fun)
186
186
-
}
187
187
-
// Continue row
188
188
-
False -> {
189
189
-
case next_bit_position == 8 {
190
190
-
// Byte complete - flush and start new byte
191
191
-
True -> {
192
192
-
let new_acc = <<acc:bits, new_byte>>
193
193
-
dither_loop(rest, width, row, next_column, 0, 0, new_acc, fun)
194
194
-
}
195
195
-
// Continue filling current byte
196
196
-
False -> {
197
197
-
dither_loop(
198
198
-
rest,
199
199
-
width,
200
200
-
row,
201
201
-
next_column,
202
202
-
new_byte,
203
203
-
next_bit_position,
204
204
-
acc,
205
205
-
fun,
206
206
-
)
207
207
-
}
208
208
-
}
209
209
-
}
210
210
-
}
197
197
+
fn pack_rows(
198
198
+
pixels: BitArray,
199
199
+
width: Int,
200
200
+
padding: Int,
201
201
+
acc: BitArray,
202
202
+
) -> BitArray {
203
203
+
case pixels {
204
204
+
<<row:bits-size(width), rest:bits>> -> {
205
205
+
let padded_row = <<
206
206
+
row:bits-size(width),
207
207
+
0:size(padding),
208
208
+
>>
209
209
+
pack_rows(rest, width, padding, <<acc:bits, padded_row:bits>>)
211
210
}
212
212
-
// Done - flush any remaining bits
213
213
-
_ -> {
214
214
-
case bit_position > 0 {
215
215
-
True -> <<acc:bits, current_byte>>
216
216
-
False -> acc
217
217
-
}
218
218
-
}
211
211
+
_ -> acc
219
212
}
220
213
}