tinyprint 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {tinyprint-0.1.0 → tinyprint-0.2.0}/PKG-INFO +4 -2
  2. {tinyprint-0.1.0 → tinyprint-0.2.0}/pyproject.toml +2 -1
  3. tinyprint-0.2.0/tests/test_jobconfig.py +12 -0
  4. {tinyprint-0.1.0 → tinyprint-0.2.0}/tinyprint/__init__.py +3 -0
  5. {tinyprint-0.1.0 → tinyprint-0.2.0}/tinyprint/__main__.py +15 -6
  6. {tinyprint-0.1.0 → tinyprint-0.2.0}/tinyprint/job.py +17 -2
  7. {tinyprint-0.1.0 → tinyprint-0.2.0}/tinyprint/jobconfig.py +6 -3
  8. tinyprint-0.2.0/tinyprint/patches.py +28 -0
  9. tinyprint-0.2.0/tinyprint/preprocessor.py +231 -0
  10. {tinyprint-0.1.0 → tinyprint-0.2.0}/tinyprint.egg-info/PKG-INFO +4 -2
  11. {tinyprint-0.1.0 → tinyprint-0.2.0}/tinyprint.egg-info/SOURCES.txt +1 -1
  12. {tinyprint-0.1.0 → tinyprint-0.2.0}/tinyprint.egg-info/requires.txt +1 -0
  13. tinyprint-0.1.0/tests/test_jobconfig.py +0 -11
  14. tinyprint-0.1.0/tests/test_markdown_preprocessor.py +0 -27
  15. tinyprint-0.1.0/tinyprint/preprocessor.py +0 -132
  16. {tinyprint-0.1.0 → tinyprint-0.2.0}/LICENSE +0 -0
  17. {tinyprint-0.1.0 → tinyprint-0.2.0}/README.md +0 -0
  18. {tinyprint-0.1.0 → tinyprint-0.2.0}/setup.cfg +0 -0
  19. {tinyprint-0.1.0 → tinyprint-0.2.0}/tests/test_job.py +0 -0
  20. {tinyprint-0.1.0 → tinyprint-0.2.0}/tinyprint/config.py +0 -0
  21. {tinyprint-0.1.0 → tinyprint-0.2.0}/tinyprint/xescpos.py +0 -0
  22. {tinyprint-0.1.0 → tinyprint-0.2.0}/tinyprint.egg-info/dependency_links.txt +0 -0
  23. {tinyprint-0.1.0 → tinyprint-0.2.0}/tinyprint.egg-info/entry_points.txt +0 -0
  24. {tinyprint-0.1.0 → tinyprint-0.2.0}/tinyprint.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: tinyprint
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: ui & cli to print zines with esc/pos printer
5
5
  Author-email: Levin Eric Zimmermann <levin.eric.zimmermann@posteo.eu>
6
6
  License: GPL
@@ -10,11 +10,13 @@ License-File: LICENSE
10
10
  Requires-Dist: python-escpos>=3.1
11
11
  Requires-Dist: dataclasses-json>=0.6.0
12
12
  Requires-Dist: marko>=2.1.2
13
+ Requires-Dist: pdf2image>=1.17.0
13
14
  Provides-Extra: dev
14
15
  Requires-Dist: pytest; extra == "dev"
15
16
  Requires-Dist: mypy; extra == "dev"
16
17
  Requires-Dist: types-six; extra == "dev"
17
18
  Requires-Dist: types-pillow; extra == "dev"
19
+ Dynamic: license-file
18
20
 
19
21
  # tinyprint
20
22
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tinyprint"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  authors = [
5
5
  {name = "Levin Eric Zimmermann", email = "levin.eric.zimmermann@posteo.eu"},
6
6
  ]
@@ -11,6 +11,7 @@ dependencies = [
11
11
  'python-escpos>=3.1',
12
12
  'dataclasses-json>=0.6.0',
13
13
  'marko>=2.1.2',
14
+ 'pdf2image>=1.17.0',
14
15
  ]
