-module(escpos@document). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/escpos/document.gleam"). -export([styled/2, text/1, line/1, line_feed/1, new_line/0, image/1, cut/0, partial_cut/0, raw/1, bold/0, underline/0, double_strike/0, reverse/0, flip/0, upside_down/0, justify/1, font/1, text_size/1, text_width/1, text_height/1, upside_down_pass/1, render/1]). -export_type([command/0, modifier/0, a_s_t/0, state/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " A high-level API for building ESC/POS printer documents.\n" "\n" " Documents are built by composing commands and modifiers, then compiled\n" " into a binary command buffer that can be sent to a printer.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import escpos/document.{bold, cut, justify, line, styled, text, Center}\n" "\n" " document.render([\n" " styled([justify(Center), bold()], [\n" " line([text(\"Receipt\")]),\n" " ]),\n" " line([text(\"Item 1 ... $5.00\")]),\n" " cut(),\n" " ])\n" " ```\n" ). -opaque command() :: {text, binary()} | {line, list(command())} | {line_feed, integer()} | {cut, escpos@protocol:cut()} | {image, escpos@image:printable_image()} | {styled, gleam@set:set(modifier()), list(command())} | {raw, bitstring()}. -opaque modifier() :: bold | underline | double_strike | reverse | flip | upside_down | {text_size, integer()} | {text_width, integer()} | {text_height, integer()} | {justify, escpos@protocol:justify()} | {font, escpos@protocol:font()}. -type a_s_t() :: init | {do_write, binary()} | {do_line_feed, integer()} | {do_cut, escpos@protocol:cut()} | {do_image, escpos@image:printable_image()} | {do_raw, bitstring()} | {set_bold, boolean()} | {set_underline, boolean()} | {set_double_strike, boolean()} | {set_reverse, boolean()} | {set_flip, boolean()} | {set_upside_down, boolean()} | {set_text_size, integer(), integer()} | {set_justify, escpos@protocol:justify()} | {set_font, escpos@protocol:font()}. -type state() :: {state, boolean(), boolean(), boolean(), boolean(), boolean(), boolean(), boolean(), integer(), integer(), escpos@protocol:justify(), escpos@protocol:font()}. -file("src/escpos/document.gleam", 96). -spec default_state() -> state(). default_state() -> {state, true, false, false, false, false, false, false, 0, 0, left, font_a}. -file("src/escpos/document.gleam", 112). -spec ensure_new_line(state(), list(a_s_t())) -> {list(a_s_t()), state()}. ensure_new_line(State, Acc) -> case erlang:element(2, State) of true -> {Acc, State}; false -> {[{do_line_feed, 1} | Acc], {state, true, erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), erlang:element(12, State)}} end. -file("src/escpos/document.gleam", 129). ?DOC( " Applies style modifiers to a group of commands. Styles are automatically\n" " reverted after the nested commands.\n" ). -spec styled(list(modifier()), list(command())) -> command(). styled(Modifiers, Commands) -> {styled, gleam@set:from_list(Modifiers), Commands}. -file("src/escpos/document.gleam", 134). ?DOC(" Prints text without a trailing newline.\n"). -spec text(binary()) -> command(). text(T) -> {text, T}. -file("src/escpos/document.gleam", 149). ?DOC( " Groups commands into a single line, with a newline at the end.\n" " Useful for combining styled and unstyled text on one line.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " line([\n" " styled([bold()], [text(\"Total:\")]),\n" " text(\" $12.99\"),\n" " ])\n" " ```\n" ). -spec line(list(command())) -> command(). line(Commands) -> {line, Commands}. -file("src/escpos/document.gleam", 154). ?DOC(" Advances the paper by the specified number of lines.\n"). -spec line_feed(integer()) -> command(). line_feed(Lines) -> {line_feed, Lines}. -file("src/escpos/document.gleam", 159). ?DOC(" Advances the paper by one line.\n"). -spec new_line() -> command(). new_line() -> {line_feed, 1}. -file("src/escpos/document.gleam", 177). ?DOC( " Prints an image.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let assert Ok(pgm) = simplifile.read_bits(from: \"./lucy.pgm\")\n" " let assert Ok(img) = image.from_pgm(raw_pgm)\n" " let img = image.dither_bayer4x4(img, 0)\n" "\n" " document.render([\n" " image(img),\n" " cut(),\n" " ])\n" " ```\n" ). -spec image(escpos@image:printable_image()) -> command(). image(Image) -> {image, Image}. -file("src/escpos/document.gleam", 182). ?DOC(" Performs a full paper cut.\n"). -spec cut() -> command(). cut() -> {cut, full}. -file("src/escpos/document.gleam", 187). ?DOC(" Performs a partial paper cut, leaving a small portion connected.\n"). -spec partial_cut() -> command(). partial_cut() -> {cut, partial}. -file("src/escpos/document.gleam", 192). ?DOC(" Sends raw ESC/POS bytes to the printer.\n"). -spec raw(bitstring()) -> command(). raw(Bytes) -> {raw, Bytes}. -file("src/escpos/document.gleam", 197). ?DOC(" Bold text modifier.\n"). -spec bold() -> modifier(). bold() -> bold. -file("src/escpos/document.gleam", 202). ?DOC(" Underlined text modifier.\n"). -spec underline() -> modifier(). underline() -> underline. -file("src/escpos/document.gleam", 207). ?DOC(" Double-strike (heavier print) modifier.\n"). -spec double_strike() -> modifier(). double_strike() -> double_strike. -file("src/escpos/document.gleam", 212). ?DOC(" Reverse (white on black) text modifier.\n"). -spec reverse() -> modifier(). reverse() -> reverse. -file("src/escpos/document.gleam", 217). ?DOC(" Flips text 180 degrees.\n"). -spec flip() -> modifier(). flip() -> flip. -file("src/escpos/document.gleam", 222). ?DOC(" Prints text upside down (requires newline before and after).\n"). -spec upside_down() -> modifier(). upside_down() -> upside_down. -file("src/escpos/document.gleam", 227). ?DOC(" Sets text justification.\n"). -spec justify(escpos@protocol:justify()) -> modifier(). justify(J) -> {justify, J}. -file("src/escpos/document.gleam", 232). ?DOC(" Sets the font.\n"). -spec font(escpos@protocol:font()) -> modifier(). font(F) -> {font, F}. -file("src/escpos/document.gleam", 237). ?DOC(" Sets both text width and height scaling (0-7).\n"). -spec text_size(integer()) -> modifier(). text_size(Size) -> {text_size, Size}. -file("src/escpos/document.gleam", 242). ?DOC(" Sets text width scaling (0-7).\n"). -spec text_width(integer()) -> modifier(). text_width(Size) -> {text_width, Size}. -file("src/escpos/document.gleam", 247). ?DOC(" Sets text height scaling (0-7).\n"). -spec text_height(integer()) -> modifier(). text_height(Size) -> {text_height, Size}. -file("src/escpos/document.gleam", 257). -spec do_upside_down_pass(list(command()), list(command()), boolean()) -> list(command()). do_upside_down_pass(Commands, Acc, Upside_down) -> case Commands of [] -> Acc; [Cmd | Rest] -> case Cmd of {styled, Mods, Cmds} -> Styled_acc = case {gleam@set:contains(Mods, upside_down), Upside_down} of {true, false} -> do_upside_down_pass(Cmds, [], true); {false, true} -> do_upside_down_pass(Cmds, [], true); {_, _} -> _pipe = do_upside_down_pass(Cmds, [], Upside_down), lists:reverse(_pipe) end, do_upside_down_pass( Rest, [{styled, Mods, Styled_acc} | Acc], Upside_down ); {line, Cmds@1} -> Line_acc = case Upside_down of true -> do_upside_down_pass(Cmds@1, [], true); false -> _pipe@1 = do_upside_down_pass(Cmds@1, [], false), lists:reverse(_pipe@1) end, do_upside_down_pass( Rest, [{line, Line_acc} | Acc], Upside_down ); _ -> do_upside_down_pass(Rest, [Cmd | Acc], Upside_down) end end. -file("src/escpos/document.gleam", 252). ?DOC(false). -spec upside_down_pass(list(command())) -> list(command()). upside_down_pass(Commands) -> _pipe = do_upside_down_pass(Commands, [], false), lists:reverse(_pipe). -file("src/escpos/document.gleam", 396). -spec do_revert_styles(list(a_s_t()), list(a_s_t()), state()) -> {list(a_s_t()), state()}. do_revert_styles(Changes, Acc, State) -> case Changes of [] -> {Acc, State}; [Style | Rest] -> case Style of {set_justify, _} -> {Acc@1, State@1} = ensure_new_line(State, Acc), do_revert_styles(Rest, [Style | Acc@1], State@1); {set_upside_down, _} -> {Acc@1, State@1} = ensure_new_line(State, Acc), do_revert_styles(Rest, [Style | Acc@1], State@1); _ -> do_revert_styles(Rest, [Style | Acc], State) end end. -file("src/escpos/document.gleam", 414). -spec changed(boolean(), a_s_t()) -> gleam@option:option(a_s_t()). changed(Change, Ast) -> case Change of true -> {some, Ast}; false -> none end. -file("src/escpos/document.gleam", 361). -spec revert_styles(state(), state(), list(a_s_t())) -> {list(a_s_t()), state()}. revert_styles(State, Nested_state, Acc) -> Changes = begin _pipe = [changed( erlang:element(3, State) /= erlang:element(3, Nested_state), {set_bold, erlang:element(3, State)} ), changed( erlang:element(4, State) /= erlang:element(4, Nested_state), {set_underline, erlang:element(4, State)} ), changed( erlang:element(5, State) /= erlang:element(5, Nested_state), {set_double_strike, erlang:element(5, State)} ), changed( erlang:element(6, State) /= erlang:element(6, Nested_state), {set_reverse, erlang:element(6, State)} ), changed( erlang:element(7, State) /= erlang:element(7, Nested_state), {set_flip, erlang:element(7, State)} ), changed( erlang:element(8, State) /= erlang:element(8, Nested_state), {set_upside_down, erlang:element(8, State)} ), changed( (erlang:element(9, State) /= erlang:element(9, Nested_state)) orelse (erlang:element(10, State) /= erlang:element( 10, Nested_state )), {set_text_size, erlang:element(9, State), erlang:element(10, State)} ), changed( erlang:element(11, State) /= erlang:element(11, Nested_state), {set_justify, erlang:element(11, State)} ), changed( erlang:element(12, State) /= erlang:element(12, Nested_state), {set_font, erlang:element(12, State)} )], gleam@option:values(_pipe) end, do_revert_styles(Changes, Acc, State). -file("src/escpos/document.gleam", 421). -spec do_apply_modifiers(list(modifier()), list(a_s_t()), state()) -> {list(a_s_t()), state()}. do_apply_modifiers(Modifiers, Acc, State) -> case Modifiers of [] -> {Acc, State}; [Mod | Rest] -> case Mod of bold -> do_apply_modifiers( Rest, [{set_bold, true} | Acc], {state, erlang:element(2, State), true, erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), erlang:element(12, State)} ); double_strike -> do_apply_modifiers( Rest, [{set_double_strike, true} | Acc], {state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), true, erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), erlang:element(12, State)} ); underline -> do_apply_modifiers( Rest, [{set_underline, true} | Acc], {state, erlang:element(2, State), erlang:element(3, State), true, erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), erlang:element(12, State)} ); reverse -> do_apply_modifiers( Rest, [{set_reverse, true} | Acc], {state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), true, erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), erlang:element(12, State)} ); flip -> do_apply_modifiers( Rest, [{set_flip, true} | Acc], {state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), true, erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), erlang:element(12, State)} ); upside_down -> {Acc@1, State@1} = ensure_new_line(State, Acc), do_apply_modifiers( Rest, [{set_upside_down, true} | Acc@1], {state, erlang:element(2, State@1), erlang:element(3, State@1), erlang:element(4, State@1), erlang:element(5, State@1), erlang:element(6, State@1), erlang:element(7, State@1), true, erlang:element(9, State@1), erlang:element(10, State@1), erlang:element(11, State@1), erlang:element(12, State@1)} ); {text_size, Size} -> do_apply_modifiers( Rest, [{set_text_size, Size, Size} | Acc], {state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), Size, Size, erlang:element(11, State), erlang:element(12, State)} ); {text_width, Size@1} -> do_apply_modifiers( Rest, [{set_text_size, Size@1, erlang:element(10, State)} | Acc], {state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), Size@1, erlang:element(10, State), erlang:element(11, State), erlang:element(12, State)} ); {text_height, Size@2} -> do_apply_modifiers( Rest, [{set_text_size, erlang:element(9, State), Size@2} | Acc], {state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), Size@2, erlang:element(11, State), erlang:element(12, State)} ); {justify, J} -> {Acc@2, State@2} = ensure_new_line(State, Acc), do_apply_modifiers( Rest, [{set_justify, J} | Acc@2], {state, erlang:element(2, State@2), erlang:element(3, State@2), erlang:element(4, State@2), erlang:element(5, State@2), erlang:element(6, State@2), erlang:element(7, State@2), erlang:element(8, State@2), erlang:element(9, State@2), erlang:element(10, State@2), J, erlang:element(12, State@2)} ); {font, F} -> do_apply_modifiers( Rest, [{set_font, F} | Acc], {state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), F} ) end end. -file("src/escpos/document.gleam", 299). -spec do_build_ast(list(command()), list(a_s_t()), state()) -> list(a_s_t()). do_build_ast(Commands, Acc, State) -> case Commands of [] -> Acc; [Cmd | Rest] -> case Cmd of {styled, Modifiers, Commands@1} -> {Acc_with_mods, Nested_state} = do_apply_modifiers( gleam@set:to_list(Modifiers), Acc, State ), Acc_with_commands = do_build_ast( Commands@1, Acc_with_mods, Nested_state ), {Acc_with_reset_styles, Reset_state} = revert_styles( State, Nested_state, Acc_with_commands ), do_build_ast( Rest, Acc_with_reset_styles, {state, erlang:element(2, Reset_state), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), erlang:element(12, State)} ); {cut, C} -> New_acc = begin _pipe = gleam@list:prepend(Acc, {do_line_feed, 3}), gleam@list:prepend(_pipe, {do_cut, C}) end, do_build_ast( Rest, New_acc, {state, true, erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), erlang:element(12, State)} ); {line_feed, N} -> do_build_ast( Rest, [{do_line_feed, N} | Acc], {state, true, erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), erlang:element(12, State)} ); {text, X} -> do_build_ast( Rest, [{do_write, X} | Acc], {state, false, erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), erlang:element(12, State)} ); {line, Cmds} -> Acc_with_commands@1 = do_build_ast(Cmds, Acc, State), do_build_ast( Rest, [{do_line_feed, 1} | Acc_with_commands@1], {state, true, erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), erlang:element(12, State)} ); {image, I} -> {Acc@1, State@1} = ensure_new_line(State, Acc), do_build_ast( Rest, [{do_image, I} | Acc@1], {state, true, erlang:element(3, State@1), erlang:element(4, State@1), erlang:element(5, State@1), erlang:element(6, State@1), erlang:element(7, State@1), erlang:element(8, State@1), erlang:element(9, State@1), erlang:element(10, State@1), erlang:element(11, State@1), erlang:element(12, State@1)} ); {raw, B} -> do_build_ast( Rest, [{do_raw, B} | Acc], {state, false, erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), erlang:element(12, State)} ) end end. -file("src/escpos/document.gleam", 294). -spec build_ast(list(command())) -> list(a_s_t()). build_ast(Commands) -> _pipe = do_build_ast(Commands, [init], default_state()), lists:reverse(_pipe). -file("src/escpos/document.gleam", 505). -spec do_compile_ast(list(a_s_t()), bitstring()) -> bitstring(). do_compile_ast(Ast, Acc) -> case Ast of [] -> Acc; [Cmd | Rest] -> case Cmd of init -> do_compile_ast( Rest, gleam@bit_array:append(Acc, <<(27), "@"/utf8>>) ); {do_write, X} -> do_compile_ast( Rest, gleam@bit_array:append(Acc, gleam_stdlib:identity(X)) ); {do_image, I} -> do_compile_ast( Rest, begin _pipe = gleam@bit_array:append( Acc, escpos@protocol:image_to_graphics_buffer( escpos@image:pixels(I), escpos@image:width(I), escpos@image:height(I), monochrome, scale1x, scale1x, color1 ) ), gleam@bit_array:append( _pipe, escpos@protocol:print_graphics_buffer() ) end ); {do_line_feed, N} -> do_compile_ast( Rest, gleam@bit_array:append( Acc, escpos@protocol:line_feed(N) ) ); {do_cut, C} -> do_compile_ast( Rest, gleam@bit_array:append(Acc, escpos@protocol:cut(C)) ); {do_raw, B} -> do_compile_ast(Rest, gleam@bit_array:append(Acc, B)); {set_bold, On} -> do_compile_ast( Rest, gleam@bit_array:append(Acc, escpos@protocol:bold(On)) ); {set_underline, On@1} -> do_compile_ast( Rest, gleam@bit_array:append( Acc, escpos@protocol:underline(On@1) ) ); {set_double_strike, On@2} -> do_compile_ast( Rest, gleam@bit_array:append( Acc, escpos@protocol:double_strike(On@2) ) ); {set_reverse, On@3} -> do_compile_ast( Rest, gleam@bit_array:append( Acc, escpos@protocol:reverse(On@3) ) ); {set_flip, On@4} -> do_compile_ast( Rest, gleam@bit_array:append(Acc, escpos@protocol:flip(On@4)) ); {set_upside_down, On@5} -> do_compile_ast( Rest, gleam@bit_array:append( Acc, escpos@protocol:upside_down(On@5) ) ); {set_text_size, Width, Height} -> do_compile_ast( Rest, gleam@bit_array:append( Acc, escpos@protocol:character_size(Width, Height) ) ); {set_justify, J} -> do_compile_ast( Rest, gleam@bit_array:append(Acc, escpos@protocol:justify(J)) ); {set_font, F} -> do_compile_ast( Rest, gleam@bit_array:append(Acc, escpos@protocol:font(F)) ) end end. -file("src/escpos/document.gleam", 501). -spec compile_ast(list(a_s_t())) -> bitstring(). compile_ast(Ast) -> do_compile_ast(Ast, <<>>). -file("src/escpos/document.gleam", 120). ?DOC(" Compiles a list of commands into a binary command buffer ready for printing.\n"). -spec render(list(command())) -> escpos@printer:command_buffer(). render(Document) -> _pipe = upside_down_pass(Document), _pipe@1 = build_ast(_pipe), _pipe@2 = compile_ast(_pipe@1), {command_buffer, _pipe@2}.