supernote 0.1.0__py3-none-any.whl

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.
supernote/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 2020 jya
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
File without changes
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Copyright (c) 2020 jya
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ import argparse
18
+ import io
19
+ import os
20
+ import sys
21
+
22
+ from colour import Color
23
+
24
+ import supernote as sn
25
+ from supernote.converter import (
26
+ ImageConverter,
27
+ SvgConverter,
28
+ PdfConverter,
29
+ TextConverter,
30
+ )
31
+ from supernote.converter import VisibilityOverlay
32
+
33
+
34
+ def convert_all(converter, total, file_name, save_func, visibility_overlay):
35
+ basename, extension = os.path.splitext(file_name)
36
+ max_digits = len(str(total))
37
+ for i in range(total):
38
+ # append page number between filename and extension
39
+ numbered_filename = basename + "_" + str(i).zfill(max_digits) + extension
40
+ img = converter.convert(i, visibility_overlay)
41
+ save_func(img, numbered_filename)
42
+
43
+
44
+ def convert_and_concat_all(converter, total, file_name, save_func, separator):
45
+ data = []
46
+ for i in range(total):
47
+ data.append(converter.convert(i))
48
+ data = list(map(lambda x: "" if x is None else (x + "\n"), data))
49
+ if len(data) > 0:
50
+ alldata = ((separator + "\n") if separator else "").join(data)
51
+ save_func(alldata, file_name)
52
+ else:
53
+ print("no data")
54
+
55
+
56
+ def convert_to_png(args, notebook, palette):
57
+ converter = ImageConverter(notebook, palette=palette)
58
+ bg_visibility = (
59
+ VisibilityOverlay.INVISIBLE
60
+ if args.exclude_background
61
+ else VisibilityOverlay.DEFAULT
62
+ )
63
+ vo = sn.converter.build_visibility_overlay(background=bg_visibility)
64
+
65
+ def save(img, file_name):
66
+ img.save(file_name, format="PNG")
67
+
68
+ if args.all:
69
+ total = notebook.get_total_pages()
70
+ convert_all(converter, total, args.output, save, vo)
71
+ else:
72
+ img = converter.convert(args.number, visibility_overlay=vo)
73
+ save(img, args.output)
74
+
75
+
76
+ def convert_to_svg(args, notebook, palette):
77
+ converter = SvgConverter(notebook, palette=palette)
78
+ bg_visibility = (
79
+ VisibilityOverlay.INVISIBLE
80
+ if args.exclude_background
81
+ else VisibilityOverlay.DEFAULT
82
+ )
83
+ vo = sn.converter.build_visibility_overlay(background=bg_visibility)
84
+
85
+ def save(svg, file_name):
86
+ if svg is not None:
87
+ with open(file_name, "w") as f:
88
+ f.write(svg)
89
+ else:
90
+ print("no path data")
91
+
92
+ if args.all:
93
+ total = notebook.get_total_pages()
94
+ convert_all(converter, total, args.output, save, vo)
95
+ else:
96
+ svg = converter.convert(args.number, visibility_overlay=vo)
97
+ save(svg, args.output)
98
+
99
+
100
+ def convert_to_pdf(args, notebook, palette):
101
+ use_link = not args.no_link
102
+ use_keyword = args.add_keyword
103
+ converter = PdfConverter(notebook, palette=palette)
104
+
105
+ def save(data, file_name):
106
+ if data is not None:
107
+ with open(file_name, "wb") as f:
108
+ f.write(data)
109
+ else:
110
+ print("no data")
111
+
112
+ if args.all:
113
+ data = converter.convert(
114
+ -1, enable_link=use_link, enable_keyword=use_keyword
115
+ ) # minus value means converting all pages
116
+ save(data, args.output)
117
+ else:
118
+ data = converter.convert(
119
+ args.number, enable_link=use_link, enable_keyword=use_keyword
120
+ )
121
+ save(data, args.output)
122
+
123
+
124
+ def convert_to_txt(args, notebook, palette):
125
+ converter = TextConverter(notebook, palette=palette)
126
+
127
+ def save(data, file_name):
128
+ if data is not None:
129
+ with open(file_name, "w") as f:
130
+ f.write(data)
131
+ else:
132
+ print("no data")
133
+
134
+ if args.all:
135
+ total = notebook.get_total_pages()
136
+ convert_and_concat_all(
137
+ converter, total, args.output, save, args.text_page_separator
138
+ )
139
+ else:
140
+ data = converter.convert(args.number)
141
+ save(data, args.output)
142
+
143
+
144
+ def subcommand_convert(args):
145
+ notebook = sn.load_notebook(args.input, policy=args.policy)
146
+ palette = None
147
+ if args.color:
148
+ try:
149
+ colors = parse_color(args.color)
150
+ except ValueError as e:
151
+ print(e, file=sys.stderr)
152
+ sys.exit(1)
153
+ palette = sn.color.ColorPalette(sn.color.MODE_RGB, colors)
154
+ if args.type == "png":
155
+ convert_to_png(args, notebook, palette)
156
+ elif args.type == "svg":
157
+ convert_to_svg(args, notebook, palette)
158
+ elif args.type == "pdf":
159
+ convert_to_pdf(args, notebook, palette)
160
+ elif args.type == "txt":
161
+ convert_to_txt(args, notebook, palette)
162
+
163
+
164
+ def subcommand_analyze(args):
165
+ # show all metadata as JSON
166
+ with open(args.input, "rb") as f:
167
+ metadata = sn.parse_metadata(f, policy=args.policy)
168
+ print(metadata.to_json(indent=2))
169
+
170
+
171
+ def subcommand_merge(args):
172
+ num_input = len(args.input)
173
+ if num_input == 1: # reconstruct a note file
174
+ notebook = sn.load_notebook(args.input[0])
175
+ reconstructed_binary = sn.reconstruct(notebook)
176
+ with open(args.output, "wb") as f:
177
+ f.write(reconstructed_binary)
178
+ else: # merge multiple note files
179
+ with open(args.input[0], "rb") as f:
180
+ merged_binary = f.read()
181
+ for i in range(1, num_input):
182
+ stream = io.BytesIO(merged_binary)
183
+ merged_notebook = sn.load(stream)
184
+ next_notebook = sn.load_notebook(args.input[i])
185
+ merged_binary = sn.merge(merged_notebook, next_notebook)
186
+ with open(args.output, "wb") as f:
187
+ f.write(merged_binary)
188
+
189
+
190
+ def subcommand_reconstruct(args):
191
+ notebook = sn.load_notebook(args.input)
192
+ reconstructed_binary = sn.reconstruct(notebook)
193
+ with open(args.output, "wb") as f:
194
+ f.write(reconstructed_binary)
195
+
196
+
197
+ def parse_color(color_string):
198
+ colorcodes = color_string.split(",")
199
+ if len(colorcodes) != 4:
200
+ raise ValueError(f"few color codes, 4 colors are required: {color_string}")
201
+ black = int(Color(colorcodes[0]).hex_l[1:7], 16)
202
+ darkgray = int(Color(colorcodes[1]).hex_l[1:7], 16)
203
+ gray = int(Color(colorcodes[2]).hex_l[1:7], 16)
204
+ white = int(Color(colorcodes[3]).hex_l[1:7], 16)
205
+ return (black, darkgray, gray, white)
206
+
207
+
208
+ def main():
209
+ parser = argparse.ArgumentParser(
210
+ prog="supernote-tool", description="Unofficial python tool for Ratta Supernote"
211
+ )
212
+ parser.add_argument(
213
+ "--version",
214
+ help="show version information and exit",
215
+ action="version",
216
+ version=f"%(prog)s {sn.__version__}",
217
+ )
218
+ subparsers = parser.add_subparsers()
219
+
220
+ # 'analyze' subcommand
221
+ parser_analyze = subparsers.add_parser("analyze", help="analyze note file")
222
+ parser_analyze.add_argument("input", type=str, help="input note file")
223
+ parser_analyze.add_argument(
224
+ "--policy",
225
+ choices=["strict", "loose"],
226
+ default="strict",
227
+ help="select parser policy",
228
+ )
229
+ parser_analyze.set_defaults(handler=subcommand_analyze)
230
+
231
+ # 'convert' subcommand
232
+ parser_convert = subparsers.add_parser("convert", help="image conversion")
233
+ parser_convert.add_argument("input", type=str, help="input note file")
234
+ parser_convert.add_argument("output", type=str, help="output image file")
235
+ parser_convert.add_argument(
236
+ "-n", "--number", type=int, default=0, help="page number to be converted"
237
+ )
238
+ parser_convert.add_argument(
239
+ "-a", "--all", action="store_true", default=False, help="convert all pages"
240
+ )
241
+ parser_convert.add_argument(
242
+ "-c",
243
+ "--color",
244
+ type=str,
245
+ help="colorize note with comma separated color codes in order of black, darkgray, gray and white.",
246
+ )
247
+ parser_convert.add_argument(
248
+ "-t",
249
+ "--type",
250
+ choices=["png", "svg", "pdf", "txt"],
251
+ default="png",
252
+ help="select conversion file type",
253
+ )
254
+ parser_convert.add_argument(
255
+ "--exclude-background",
256
+ action="store_true",
257
+ default=False,
258
+ help="exclude background and make it transparent (PNG and SVG are supported)",
259
+ )
260
+ parser_convert.add_argument(
261
+ "--pdf-type",
262
+ choices=["original"],
263
+ default="original",
264
+ help="select PDF conversion type",
265
+ )
266
+ parser_convert.add_argument(
267
+ "--no-link", action="store_true", default=False, help="disable links in PDF"
268
+ )
269
+ parser_convert.add_argument(
270
+ "--add-keyword",
271
+ action="store_true",
272
+ default=False,
273
+ help="enable keywords in PDF",
274
+ )
275
+ parser_convert.add_argument(
276
+ "--text-page-separator",
277
+ type=str,
278
+ default="",
279
+ help="page separator string for text conversion",
280
+ )
281
+ parser_convert.add_argument(
282
+ "--policy",
283
+ choices=["strict", "loose"],
284
+ default="strict",
285
+ help="select parser policy",
286
+ )
287
+ parser_convert.set_defaults(handler=subcommand_convert)
288
+
289
+ # 'merge' subcommand
290
+ description = """
291
+ (EXPERIMENTAL FEATURE)
292
+ This command merge multiple note files to one.
293
+ Backup your input files to save your data because you might get a corrupted output file.
294
+ """
295
+ parser_merge = subparsers.add_parser(
296
+ "merge",
297
+ description=description,
298
+ help="merge multiple note files (EXPERIMENTAL FEATURE)",
299
+ )
300
+ parser_merge.add_argument("input", type=str, nargs="+", help="input note files")
301
+ parser_merge.add_argument("output", type=str, help="output note file")
302
+ parser_merge.set_defaults(handler=subcommand_merge)
303
+
304
+ # 'reconstruct' subcommand
305
+ description = """
306
+ (EXPERIMENTAL FEATURE)
307
+ This command disassemble and reconstruct a note file for debugging and testing.
308
+ Backup your input file to save your data because you might get a corrupted output file.
309
+ """
310
+ parser_reconstruct = subparsers.add_parser(
311
+ "reconstruct",
312
+ description=description,
313
+ help="reconstruct a note file (EXPERIMENTAL FEATURE)",
314
+ )
315
+ parser_reconstruct.add_argument("input", type=str, help="input note file")
316
+ parser_reconstruct.add_argument("output", type=str, help="output note file")
317
+ parser_reconstruct.set_defaults(handler=subcommand_reconstruct)
318
+
319
+ args = parser.parse_args()
320
+ if hasattr(args, "handler"):
321
+ args.handler(args)
322
+ else:
323
+ parser.print_help()
324
+
325
+
326
+ if __name__ == "__main__":
327
+ main()
supernote/color.py ADDED
@@ -0,0 +1,93 @@
1
+ # Copyright (c) 2020 jya
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Color classes."""
16
+
17
+ # color mode
18
+ MODE_GRAYSCALE = "grayscale"
19
+ MODE_RGB = "rgb"
20
+
21
+ # preset grayscale colors
22
+ BLACK = 0x00
23
+ DARK_GRAY = 0x9D
24
+ GRAY = 0xC9
25
+ WHITE = 0xFE
26
+ TRANSPARENT = 0xFF
27
+ DARK_GRAY_COMPAT = 0x30
28
+ GRAY_COMPAT = 0x50
29
+
30
+ # preset RGB colors
31
+ RGB_BLACK = 0x000000
32
+ RGB_DARK_GRAY = 0x9D9D9D
33
+ RGB_GRAY = 0xC9C9C9
34
+ RGB_WHITE = 0xFEFEFE
35
+ RGB_TRANSPARENT = 0xFFFFFF
36
+ RGB_DARK_GRAY_COMPAT = 0x303030
37
+ RGB_GRAY_COMPAT = 0x505050
38
+
39
+
40
+ def get_rgb(value: int) -> tuple[int, int, int]:
41
+ r = (value & 0xFF0000) >> 16
42
+ g = (value & 0x00FF00) >> 8
43
+ b = value & 0x0000FF
44
+ return (r, g, b)
45
+
46
+
47
+ def web_string(value: int, mode: str = MODE_RGB) -> str:
48
+ if mode == MODE_GRAYSCALE:
49
+ return "#" + (format(value & 0xFF, "02x") * 3)
50
+ else:
51
+ r, g, b = get_rgb(value)
52
+ return (
53
+ "#"
54
+ + format(r & 0xFF, "02x")
55
+ + format(g & 0xFF, "02x")
56
+ + format(b & 0xFF, "02x")
57
+ )
58
+
59
+
60
+ class ColorPalette:
61
+ def __init__(
62
+ self,
63
+ mode: str = MODE_GRAYSCALE,
64
+ colors: tuple[int, int, int, int] = (BLACK, DARK_GRAY, GRAY, WHITE),
65
+ compat_colors: tuple[int, int] = (DARK_GRAY_COMPAT, GRAY_COMPAT),
66
+ ) -> None:
67
+ if mode not in [MODE_GRAYSCALE, MODE_RGB]:
68
+ raise ValueError("mode must be MODE_GRAYSCALE or MODE_RGB")
69
+ if len(colors) != 4:
70
+ raise ValueError(
71
+ "colors must have 4 color values (black, darkgray, gray, white)"
72
+ )
73
+ self.mode = mode
74
+ self.black = colors[0]
75
+ self.darkgray = colors[1]
76
+ self.gray = colors[2]
77
+ self.white = colors[3]
78
+ if mode == MODE_GRAYSCALE:
79
+ self.transparent = TRANSPARENT
80
+ else:
81
+ self.transparent = RGB_TRANSPARENT
82
+ self.darkgray_compat = compat_colors[0]
83
+ self.gray_compat = compat_colors[1]
84
+
85
+
86
+ DEFAULT_COLORPALETTE = ColorPalette(
87
+ MODE_GRAYSCALE, (BLACK, DARK_GRAY, GRAY, WHITE), (DARK_GRAY_COMPAT, GRAY_COMPAT)
88
+ )
89
+ DEFAULT_RGB_COLORPALETTE = ColorPalette(
90
+ MODE_RGB,
91
+ (RGB_BLACK, RGB_DARK_GRAY, RGB_GRAY, RGB_WHITE),
92
+ (RGB_DARK_GRAY_COMPAT, RGB_GRAY_COMPAT),
93
+ )