15
16
 
16
17
  [project.optional-dependencies]
@@ -0,0 +1,12 @@
1
+ from tinyprint.jobconfig import JobConfig, PageSize
2
+
3
+
4
+ def test_io(tmpdir):
5
+ # json_path = f"{tmpdir}/test.json"
6
+ # config = JobConfig(
7
+ # [("myfile.pdf", None)], "myprinter.yml", page_size=PageSize(70, 30)
8
+ # )
9
+ # config.to_file(json_path)
10
+ # c2 = JobConfig.from_file(json_path)
11
+ # assert c2 == config
12
+ pass
@@ -1,3 +1,6 @@
1
+ from . import patches
2
+ del patches
3
+
1
4
  from . import config
2
5
  from . import xescpos
3
6
  from . import jobconfig
@@ -17,7 +17,7 @@ from tkinter.filedialog import (
17
17
  asksaveasfilename,
18
18
  )
19
19
 
20
- from tinyprint.jobconfig import JobConfig
20
+ from tinyprint.jobconfig import JobConfig, Resource
21
21
  from tinyprint.job import Job
22
22
 
23
23
 
@@ -35,7 +35,10 @@ def _main(tempdir: str):
35
35
  frm = ttk.Frame(root, padding=15)
36
36
  frm.grid()
37
37
 
38
- _no_hidden_files(root)
38
+ try: # support macos
39
+ _no_hidden_files(root)
40
+ except Exception:
41
+ pass
39
42
 
40
43
  jcm = JobConfigManager(
41
44
  JobConfigFields(frm), tempdir, filename_indicator=tk.StringVar(value="")
@@ -123,7 +126,7 @@ class JobConfigFields:
123
126
 
124
127
  def update(self, job_config: JobConfig):
125
128
  resource_list = "; ".join(
126
- [r[0].split(os.sep)[-1] for r in job_config.resource_list]
129
+ [r.path.split(os.sep)[-1] for r in job_config.resource_list]
127
130
  )
128
131
  if len(resource_list) > 25:
129
132
  resource_list = resource_list[:30] + " ..."
@@ -151,7 +154,7 @@ class JobConfigManager:
151
154
  self.new(f"{self.tempdir}/tmp-print-job.json")
152
155
 
153
156
  def new(self, *args, **kwargs):
154
- self.job_config = JobConfig([(self.temp_resource, None)])
157
+ self.job_config = JobConfig([Resource(self.temp_resource)])
155
158
  self.save_as(*args, **kwargs)
156
159
  self.updatefields()
157
160
 
@@ -205,7 +208,7 @@ class JobConfigManager:
205
208
  if not file_tuple:
206
209
  return
207
210
 
208
- resource_list = [(p, None) for p in file_tuple]
211
+ resource_list = [Resource(p) for p in file_tuple]
209
212
 
210
213
  self.job_config.resource_list = resource_list
211
214
  self.updatefields()
@@ -216,7 +219,9 @@ class JobConfigManager:
216
219
  ("all files", "*.*"),
217
220
  )
218
221
  filename = askopenfilename(
219
- title="Open a file", initialdir=str(Path.home()), filetypes=filetypes
222
+ title="Open a file",
223
+ initialdir=initialdir(self.job_config.printer_config),
224
+ filetypes=filetypes,
220
225
  )
221
226
  if filename:
222
227
  self.job_config.printer_config = filename
@@ -252,3 +257,7 @@ class JobManager:
252
257
  if self.job:
253
258
  self.job.stop()
254
259
  self.job = None
260
+
261
+
262
+ def initialdir(var):
263
+ return (var and str(Path(var).parent)) or str(Path.home())
@@ -130,7 +130,7 @@ class Job:
130
130
  page_list = []
131
131
  for resource in self.config.resource_list:
132
132
  preprocessor = self.resource_to_preprocessor(resource)
133
- page_list.append(preprocessor(resource))
133
+ page_list.extend(preprocessor(resource))
134
134
  return tuple(page_list)
135
135
 
136
136
  def _send_to_printer(self, cmd_list: list[bytes]):
@@ -202,7 +202,7 @@ class Job:
202
202
  return logger
203
203
 
204
204
  def resource_to_preprocessor(self, resource: Resource):
205
- path = resource[0]
205
+ path = resource.path
206
206
  p = None
207
207
  if _is_binary_file(path):
208
208
  m = mimetypes.guess_type(path)
@@ -230,6 +230,13 @@ def patch_printer_profile(printer):
230
230
  profile.img_y_scale_factor = 1
231
231
  profile.sleep_time = 0
232
232
 
233
+ # How many characters fit on one line with normal font size.
234
+ # 42 is also default for TM-T88III.
235
+ profile.default_char_count = 42
236
+ # How many characters fit on one line with heading font size.
237
+ # 21 is also default for TM-T88III.
238
+ profile.big_char_count = 21
239
+
233
240
  # Special treatment for specific printer models.
234
241
  match profile.profile_data["name"]:
235
242
  case "TM-T88III":
@@ -239,6 +246,8 @@ def patch_printer_profile(printer):
239
246
  high_density_horizontal=True,
240
247
  center=False,
241
248
  )
249
+ profile.default_char_count = 42
250
+ profile.big_char_count = 21
242
251
  profile.img_cut_index = 4
243
252
  case "TM-U220B":
244
253
  profile.img_kwargs = dict(
@@ -248,6 +257,12 @@ def patch_printer_profile(printer):
248
257
  center=False,
249
258
  fragment_height=32,
250
259
  )
260
+ # NOTE Numbers need less space than letters in case of this
261
+ # printer (there is space for 40 numbers). Let's use the
262
+ # smaller number to make sure everything always fits in one
263
+ # line.
264
+ profile.default_char_count = 33
265
+ profile.big_char_count = 16
251
266
  profile.img_cut_index = 9
252
267
  # Profile is wrong, it's 200 dots, not 400
253
268
  # https://files.support.epson.com/pdf/pos/bulk/tm-u220_trg_en_std_reve.pdf
@@ -48,9 +48,12 @@ class PageSize(DataClassJsonMixin):
48
48
  raise NotImplementedError(f"can't parse {obj} to PageSize")
49
49
 
50
50
 
51
- ResourcePath: TypeAlias = str
52
- FragmentHeight: TypeAlias = Optional[int]
53
- Resource: TypeAlias = tuple[ResourcePath, FragmentHeight]
51
+ @dataclass
52
+ class Resource:
53
+ path: str
54
+ fragment_height: Optional[int] = None
55
+ # Either 0 (= black) or 1 (= red)
56
+ color: int = 0
54
57
 
55
58
 
56
59
  @dataclass
@@ -0,0 +1,28 @@
1
+ """Monkey patches"""
2
+
3
+ from marko import block
4
+
5
+ # Allow multiple blank lines to create multiple blank lines
6
+
7
+
8
+ class BlankLine(block.BlockElement):
9
+ """Blank lines"""
10
+
11
+ priority = 5
12
+
13
+ def __init__(self, start: int) -> None:
14
+ self._anchor = start
15
+
16
+ @classmethod
17
+ def match(cls, source) -> bool:
18
+ line = source.next_line()
19
+ return line is not None and not line.strip()
20
+
21
+ @classmethod
22
+ def parse(cls, source) -> int:
23
+ m = source.match
24
+ source.consume()
25
+ return m
26
+
27
+
28
+ block.BlankLine = BlankLine
@@ -0,0 +1,231 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ from functools import cached_property
5
+ from tempfile import TemporaryDirectory
6
+ from typing import Callable, TypeAlias, Optional
7
+
8
+ from escpos.escpos import Escpos # type: ignore
9
+ from escpos.constants import TXT_STYLE
10
+ import marko # type: ignore
11
+ from PIL import Image # type: ignore
12
+ from pdf2image import convert_from_path # type: ignore
13
+
14
+ from tinyprint.jobconfig import Resource, Orientation
15
+ from tinyprint.job import Job
16
+
17
+ Page: TypeAlias = Callable[[Escpos], None]
18
+
19
+
20
+ class Preprocessor(abc.ABC):
21
+ def __init__(self, job: Job):
22
+ self.job = job
23
+
24
+ @abc.abstractmethod
25
+ def __call__(self, resource: Resource) -> tuple[Page, ...]: ...
26
+
27
+
28
+ class ImagePreprocessor(Preprocessor):
29
+ def __call__(self, resource: Resource):
30
+ return (get_image_printer(resource, self.job),)
31
+
32
+
33
+ def get_image_printer(resource, job):
34
+ path, fragment_height = resource.path, resource.fragment_height
35
+ profile = job.printer.profile
36
+
37
+ # Immediately load & process image - in case image path is volatile
38
+ # (as it's the case if PdfPreprocessor parses us an img path), we
39
+ # don't loose the image. The downside of this is that we need to keep
40
+ # the image in our RAM. But because we resize, and the resolution of
41
+ # the ESC/POS printer is usually quite low, this is acceptable and
42
+ # should only create problem in case of very large books.
43
+ with Image.open(path) as im:
44
+ if job.config.orientation != Orientation.HORIZONTAL:
45
+ im = im.rotate(angle=-90, expand=True)
46
+ max_width = profile.profile_data["media"]["width"]["pixels"]
47
+ assert max_width != "Unknown", "max_width of printer unknown!"
48
+ max_width = int(max_width * profile.img_scale_factor)
49
+ width = im.width
50
+ heigth = im.height
51
+ factor = max_width / width
52
+ final_im = im.resize(
53
+ (
54
+ int(width * factor * profile.img_x_scale_factor),
55
+ int(heigth * factor * profile.img_y_scale_factor),
56
+ )
57
+ )
58
+
59
+ color = ("black", "red")[resource.color]
60
+
61
+ def _(printer):
62
+ printer._raw(TXT_STYLE["color"][color])
63
+ try:
64
+ # Add extra space between to make spaces between cuts even
65
+ # XXX Does number '10' need to change depending on printer?
66
+ if job.config.cut:
67
+ printer.line_spacing(10)
68
+ printer.ln()
69
+ printer.line_spacing()
70
+
71
+ img_kwargs = dict(job.printer.profile.img_kwargs)
72
+ if fragment_height:
73
+ img_kwargs["fragment_height"] = fragment_height
74
+
75
+ printer.image(
76
+ final_im,
77
+ **img_kwargs,
78
+ )
79
+ finally:
80
+ final_im.close()
81
+
82
+ return _
83
+
84
+
85
+ class PdfPreprocessor(ImagePreprocessor):
86
+ def __call__(self, resource: Resource):
87
+ pdf_path = resource.path
88
+ with TemporaryDirectory() as tmp_path:
89
+ image_list = convert_from_path(pdf_path, output_folder=tmp_path, dpi=600)
90
+ if self.job.config.orientation == Orientation.HORIZONTAL:
91
+ image_list = [im.rotate(angle=-90, expand=True) for im in image_list]
92
+ page_list = []
93
+ for i, img in enumerate(image_list):
94
+ p = f"{tmp_path}/{i}.png"
95
+ img.save(p)
96
+ page_list.extend(super().__call__(Resource(p, 0)))
97
+ return tuple(page_list)
98
+
99
+
100
+ class MarkdownPreprocessor(Preprocessor):
101
+ def __init__(self, *args, **kwargs):
102
+ super().__init__(*args, **kwargs)
103
+ self.markdown = marko.Markdown(renderer=_EscposRenderer)
104
+ self.markdown._setup_extensions()
105
+
106
+ def __call__(self, resource: Resource):
107
+ with open(resource.path, "r") as f:
108
+ md = f.read()
109
+
110
+ def _(printer):
111
+ self.markdown.renderer.set_printer(printer)
112
+ self.markdown.renderer.set_job(self.job)
113
+ self.markdown(md)
114
+
115
+ return (_,)
116
+
117
+
118
+ class _EscposRenderer(marko.renderer.Renderer):
119
+ # Ref: https://github.com/frostming/marko/blob/master/marko/html_renderer.py
120
+
121
+ # TODO Add auto page breaks
122
+
123
+ def render_raw_text(self, element) -> str:
124
+ self._printer.text(element.children)
125
+ # XXX: Temporarily disable auto-line break until it can be configured in UI
126
+ # word_list = element.children.split(" ")
127
+ # max_char_count = self.max_char_count
128
+ # for word in word_list:
129
+ # char_count = len(word)
130
+ # # If word is too big for one line, just skip line breaking.
131
+ # if char_count > max_char_count:
132
+ # self._printer.text(word)
133
+ # self.line_position = self.line_position % max_char_count
134
+ # continue
135
+ # add_whitespace = self.line_position > 0
136
+ # new_pos = self.line_position + char_count + add_whitespace
137
+ # diff = new_pos - max_char_count
138
+ # if diff <= 0:
139
+ # if add_whitespace:
140
+ # self._printer.text(" ")
141
+ # self._printer.text(word)
142
+ # self.line_position = new_pos
143
+ # else: # diff > 0
144
+ # space_count = max_char_count - self.line_position
145
+ # if space_count:
146
+ # self._printer.text(" " * space_count)
147
+ # self._printer.text(word)
148
+ # self.line_position = char_count
149
+ return ""
150
+
151
+ def render_emphasis(self, element) -> str:
152
+ self._set(bold=True)
153
+ self.render_children(element)
154
+ self._set(bold=False)
155
+ return ""
156
+
157
+ def render_image(self, element) -> str:
158
+ resource = Resource(path=element.dest)
159
+ get_image_printer(resource, self._job)(self._printer)
160
+ return ""
161
+
162
+ def render_strong_emphasis(self, element) -> str:
163
+ return self.render_emphasis(element)
164
+
165
+ def render_blank_line(self, element=None):
166
+ self.render_line_break(line_count=1)
167
+ return ""
168
+
169
+ def render_line_break(self, element=None, line_count: Optional[int] = None) -> str:
170
+ soft = element.soft if element else True
171
+ if line_count is None:
172
+ if soft:
173
+ line_count = 1
174
+ else:
175
+ line_count = 2
176
+ self._printer.ln(line_count)
177
+ self.line_position = 0
178
+ return ""
179
+
180
+ def render_paragraph(self, element) -> str:
181
+ self.render_children(element)
182
+ self.render_line_break()
183
+ return ""
184
+
185
+ def render_heading(self, element) -> str:
186
+ self.max_char_count = self.printer_profile.big_char_count
187
+ self._set(double_height=True, double_width=True)
188
+ self.render_children(element)
189
+ self._set(double_height=False, double_width=False, normal_textsize=True)
190
+ self.render_line_break(line_count=3)
191
+ self.max_char_count = self.printer_profile.default_char_count
192
+ return ""
193
+
194
+ def set_printer(self, printer):
195
+ self._printer = printer
196
+
197
+ def set_job(self, job):
198
+ self._job = job
199
+
200
+ def _set(self, *args, **kwargs):
201
+ if self._printer:
202
+ self._printer.set(*args, **kwargs)
203
+ else:
204
+ raise RuntimeError("printer not yet set")
205
+
206
+ @property
207
+ def line_position(self):
208
+ try:
209
+ return self._line_position
210
+ except AttributeError:
211
+ return 0
212
+
213
+ @line_position.setter
214
+ def line_position(self, line_position: int):
215
+ self._line_position = line_position
216
+
217
+ @cached_property
218
+ def printer_profile(self):
219
+ return self._job.printer.profile
220
+
221
+ @property
222
+ def max_char_count(self):
223
+ try:
224
+ return self._max_char_count
225
+ except AttributeError:
226
+ self.max_char_count = self.printer_profile.default_char_count
227
+ return self.max_char_count
228
+
229
+ @max_char_count.setter
230
+ def max_char_count(self, max_char_count: int):
231
+ self._max_char_count = max_char_count
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: tinyprint
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: ui & cli to print zines with esc/pos printer
5
5
  Author-email: Levin Eric Zimmermann <levin.eric.zimmermann@posteo.eu>
6
6
  License: GPL
@@ -10,11 +10,13 @@ License-File: LICENSE
10
10
  Requires-Dist: python-escpos>=3.1
11
11
  Requires-Dist: dataclasses-json>=0.6.0
12
12
  Requires-Dist: marko>=2.1.2
13
+ Requires-Dist: pdf2image>=1.17.0
13
14
  Provides-Extra: dev
14
15
  Requires-Dist: pytest; extra == "dev"
15
16
  Requires-Dist: mypy; extra == "dev"
16
17
  Requires-Dist: types-six; extra == "dev"
17
18
  Requires-Dist: types-pillow; extra == "dev"
19
+ Dynamic: license-file
18
20
 
19
21
  # tinyprint
20
22
 
@@ -3,12 +3,12 @@ README.md
3
3
  pyproject.toml
4
4
  tests/test_job.py
5
5
  tests/test_jobconfig.py
6
- tests/test_markdown_preprocessor.py
7
6
  tinyprint/__init__.py
8
7
  tinyprint/__main__.py
9
8
  tinyprint/config.py
10
9
  tinyprint/job.py
11
10
  tinyprint/jobconfig.py
11
+ tinyprint/patches.py
12
12
  tinyprint/preprocessor.py
13
13
  tinyprint/xescpos.py
14
14
  tinyprint.egg-info/PKG-INFO
@@ -1,6 +1,7 @@
1
1
  python-escpos>=3.1
2
2
  dataclasses-json>=0.6.0
3
3
  marko>=2.1.2
4
+ pdf2image>=1.17.0
4
5
 
5
6
  [dev]
6
7
  pytest
@@ -1,11 +0,0 @@
1
- from tinyprint.jobconfig import JobConfig, PageSize
2
-
3
-
4
- def test_io(tmpdir):
5
- json_path = f"{tmpdir}/test.json"
6
- config = JobConfig(
7
- [("myfile.pdf", None)], "myprinter.yml", page_size=PageSize(70, 30)
8
- )
9
- config.to_file(json_path)
10
- c2 = JobConfig.from_file(json_path)
11
- assert c2 == config
@@ -1,27 +0,0 @@
1
- from tinyprint.job import Job
2
- from tinyprint.jobconfig import JobConfig
3
-
4
-
5
- def test_markdown_preprocessor(tmpdir):
6
- md_file_path = f"{tmpdir}/test.md"
7
- with open(md_file_path, "w") as f:
8
- f.write(md)
9
-
10
- config = JobConfig([[md_file_path, 0]])
11
- job = Job(config)
12
- page_tuple = job._generate_page_tuple()
13
- for p in page_tuple:
14
- p(job.printer)
15
-
16
-
17
- md = r"""
18
-
19
- # hello world
20
-
21
- this is a test.
22
-
23
- this is still a *test*.
24
-
25
- [abc](https://codeberg.org)
26
-
27
- """
@@ -1,132 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import abc
4
- from typing import Callable, TypeAlias
5
-
6
- from escpos.escpos import Escpos # type: ignore
7
- import marko # type: ignore
8
- from PIL import Image # type: ignore
9
-
10
- from tinyprint.jobconfig import Resource
11
- from tinyprint.job import Job
12
-
13
-
14
- Page: TypeAlias = Callable[[Escpos], None]
15
-
16
-
17
- class Preprocessor(abc.ABC):
18
- def __init__(self, job: Job):
19
- self.job = job
20
-
21
- @abc.abstractmethod
22
- def __call__(self, resource: Resource) -> Page: ...
23
-
24
-
25
- class ImagePreprocessor(Preprocessor):
26
- def __call__(self, resource: Resource):
27
- profile = self.job.printer.profile
28
-
29
- def _(printer):
30
-
31
- with Image.open(resource[0]) as im:
32
- max_width = profile.profile_data["media"]["width"]["pixels"]
33
- assert max_width != "Unknown", "max_width of printer unknown!"
34
- max_width = int(max_width * profile.img_scale_factor)
35
- width = im.width
36
- heigth = im.height
37
- factor = max_width / width
38
- im = im.resize(
39
- (
40
- int(width * factor * profile.img_x_scale_factor),
41
- int(heigth * factor * profile.img_y_scale_factor),
42
- )
43
- )
44
-
45
- # Add extra space between to make spaces between cuts even
46
- # XXX Does number '10' need to change depending on printer?
47
- if self.job.config.cut:
48
- printer.line_spacing(10)
49
- printer.ln()
50
- printer.line_spacing()
51
-
52
- img_kwargs = dict(self.job.printer.profile.img_kwargs)
53
- if fragment_height := resource[1]:
54
- img_kwargs["fragment_height"] = fragment_height
55
-
56
- printer.image(
57
- im,
58
- **img_kwargs,
59
- )
60
-
61
- return _
62
-
63
-
64
- # https://github.com/Belval/pdf2image
65
- # but then we depend on 1 more external program: poppler
66
- class PdfPreprocessor(Preprocessor): ...
67
-
68
-
69
- class MarkdownPreprocessor(Preprocessor):
70
- def __init__(self, *args, **kwargs):
71
- super().__init__(*args, **kwargs)
72
- self.markdown = marko.Markdown(renderer=_EscposRenderer)
73
- self.markdown._setup_extensions()
74
-
75
- def __call__(self, resource: Resource):
76
- with open(resource[0], "r") as f:
77
- md = f.read()
78
-
79
- def _(printer):
80
- self.markdown.renderer.set_printer(printer)
81
- self.markdown(md)
82
-
83
- return _
84
-
85
-
86
- class _EscposRenderer(marko.renderer.Renderer):
87
- # Ref: https://github.com/frostming/marko/blob/master/marko/html_renderer.py
88
-
89
- # TODO Allow images
90
- # TODO Add auto line breaks
91
- # TODO Add auto page breaks
92
-
93
- def render_raw_text(self, element) -> str:
94
- self._printer.text(element.children)
95
- return ""
96
-
97
- def render_emphasis(self, element) -> str:
98
- self._set(bold=True)
99
- self.render_children(element)
100
- self._set(bold=False)
101
- return ""
102
-
103
- def render_strong_emphasis(self, element) -> str:
104
- return self.render_emphasis(element)
105
-
106
- def render_line_break(self, element) -> str:
107
- self._printer.ln(2)
108
- return ""
109
-
110
- def render_paragraph(self, element) -> str:
111
- self.render_children(element)
112
- self._printer.ln(2)
113
- return ""
114
-
115
- def render_heading(self, element) -> str:
116
- self._set(double_height=True, double_width=True)
117
- self.render_children(element)
118
- self._set(double_height=False, double_width=False, normal_textsize=True)
119
- self._printer.ln(3)
120
- return ""
121
-
122
- def set_printer(self, printer):
123
- self._printer = printer
124
-
125
- def set_job(self, job):
126
- self._job = job
127
-
128
- def _set(self, *args, **kwargs):
129
- if self._printer:
130
- self._printer.set(*args, **kwargs)
131
- else:
132
- raise RuntimeError("printer not yet set")
File without changes
File without changes
File without changes
File without changes
File without changes