comparison src_classic/helper-python.i @ 1:1d09e1dec1d9 upstream

ADD: PyMuPDF v1.26.4: the original sdist. It does not yet contain MuPDF. This normally will be downloaded when building PyMuPDF.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:37:51 +0200
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 1:1d09e1dec1d9
1 %pythoncode %{
2 # ------------------------------------------------------------------------
3 # Copyright 2020-2022, Harald Lieder, mailto:harald.lieder@outlook.com
4 # License: GNU AFFERO GPL 3.0, https://www.gnu.org/licenses/agpl-3.0.html
5 #
6 # Part of "PyMuPDF", a Python binding for "MuPDF" (http://mupdf.com), a
7 # lightweight PDF, XPS, and E-book viewer, renderer and toolkit which is
8 # maintained and developed by Artifex Software, Inc. https://artifex.com.
9 # ------------------------------------------------------------------------
10
11 # ------------------------------------------------------------------------------
12 # Various PDF Optional Content Flags
13 # ------------------------------------------------------------------------------
14 PDF_OC_ON = 0
15 PDF_OC_TOGGLE = 1
16 PDF_OC_OFF = 2
17
18 # ------------------------------------------------------------------------------
19 # link kinds and link flags
20 # ------------------------------------------------------------------------------
21 LINK_NONE = 0
22 LINK_GOTO = 1
23 LINK_URI = 2
24 LINK_LAUNCH = 3
25 LINK_NAMED = 4
26 LINK_GOTOR = 5
27 LINK_FLAG_L_VALID = 1
28 LINK_FLAG_T_VALID = 2
29 LINK_FLAG_R_VALID = 4
30 LINK_FLAG_B_VALID = 8
31 LINK_FLAG_FIT_H = 16
32 LINK_FLAG_FIT_V = 32
33 LINK_FLAG_R_IS_ZOOM = 64
34
35 # ------------------------------------------------------------------------------
36 # Text handling flags
37 # ------------------------------------------------------------------------------
38 TEXT_ALIGN_LEFT = 0
39 TEXT_ALIGN_CENTER = 1
40 TEXT_ALIGN_RIGHT = 2
41 TEXT_ALIGN_JUSTIFY = 3
42
43 TEXT_OUTPUT_TEXT = 0
44 TEXT_OUTPUT_HTML = 1
45 TEXT_OUTPUT_JSON = 2
46 TEXT_OUTPUT_XML = 3
47 TEXT_OUTPUT_XHTML = 4
48
49 TEXT_PRESERVE_LIGATURES = 1
50 TEXT_PRESERVE_WHITESPACE = 2
51 TEXT_PRESERVE_IMAGES = 4
52 TEXT_INHIBIT_SPACES = 8
53 TEXT_DEHYPHENATE = 16
54 TEXT_PRESERVE_SPANS = 32
55 TEXT_MEDIABOX_CLIP = 64
56 TEXT_CID_FOR_UNKNOWN_UNICODE = 128
57
58 TEXTFLAGS_WORDS = (0
59 | TEXT_PRESERVE_LIGATURES
60 | TEXT_PRESERVE_WHITESPACE
61 | TEXT_MEDIABOX_CLIP
62 | TEXT_CID_FOR_UNKNOWN_UNICODE
63 )
64
65 TEXTFLAGS_BLOCKS = (0
66 | TEXT_PRESERVE_LIGATURES
67 | TEXT_PRESERVE_WHITESPACE
68 | TEXT_MEDIABOX_CLIP
69 | TEXT_CID_FOR_UNKNOWN_UNICODE
70 )
71
72 TEXTFLAGS_DICT = (0
73 | TEXT_PRESERVE_LIGATURES
74 | TEXT_PRESERVE_WHITESPACE
75 | TEXT_MEDIABOX_CLIP
76 | TEXT_PRESERVE_IMAGES
77 | TEXT_CID_FOR_UNKNOWN_UNICODE
78 )
79
80 TEXTFLAGS_RAWDICT = TEXTFLAGS_DICT
81
82 TEXTFLAGS_SEARCH = (0
83 | TEXT_PRESERVE_LIGATURES
84 | TEXT_PRESERVE_WHITESPACE
85 | TEXT_MEDIABOX_CLIP
86 | TEXT_DEHYPHENATE
87 | TEXT_CID_FOR_UNKNOWN_UNICODE
88 )
89
90 TEXTFLAGS_HTML = (0
91 | TEXT_PRESERVE_LIGATURES
92 | TEXT_PRESERVE_WHITESPACE
93 | TEXT_MEDIABOX_CLIP
94 | TEXT_PRESERVE_IMAGES
95 | TEXT_CID_FOR_UNKNOWN_UNICODE
96 )
97
98 TEXTFLAGS_XHTML = (0
99 | TEXT_PRESERVE_LIGATURES
100 | TEXT_PRESERVE_WHITESPACE
101 | TEXT_MEDIABOX_CLIP
102 | TEXT_PRESERVE_IMAGES
103 | TEXT_CID_FOR_UNKNOWN_UNICODE
104 )
105
106 TEXTFLAGS_XML = (0
107 | TEXT_PRESERVE_LIGATURES
108 | TEXT_PRESERVE_WHITESPACE
109 | TEXT_MEDIABOX_CLIP
110 | TEXT_CID_FOR_UNKNOWN_UNICODE
111 )
112
113 TEXTFLAGS_TEXT = (0
114 | TEXT_PRESERVE_LIGATURES
115 | TEXT_PRESERVE_WHITESPACE
116 | TEXT_MEDIABOX_CLIP
117 | TEXT_CID_FOR_UNKNOWN_UNICODE
118 )
119
120 # ------------------------------------------------------------------------------
121 # Simple text encoding options
122 # ------------------------------------------------------------------------------
123 TEXT_ENCODING_LATIN = 0
124 TEXT_ENCODING_GREEK = 1
125 TEXT_ENCODING_CYRILLIC = 2
126 # ------------------------------------------------------------------------------
127 # Stamp annotation icon numbers
128 # ------------------------------------------------------------------------------
129 STAMP_Approved = 0
130 STAMP_AsIs = 1
131 STAMP_Confidential = 2
132 STAMP_Departmental = 3
133 STAMP_Experimental = 4
134 STAMP_Expired = 5
135 STAMP_Final = 6
136 STAMP_ForComment = 7
137 STAMP_ForPublicRelease = 8
138 STAMP_NotApproved = 9
139 STAMP_NotForPublicRelease = 10
140 STAMP_Sold = 11
141 STAMP_TopSecret = 12
142 STAMP_Draft = 13
143
144 # ------------------------------------------------------------------------------
145 # Base 14 font names and dictionary
146 # ------------------------------------------------------------------------------
147 Base14_fontnames = (
148 "Courier",
149 "Courier-Oblique",
150 "Courier-Bold",
151 "Courier-BoldOblique",
152 "Helvetica",
153 "Helvetica-Oblique",
154 "Helvetica-Bold",
155 "Helvetica-BoldOblique",
156 "Times-Roman",
157 "Times-Italic",
158 "Times-Bold",
159 "Times-BoldItalic",
160 "Symbol",
161 "ZapfDingbats",
162 )
163
164 Base14_fontdict = {}
165 for f in Base14_fontnames:
166 Base14_fontdict[f.lower()] = f
167 del f
168 Base14_fontdict["helv"] = "Helvetica"
169 Base14_fontdict["heit"] = "Helvetica-Oblique"
170 Base14_fontdict["hebo"] = "Helvetica-Bold"
171 Base14_fontdict["hebi"] = "Helvetica-BoldOblique"
172 Base14_fontdict["cour"] = "Courier"
173 Base14_fontdict["coit"] = "Courier-Oblique"
174 Base14_fontdict["cobo"] = "Courier-Bold"
175 Base14_fontdict["cobi"] = "Courier-BoldOblique"
176 Base14_fontdict["tiro"] = "Times-Roman"
177 Base14_fontdict["tibo"] = "Times-Bold"
178 Base14_fontdict["tiit"] = "Times-Italic"
179 Base14_fontdict["tibi"] = "Times-BoldItalic"
180 Base14_fontdict["symb"] = "Symbol"
181 Base14_fontdict["zadb"] = "ZapfDingbats"
182
183 annot_skel = {
184 "goto1": "<</A<</S/GoTo/D[%i 0 R/XYZ %g %g %g]>>/Rect[%s]/BS<</W 0>>/Subtype/Link>>",
185 "goto2": "<</A<</S/GoTo/D%s>>/Rect[%s]/BS<</W 0>>/Subtype/Link>>",
186 "gotor1": "<</A<</S/GoToR/D[%i /XYZ %g %g %g]/F<</F(%s)/UF(%s)/Type/Filespec>>>>/Rect[%s]/BS<</W 0>>/Subtype/Link>>",
187 "gotor2": "<</A<</S/GoToR/D%s/F(%s)>>/Rect[%s]/BS<</W 0>>/Subtype/Link>>",
188 "launch": "<</A<</S/Launch/F<</F(%s)/UF(%s)/Type/Filespec>>>>/Rect[%s]/BS<</W 0>>/Subtype/Link>>",
189 "uri": "<</A<</S/URI/URI(%s)>>/Rect[%s]/BS<</W 0>>/Subtype/Link>>",
190 "named": "<</A<</S/Named/N/%s/Type/Action>>/Rect[%s]/BS<</W 0>>/Subtype/Link>>",
191 }
192
193 class FileDataError(RuntimeError):
194 """Raised for documents with file structure issues."""
195 pass
196
197 class FileNotFoundError(RuntimeError):
198 """Raised if file does not exist."""
199 pass
200
201 class EmptyFileError(FileDataError):
202 """Raised when creating documents from zero-length data."""
203 pass
204
205 # propagate exception class to C-level code
206 _set_FileDataError(FileDataError)
207
208 def css_for_pymupdf_font(
209 fontcode: str, *, CSS: OptStr = None, archive: AnyType = None, name: OptStr = None
210 ) -> str:
211 """Create @font-face items for the given fontcode of pymupdf-fonts.
212
213 Adds @font-face support for fonts contained in package pymupdf-fonts.
214
215 Creates a CSS font-family for all fonts starting with string 'fontcode'.
216
217 Note:
218 The font naming convention in package pymupdf-fonts is "fontcode<sf>",
219 where the suffix "sf" is either empty or one of "it", "bo" or "bi".
220 These suffixes thus represent the regular, italic, bold or bold-italic
221 variants of a font. For example, font code "notos" refers to fonts
222 "notos" - "Noto Sans Regular"
223 "notosit" - "Noto Sans Italic"
224 "notosbo" - "Noto Sans Bold"
225 "notosbi" - "Noto Sans Bold Italic"
226
227 This function creates four CSS @font-face definitions and collectively
228 assigns the font-family name "notos" to them (or the "name" value).
229
230 All fitting font buffers of the pymupdf-fonts package are placed / added
231 to the archive provided as parameter.
232 To use the font in fitz.Story, execute 'set_font(fontcode)'. The correct
233 font weight (bold) or style (italic) will automatically be selected.
234 Expects and returns the CSS source, with the new CSS definitions appended.
235
236 Args:
237 fontcode: (str) font code for naming the font variants to include.
238 E.g. "fig" adds notos, notosi, notosb, notosbi fonts.
239 A maximum of 4 font variants is accepted.
240 CSS: (str) CSS string to add @font-face definitions to.
241 archive: (Archive, mandatory) where to place the font buffers.
242 name: (str) use this as family-name instead of 'fontcode'.
243 Returns:
244 Modified CSS, with appended @font-face statements for each font variant
245 of fontcode.
246 Fontbuffers associated with "fontcode" will be added to 'archive'.
247 """
248 # @font-face template string
249 CSSFONT = "\n@font-face {font-family: %s; src: url(%s);%s%s}\n"
250
251 if not type(archive) is Archive:
252 raise ValueError("'archive' must be an Archive")
253 if CSS == None:
254 CSS = ""
255
256 # select font codes starting with the pass-in string
257 font_keys = [k for k in fitz_fontdescriptors.keys() if k.startswith(fontcode)]
258 if font_keys == []:
259 raise ValueError(f"No font code '{fontcode}' found in pymupdf-fonts.")
260 if len(font_keys) > 4:
261 raise ValueError("fontcode too short")
262 if name == None: # use this name for font-family
263 name = fontcode
264
265 for fkey in font_keys:
266 font = fitz_fontdescriptors[fkey]
267 bold = font["bold"] # determine font property
268 italic = font["italic"] # determine font property
269 fbuff = font["loader"]() # load the fontbuffer
270 archive.add(fbuff, fkey) # update the archive
271 bold_text = "font-weight: bold;" if bold else ""
272 italic_text = "font-style: italic;" if italic else ""
273 CSS += CSSFONT % (name, fkey, bold_text, italic_text)
274 return CSS
275
276
277 def get_text_length(text: str, fontname: str ="helv", fontsize: float =11, encoding: int =0) -> float:
278 """Calculate length of a string for a built-in font.
279
280 Args:
281 fontname: name of the font.
282 fontsize: font size points.
283 encoding: encoding to use, 0=Latin (default), 1=Greek, 2=Cyrillic.
284 Returns:
285 (float) length of text.
286 """
287 fontname = fontname.lower()
288 basename = Base14_fontdict.get(fontname, None)
289
290 glyphs = None
291 if basename == "Symbol":
292 glyphs = symbol_glyphs
293 if basename == "ZapfDingbats":
294 glyphs = zapf_glyphs
295 if glyphs is not None:
296 w = sum([glyphs[ord(c)][1] if ord(c) < 256 else glyphs[183][1] for c in text])
297 return w * fontsize
298
299 if fontname in Base14_fontdict.keys():
300 return util_measure_string(
301 text, Base14_fontdict[fontname], fontsize, encoding
302 )
303
304 if fontname in (
305 "china-t",
306 "china-s",
307 "china-ts",
308 "china-ss",
309 "japan",
310 "japan-s",
311 "korea",
312 "korea-s",
313 ):
314 return len(text) * fontsize
315
316 raise ValueError("Font '%s' is unsupported" % fontname)
317
318
319 # ------------------------------------------------------------------------------
320 # Glyph list for the built-in font 'ZapfDingbats'
321 # ------------------------------------------------------------------------------
322 zapf_glyphs = (
323 (183, 0.788),
324 (183, 0.788),
325 (183, 0.788),
326 (183, 0.788),
327 (183, 0.788),
328 (183, 0.788),
329 (183, 0.788),
330 (183, 0.788),
331 (183, 0.788),
332 (183, 0.788),
333 (183, 0.788),
334 (183, 0.788),
335 (183, 0.788),
336 (183, 0.788),
337 (183, 0.788),
338 (183, 0.788),
339 (183, 0.788),
340 (183, 0.788),
341 (183, 0.788),
342 (183, 0.788),
343 (183, 0.788),
344 (183, 0.788),
345 (183, 0.788),
346 (183, 0.788),
347 (183, 0.788),
348 (183, 0.788),
349 (183, 0.788),
350 (183, 0.788),
351 (183, 0.788),
352 (183, 0.788),
353 (183, 0.788),
354 (183, 0.788),
355 (32, 0.278),
356 (33, 0.974),
357 (34, 0.961),
358 (35, 0.974),
359 (36, 0.98),
360 (37, 0.719),
361 (38, 0.789),
362 (39, 0.79),
363 (40, 0.791),
364 (41, 0.69),
365 (42, 0.96),
366 (43, 0.939),
367 (44, 0.549),
368 (45, 0.855),
369 (46, 0.911),
370 (47, 0.933),
371 (48, 0.911),
372 (49, 0.945),
373 (50, 0.974),
374 (51, 0.755),
375 (52, 0.846),
376 (53, 0.762),
377 (54, 0.761),
378 (55, 0.571),
379 (56, 0.677),
380 (57, 0.763),
381 (58, 0.76),
382 (59, 0.759),
383 (60, 0.754),
384 (61, 0.494),
385 (62, 0.552),
386 (63, 0.537),
387 (64, 0.577),
388 (65, 0.692),
389 (66, 0.786),
390 (67, 0.788),
391 (68, 0.788),
392 (69, 0.79),
393 (70, 0.793),
394 (71, 0.794),
395 (72, 0.816),
396 (73, 0.823),
397 (74, 0.789),
398 (75, 0.841),
399 (76, 0.823),
400 (77, 0.833),
401 (78, 0.816),
402 (79, 0.831),
403 (80, 0.923),
404 (81, 0.744),
405 (82, 0.723),
406 (83, 0.749),
407 (84, 0.79),
408 (85, 0.792),
409 (86, 0.695),
410 (87, 0.776),
411 (88, 0.768),
412 (89, 0.792),
413 (90, 0.759),
414 (91, 0.707),
415 (92, 0.708),
416 (93, 0.682),
417 (94, 0.701),
418 (95, 0.826),
419 (96, 0.815),
420 (97, 0.789),
421 (98, 0.789),
422 (99, 0.707),
423 (100, 0.687),
424 (101, 0.696),
425 (102, 0.689),
426 (103, 0.786),
427 (104, 0.787),
428 (105, 0.713),
429 (106, 0.791),
430 (107, 0.785),
431 (108, 0.791),
432 (109, 0.873),
433 (110, 0.761),
434 (111, 0.762),
435 (112, 0.762),
436 (113, 0.759),
437 (114, 0.759),
438 (115, 0.892),
439 (116, 0.892),
440 (117, 0.788),
441 (118, 0.784),
442 (119, 0.438),
443 (120, 0.138),
444 (121, 0.277),
445 (122, 0.415),
446 (123, 0.392),
447 (124, 0.392),
448 (125, 0.668),
449 (126, 0.668),
450 (183, 0.788),
451 (183, 0.788),
452 (183, 0.788),
453 (183, 0.788),
454 (183, 0.788),
455 (183, 0.788),
456 (183, 0.788),
457 (183, 0.788),
458 (183, 0.788),
459 (183, 0.788),
460 (183, 0.788),
461 (183, 0.788),
462 (183, 0.788),
463 (183, 0.788),
464 (183, 0.788),
465 (183, 0.788),
466 (183, 0.788),
467 (183, 0.788),
468 (183, 0.788),
469 (183, 0.788),
470 (183, 0.788),
471 (183, 0.788),
472 (183, 0.788),
473 (183, 0.788),
474 (183, 0.788),
475 (183, 0.788),
476 (183, 0.788),
477 (183, 0.788),
478 (183, 0.788),
479 (183, 0.788),
480 (183, 0.788),
481 (183, 0.788),
482 (183, 0.788),
483 (183, 0.788),
484 (161, 0.732),
485 (162, 0.544),
486 (163, 0.544),
487 (164, 0.91),
488 (165, 0.667),
489 (166, 0.76),
490 (167, 0.76),
491 (168, 0.776),
492 (169, 0.595),
493 (170, 0.694),
494 (171, 0.626),
495 (172, 0.788),
496 (173, 0.788),
497 (174, 0.788),
498 (175, 0.788),
499 (176, 0.788),
500 (177, 0.788),
501 (178, 0.788),
502 (179, 0.788),
503 (180, 0.788),
504 (181, 0.788),
505 (182, 0.788),
506 (183, 0.788),
507 (184, 0.788),
508 (185, 0.788),
509 (186, 0.788),
510 (187, 0.788),
511 (188, 0.788),
512 (189, 0.788),
513 (190, 0.788),
514 (191, 0.788),
515 (192, 0.788),
516 (193, 0.788),
517 (194, 0.788),
518 (195, 0.788),
519 (196, 0.788),
520 (197, 0.788),
521 (198, 0.788),
522 (199, 0.788),
523 (200, 0.788),
524 (201, 0.788),
525 (202, 0.788),
526 (203, 0.788),
527 (204, 0.788),
528 (205, 0.788),
529 (206, 0.788),
530 (207, 0.788),
531 (208, 0.788),
532 (209, 0.788),
533 (210, 0.788),
534 (211, 0.788),
535 (212, 0.894),
536 (213, 0.838),
537 (214, 1.016),
538 (215, 0.458),
539 (216, 0.748),
540 (217, 0.924),
541 (218, 0.748),
542 (219, 0.918),
543 (220, 0.927),
544 (221, 0.928),
545 (222, 0.928),
546 (223, 0.834),
547 (224, 0.873),
548 (225, 0.828),
549 (226, 0.924),
550 (227, 0.924),
551 (228, 0.917),
552 (229, 0.93),
553 (230, 0.931),
554 (231, 0.463),
555 (232, 0.883),
556 (233, 0.836),
557 (234, 0.836),
558 (235, 0.867),
559 (236, 0.867),
560 (237, 0.696),
561 (238, 0.696),
562 (239, 0.874),
563 (183, 0.788),
564 (241, 0.874),
565 (242, 0.76),
566 (243, 0.946),
567 (244, 0.771),
568 (245, 0.865),
569 (246, 0.771),
570 (247, 0.888),
571 (248, 0.967),
572 (249, 0.888),
573 (250, 0.831),
574 (251, 0.873),
575 (252, 0.927),
576 (253, 0.97),
577 (183, 0.788),
578 (183, 0.788),
579 )
580
581 # ------------------------------------------------------------------------------
582 # Glyph list for the built-in font 'Symbol'
583 # ------------------------------------------------------------------------------
584 symbol_glyphs = (
585 (183, 0.46),
586 (183, 0.46),
587 (183, 0.46),
588 (183, 0.46),
589 (183, 0.46),
590 (183, 0.46),
591 (183, 0.46),
592 (183, 0.46),
593 (183, 0.46),
594 (183, 0.46),
595 (183, 0.46),
596 (183, 0.46),
597 (183, 0.46),
598 (183, 0.46),
599 (183, 0.46),
600 (183, 0.46),
601 (183, 0.46),
602 (183, 0.46),
603 (183, 0.46),
604 (183, 0.46),
605 (183, 0.46),
606 (183, 0.46),
607 (183, 0.46),
608 (183, 0.46),
609 (183, 0.46),
610 (183, 0.46),
611 (183, 0.46),
612 (183, 0.46),
613 (183, 0.46),
614 (183, 0.46),
615 (183, 0.46),
616 (183, 0.46),
617 (32, 0.25),
618 (33, 0.333),
619 (34, 0.713),
620 (35, 0.5),
621 (36, 0.549),
622 (37, 0.833),
623 (38, 0.778),
624 (39, 0.439),
625 (40, 0.333),
626 (41, 0.333),
627 (42, 0.5),
628 (43, 0.549),
629 (44, 0.25),
630 (45, 0.549),
631 (46, 0.25),
632 (47, 0.278),
633 (48, 0.5),
634 (49, 0.5),
635 (50, 0.5),
636 (51, 0.5),
637 (52, 0.5),
638 (53, 0.5),
639 (54, 0.5),
640 (55, 0.5),
641 (56, 0.5),
642 (57, 0.5),
643 (58, 0.278),
644 (59, 0.278),
645 (60, 0.549),
646 (61, 0.549),
647 (62, 0.549),
648 (63, 0.444),
649 (64, 0.549),
650 (65, 0.722),
651 (66, 0.667),
652 (67, 0.722),
653 (68, 0.612),
654 (69, 0.611),
655 (70, 0.763),
656 (71, 0.603),
657 (72, 0.722),
658 (73, 0.333),
659 (74, 0.631),
660 (75, 0.722),
661 (76, 0.686),
662 (77, 0.889),
663 (78, 0.722),
664 (79, 0.722),
665 (80, 0.768),
666 (81, 0.741),
667 (82, 0.556),
668 (83, 0.592),
669 (84, 0.611),
670 (85, 0.69),
671 (86, 0.439),
672 (87, 0.768),
673 (88, 0.645),
674 (89, 0.795),
675 (90, 0.611),
676 (91, 0.333),
677 (92, 0.863),
678 (93, 0.333),
679 (94, 0.658),
680 (95, 0.5),
681 (96, 0.5),
682 (97, 0.631),
683 (98, 0.549),
684 (99, 0.549),
685 (100, 0.494),
686 (101, 0.439),
687 (102, 0.521),
688 (103, 0.411),
689 (104, 0.603),
690 (105, 0.329),
691 (106, 0.603),
692 (107, 0.549),
693 (108, 0.549),
694 (109, 0.576),
695 (110, 0.521),
696 (111, 0.549),
697 (112, 0.549),
698 (113, 0.521),
699 (114, 0.549),
700 (115, 0.603),
701 (116, 0.439),
702 (117, 0.576),
703 (118, 0.713),
704 (119, 0.686),
705 (120, 0.493),
706 (121, 0.686),
707 (122, 0.494),
708 (123, 0.48),
709 (124, 0.2),
710 (125, 0.48),
711 (126, 0.549),
712 (183, 0.46),
713 (183, 0.46),
714 (183, 0.46),
715 (183, 0.46),
716 (183, 0.46),
717 (183, 0.46),
718 (183, 0.46),
719 (183, 0.46),
720 (183, 0.46),
721 (183, 0.46),
722 (183, 0.46),
723 (183, 0.46),
724 (183, 0.46),
725 (183, 0.46),
726 (183, 0.46),
727 (183, 0.46),
728 (183, 0.46),
729 (183, 0.46),
730 (183, 0.46),
731 (183, 0.46),
732 (183, 0.46),
733 (183, 0.46),
734 (183, 0.46),
735 (183, 0.46),
736 (183, 0.46),
737 (183, 0.46),
738 (183, 0.46),
739 (183, 0.46),
740 (183, 0.46),
741 (183, 0.46),
742 (183, 0.46),
743 (183, 0.46),
744 (183, 0.46),
745 (160, 0.25),
746 (161, 0.62),
747 (162, 0.247),
748 (163, 0.549),
749 (164, 0.167),
750 (165, 0.713),
751 (166, 0.5),
752 (167, 0.753),
753 (168, 0.753),
754 (169, 0.753),
755 (170, 0.753),
756 (171, 1.042),
757 (172, 0.713),
758 (173, 0.603),
759 (174, 0.987),
760 (175, 0.603),
761 (176, 0.4),
762 (177, 0.549),
763 (178, 0.411),
764 (179, 0.549),
765 (180, 0.549),
766 (181, 0.576),
767 (182, 0.494),
768 (183, 0.46),
769 (184, 0.549),
770 (185, 0.549),
771 (186, 0.549),
772 (187, 0.549),
773 (188, 1),
774 (189, 0.603),
775 (190, 1),
776 (191, 0.658),
777 (192, 0.823),
778 (193, 0.686),
779 (194, 0.795),
780 (195, 0.987),
781 (196, 0.768),
782 (197, 0.768),
783 (198, 0.823),
784 (199, 0.768),
785 (200, 0.768),
786 (201, 0.713),
787 (202, 0.713),
788 (203, 0.713),
789 (204, 0.713),
790 (205, 0.713),
791 (206, 0.713),
792 (207, 0.713),
793 (208, 0.768),
794 (209, 0.713),
795 (210, 0.79),
796 (211, 0.79),
797 (212, 0.89),
798 (213, 0.823),
799 (214, 0.549),
800 (215, 0.549),
801 (216, 0.713),
802 (217, 0.603),
803 (218, 0.603),
804 (219, 1.042),
805 (220, 0.987),
806 (221, 0.603),
807 (222, 0.987),
808 (223, 0.603),
809 (224, 0.494),
810 (225, 0.329),
811 (226, 0.79),
812 (227, 0.79),
813 (228, 0.786),
814 (229, 0.713),
815 (230, 0.384),
816 (231, 0.384),
817 (232, 0.384),
818 (233, 0.384),
819 (234, 0.384),
820 (235, 0.384),
821 (236, 0.494),
822 (237, 0.494),
823 (238, 0.494),
824 (239, 0.494),
825 (183, 0.46),
826 (241, 0.329),
827 (242, 0.274),
828 (243, 0.686),
829 (244, 0.686),
830 (245, 0.686),
831 (246, 0.384),
832 (247, 0.549),
833 (248, 0.384),
834 (249, 0.384),
835 (250, 0.384),
836 (251, 0.384),
837 (252, 0.494),
838 (253, 0.494),
839 (254, 0.494),
840 (183, 0.46),
841 )
842
843
844 class linkDest(object):
845 """link or outline destination details"""
846
847 def __init__(self, obj, rlink):
848 isExt = obj.is_external
849 isInt = not isExt
850 self.dest = ""
851 self.fileSpec = ""
852 self.flags = 0
853 self.isMap = False
854 self.isUri = False
855 self.kind = LINK_NONE
856 self.lt = Point(0, 0)
857 self.named = ""
858 self.newWindow = ""
859 self.page = obj.page
860 self.rb = Point(0, 0)
861 self.uri = obj.uri
862 if rlink and not self.uri.startswith("#"):
863 self.uri = "#page=%i&zoom=0,%g,%g" % (rlink[0] + 1, rlink[1], rlink[2])
864 if obj.is_external:
865 self.page = -1
866 self.kind = LINK_URI
867 if not self.uri:
868 self.page = -1
869 self.kind = LINK_NONE
870 if isInt and self.uri:
871 self.uri = self.uri.replace("&zoom=nan", "&zoom=0")
872 if self.uri.startswith("#"):
873 self.named = ""
874 self.kind = LINK_GOTO
875 m = re.match('^#page=([0-9]+)&zoom=([0-9.]+),(-?[0-9.]+),(-?[0-9.]+)$', self.uri)
876 if m:
877 self.page = int(m.group(1)) - 1
878 self.lt = Point(float((m.group(3))), float(m.group(4)))
879 self.flags = self.flags | LINK_FLAG_L_VALID | LINK_FLAG_T_VALID
880 else:
881 m = re.match('^#page=([0-9]+)$', self.uri)
882 if m:
883 self.page = int(m.group(1)) - 1
884 else:
885 self.kind = LINK_NAMED
886 self.named = self.uri[1:]
887 else:
888 self.kind = LINK_NAMED
889 self.named = self.uri
890 if obj.is_external:
891 if self.uri.startswith(("http://", "https://", "mailto:", "ftp://")):
892 self.isUri = True
893 self.kind = LINK_URI
894 elif self.uri.startswith("file://"):
895 self.fileSpec = self.uri[7:]
896 self.isUri = False
897 self.uri = ""
898 self.kind = LINK_LAUNCH
899 ftab = self.fileSpec.split("#")
900 if len(ftab) == 2:
901 if ftab[1].startswith("page="):
902 self.kind = LINK_GOTOR
903 self.fileSpec = ftab[0]
904 self.page = int(ftab[1][5:]) - 1
905 else:
906 self.isUri = True
907 self.kind = LINK_LAUNCH
908
909
910 # -------------------------------------------------------------------------------
911 # "Now" timestamp in PDF Format
912 # -------------------------------------------------------------------------------
913 def get_pdf_now() -> str:
914 import time
915
916 tz = "%s'%s'" % (
917 str(abs(time.altzone // 3600)).rjust(2, "0"),
918 str((abs(time.altzone // 60) % 60)).rjust(2, "0"),
919 )
920 tstamp = time.strftime("D:%Y%m%d%H%M%S", time.localtime())
921 if time.altzone > 0:
922 tstamp += "-" + tz
923 elif time.altzone < 0:
924 tstamp += "+" + tz
925 else:
926 pass
927 return tstamp
928
929
930 def get_pdf_str(s: str) -> str:
931 """ Return a PDF string depending on its coding.
932
933 Notes:
934 Returns a string bracketed with either "()" or "<>" for hex values.
935 If only ascii then "(original)" is returned, else if only 8 bit chars
936 then "(original)" with interspersed octal strings \nnn is returned,
937 else a string "<FEFF[hexstring]>" is returned, where [hexstring] is the
938 UTF-16BE encoding of the original.
939 """
940 if not bool(s):
941 return "()"
942
943 def make_utf16be(s):
944 r = bytearray([254, 255]) + bytearray(s, "UTF-16BE")
945 return "<" + r.hex() + ">" # brackets indicate hex
946
947 # The following either returns the original string with mixed-in
948 # octal numbers \nnn for chars outside the ASCII range, or returns
949 # the UTF-16BE BOM version of the string.
950 r = ""
951 for c in s:
952 oc = ord(c)
953 if oc > 255: # shortcut if beyond 8-bit code range
954 return make_utf16be(s)
955
956 if oc > 31 and oc < 127: # in ASCII range
957 if c in ("(", ")", "\\"): # these need to be escaped
958 r += "\\"
959 r += c
960 continue
961
962 if oc > 127: # beyond ASCII
963 r += "\\%03o" % oc
964 continue
965
966 # now the white spaces
967 if oc == 8: # backspace
968 r += "\\b"
969 elif oc == 9: # tab
970 r += "\\t"
971 elif oc == 10: # line feed
972 r += "\\n"
973 elif oc == 12: # form feed
974 r += "\\f"
975 elif oc == 13: # carriage return
976 r += "\\r"
977 else:
978 r += "\\267" # unsupported: replace by 0xB7
979
980 return "(" + r + ")"
981
982
983 def getTJstr(text: str, glyphs: typing.Union[list, tuple, None], simple: bool, ordering: int) -> str:
984 """ Return a PDF string enclosed in [] brackets, suitable for the PDF TJ
985 operator.
986
987 Notes:
988 The input string is converted to either 2 or 4 hex digits per character.
989 Args:
990 simple: no glyphs: 2-chars, use char codes as the glyph
991 glyphs: 2-chars, use glyphs instead of char codes (Symbol,
992 ZapfDingbats)
993 not simple: ordering < 0: 4-chars, use glyphs not char codes
994 ordering >=0: a CJK font! 4 chars, use char codes as glyphs
995 """
996 if text.startswith("[<") and text.endswith(">]"): # already done
997 return text
998
999 if not bool(text):
1000 return "[<>]"
1001
1002 if simple: # each char or its glyph is coded as a 2-byte hex
1003 if glyphs is None: # not Symbol, not ZapfDingbats: use char code
1004 otxt = "".join(["%02x" % ord(c) if ord(c) < 256 else "b7" for c in text])
1005 else: # Symbol or ZapfDingbats: use glyphs
1006 otxt = "".join(
1007 ["%02x" % glyphs[ord(c)][0] if ord(c) < 256 else "b7" for c in text]
1008 )
1009 return "[<" + otxt + ">]"
1010
1011 # non-simple fonts: each char or its glyph is coded as 4-byte hex
1012 if ordering < 0: # not a CJK font: use the glyphs
1013 otxt = "".join(["%04x" % glyphs[ord(c)][0] for c in text])
1014 else: # CJK: use the char codes
1015 otxt = "".join(["%04x" % ord(c) for c in text])
1016
1017 return "[<" + otxt + ">]"
1018
1019
1020 def paper_sizes():
1021 """Known paper formats @ 72 dpi as a dictionary. Key is the format string
1022 like "a4" for ISO-A4. Value is the tuple (width, height).
1023
1024 Information taken from the following web sites:
1025 www.din-formate.de
1026 www.din-formate.info/amerikanische-formate.html
1027 www.directtools.de/wissen/normen/iso.htm
1028 """
1029 return {
1030 "a0": (2384, 3370),
1031 "a1": (1684, 2384),
1032 "a10": (74, 105),
1033 "a2": (1191, 1684),
1034 "a3": (842, 1191),
1035 "a4": (595, 842),
1036 "a5": (420, 595),
1037 "a6": (298, 420),
1038 "a7": (210, 298),
1039 "a8": (147, 210),
1040 "a9": (105, 147),
1041 "b0": (2835, 4008),
1042 "b1": (2004, 2835),
1043 "b10": (88, 125),
1044 "b2": (1417, 2004),
1045 "b3": (1001, 1417),
1046 "b4": (709, 1001),
1047 "b5": (499, 709),
1048 "b6": (354, 499),
1049 "b7": (249, 354),
1050 "b8": (176, 249),
1051 "b9": (125, 176),
1052 "c0": (2599, 3677),
1053 "c1": (1837, 2599),
1054 "c10": (79, 113),
1055 "c2": (1298, 1837),
1056 "c3": (918, 1298),
1057 "c4": (649, 918),
1058 "c5": (459, 649),
1059 "c6": (323, 459),
1060 "c7": (230, 323),
1061 "c8": (162, 230),
1062 "c9": (113, 162),
1063 "card-4x6": (288, 432),
1064 "card-5x7": (360, 504),
1065 "commercial": (297, 684),
1066 "executive": (522, 756),
1067 "invoice": (396, 612),
1068 "ledger": (792, 1224),
1069 "legal": (612, 1008),
1070 "legal-13": (612, 936),
1071 "letter": (612, 792),
1072 "monarch": (279, 540),
1073 "tabloid-extra": (864, 1296),
1074 }
1075
1076
1077 def paper_size(s: str) -> tuple:
1078 """Return a tuple (width, height) for a given paper format string.
1079
1080 Notes:
1081 'A4-L' will return (842, 595), the values for A4 landscape.
1082 Suffix '-P' and no suffix return the portrait tuple.
1083 """
1084 size = s.lower()
1085 f = "p"
1086 if size.endswith("-l"):
1087 f = "l"
1088 size = size[:-2]
1089 if size.endswith("-p"):
1090 size = size[:-2]
1091 rc = paper_sizes().get(size, (-1, -1))
1092 if f == "p":
1093 return rc
1094 return (rc[1], rc[0])
1095
1096
1097 def paper_rect(s: str) -> Rect:
1098 """Return a Rect for the paper size indicated in string 's'. Must conform to the argument of method 'PaperSize', which will be invoked.
1099 """
1100 width, height = paper_size(s)
1101 return Rect(0.0, 0.0, width, height)
1102
1103
1104 def CheckParent(o: typing.Any):
1105 if getattr(o, "parent", None) == None:
1106 raise ValueError("orphaned object: parent is None")
1107
1108
1109 def EnsureOwnership(o: typing.Any):
1110 if not getattr(o, "thisown", False):
1111 raise RuntimeError("object destroyed")
1112
1113
1114 def CheckColor(c: OptSeq):
1115 if c:
1116 if (
1117 type(c) not in (list, tuple)
1118 or len(c) not in (1, 3, 4)
1119 or min(c) < 0
1120 or max(c) > 1
1121 ):
1122 raise ValueError("need 1, 3 or 4 color components in range 0 to 1")
1123
1124
1125 def ColorCode(c: typing.Union[list, tuple, float, None], f: str) -> str:
1126 if not c:
1127 return ""
1128 if hasattr(c, "__float__"):
1129 c = (c,)
1130 CheckColor(c)
1131 if len(c) == 1:
1132 s = "%g " % c[0]
1133 return s + "G " if f == "c" else s + "g "
1134
1135 if len(c) == 3:
1136 s = "%g %g %g " % tuple(c)
1137 return s + "RG " if f == "c" else s + "rg "
1138
1139 s = "%g %g %g %g " % tuple(c)
1140 return s + "K " if f == "c" else s + "k "
1141
1142
1143 def JM_TUPLE(o: typing.Sequence) -> tuple:
1144 return tuple(map(lambda x: round(x, 5) if abs(x) >= 1e-4 else 0, o))
1145
1146
1147 def JM_TUPLE3(o: typing.Sequence) -> tuple:
1148 return tuple(map(lambda x: round(x, 3) if abs(x) >= 1e-3 else 0, o))
1149
1150
1151 def CheckRect(r: typing.Any) -> bool:
1152 """Check whether an object is non-degenerate rect-like.
1153
1154 It must be a sequence of 4 numbers.
1155 """
1156 try:
1157 r = Rect(r)
1158 except:
1159 return False
1160 return not (r.is_empty or r.is_infinite)
1161
1162
1163 def CheckQuad(q: typing.Any) -> bool:
1164 """Check whether an object is convex, not empty quad-like.
1165
1166 It must be a sequence of 4 number pairs.
1167 """
1168 try:
1169 q0 = Quad(q)
1170 except:
1171 return False
1172 return q0.is_convex
1173
1174
1175 def CheckMarkerArg(quads: typing.Any) -> tuple:
1176 if CheckRect(quads):
1177 r = Rect(quads)
1178 return (r.quad,)
1179 if CheckQuad(quads):
1180 return (quads,)
1181 for q in quads:
1182 if not (CheckRect(q) or CheckQuad(q)):
1183 raise ValueError("bad quads entry")
1184 return quads
1185
1186
1187 def CheckMorph(o: typing.Any) -> bool:
1188 if not bool(o):
1189 return False
1190 if not (type(o) in (list, tuple) and len(o) == 2):
1191 raise ValueError("morph must be a sequence of length 2")
1192 if not (len(o[0]) == 2 and len(o[1]) == 6):
1193 raise ValueError("invalid morph parm 0")
1194 if not o[1][4] == o[1][5] == 0:
1195 raise ValueError("invalid morph parm 1")
1196 return True
1197
1198
1199 def CheckFont(page: "struct Page *", fontname: str) -> tuple:
1200 """Return an entry in the page's font list if reference name matches.
1201 """
1202 for f in page.get_fonts():
1203 if f[4] == fontname:
1204 return f
1205
1206
1207 def CheckFontInfo(doc: "struct Document *", xref: int) -> list:
1208 """Return a font info if present in the document.
1209 """
1210 for f in doc.FontInfos:
1211 if xref == f[0]:
1212 return f
1213
1214
1215 def UpdateFontInfo(doc: "struct Document *", info: typing.Sequence):
1216 xref = info[0]
1217 found = False
1218 for i, fi in enumerate(doc.FontInfos):
1219 if fi[0] == xref:
1220 found = True
1221 break
1222 if found:
1223 doc.FontInfos[i] = info
1224 else:
1225 doc.FontInfos.append(info)
1226
1227
1228 def DUMMY(*args, **kw):
1229 return
1230
1231
1232 def planish_line(p1: point_like, p2: point_like) -> Matrix:
1233 """Compute matrix which maps line from p1 to p2 to the x-axis, such that it
1234 maintains its length and p1 * matrix = Point(0, 0).
1235
1236 Args:
1237 p1, p2: point_like
1238 Returns:
1239 Matrix which maps p1 to Point(0, 0) and p2 to a point on the x axis at
1240 the same distance to Point(0,0). Will always combine a rotation and a
1241 transformation.
1242 """
1243 p1 = Point(p1)
1244 p2 = Point(p2)
1245 return Matrix(util_hor_matrix(p1, p2))
1246
1247
1248 def image_profile(img: ByteString) -> dict:
1249 """ Return basic properties of an image.
1250
1251 Args:
1252 img: bytes, bytearray, io.BytesIO object or an opened image file.
1253 Returns:
1254 A dictionary with keys width, height, colorspace.n, bpc, type, ext and size,
1255 where 'type' is the MuPDF image type (0 to 14) and 'ext' the suitable
1256 file extension.
1257 """
1258 if type(img) is io.BytesIO:
1259 stream = img.getvalue()
1260 elif hasattr(img, "read"):
1261 stream = img.read()
1262 elif type(img) in (bytes, bytearray):
1263 stream = img
1264 else:
1265 raise ValueError("bad argument 'img'")
1266
1267 return TOOLS.image_profile(stream)
1268
1269
1270 def ConversionHeader(i: str, filename: OptStr ="unknown"):
1271 t = i.lower()
1272 html = """<!DOCTYPE html>
1273 <html>
1274 <head>
1275 <style>
1276 body{background-color:gray}
1277 div{position:relative;background-color:white;margin:1em auto}
1278 p{position:absolute;margin:0}
1279 img{position:absolute}
1280 </style>
1281 </head>
1282 <body>\n"""
1283
1284 xml = (
1285 """<?xml version="1.0"?>
1286 <document name="%s">\n"""
1287 % filename
1288 )
1289
1290 xhtml = """<?xml version="1.0"?>
1291 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
1292 <html xmlns="http://www.w3.org/1999/xhtml">
1293 <head>
1294 <style>
1295 body{background-color:gray}
1296 div{background-color:white;margin:1em;padding:1em}
1297 p{white-space:pre-wrap}
1298 </style>
1299 </head>
1300 <body>\n"""
1301
1302 text = ""
1303 json = '{"document": "%s", "pages": [\n' % filename
1304 if t == "html":
1305 r = html
1306 elif t == "json":
1307 r = json
1308 elif t == "xml":
1309 r = xml
1310 elif t == "xhtml":
1311 r = xhtml
1312 else:
1313 r = text
1314
1315 return r
1316
1317
1318 def ConversionTrailer(i: str):
1319 t = i.lower()
1320 text = ""
1321 json = "]\n}"
1322 html = "</body>\n</html>\n"
1323 xml = "</document>\n"
1324 xhtml = html
1325 if t == "html":
1326 r = html
1327 elif t == "json":
1328 r = json
1329 elif t == "xml":
1330 r = xml
1331 elif t == "xhtml":
1332 r = xhtml
1333 else:
1334 r = text
1335
1336 return r
1337
1338 class ElementPosition(object):
1339 """Convert a dictionary with element position information to an object."""
1340 def __init__(self):
1341 pass
1342 def __str__(self):
1343 ret = ""
1344 for n, v in self.__dict__.items():
1345 ret += f" {n}={v!r}"
1346 return ret
1347
1348 def make_story_elpos():
1349 return ElementPosition()
1350
1351
1352 def get_highlight_selection(page, start: point_like =None, stop: point_like =None, clip: rect_like =None) -> list:
1353 """Return rectangles of text lines between two points.
1354
1355 Notes:
1356 The default of 'start' is top-left of 'clip'. The default of 'stop'
1357 is bottom-reight of 'clip'.
1358
1359 Args:
1360 start: start point_like
1361 stop: end point_like, must be 'below' start
1362 clip: consider this rect_like only, default is page rectangle
1363 Returns:
1364 List of line bbox intersections with the area established by the
1365 parameters.
1366 """
1367 # validate and normalize arguments
1368 if clip is None:
1369 clip = page.rect
1370 clip = Rect(clip)
1371 if start is None:
1372 start = clip.tl
1373 if stop is None:
1374 stop = clip.br
1375 clip.y0 = start.y
1376 clip.y1 = stop.y
1377 if clip.is_empty or clip.is_infinite:
1378 return []
1379
1380 # extract text of page, clip only, no images, expand ligatures
1381 blocks = page.get_text(
1382 "dict", flags=0, clip=clip,
1383 )["blocks"]
1384
1385 lines = [] # will return this list of rectangles
1386 for b in blocks:
1387 bbox = Rect(b["bbox"])
1388 if bbox.is_infinite or bbox.is_empty:
1389 continue
1390 for line in b["lines"]:
1391 bbox = Rect(line["bbox"])
1392 if bbox.is_infinite or bbox.is_empty:
1393 continue
1394 lines.append(bbox)
1395
1396 if lines == []: # did not select anything
1397 return lines
1398
1399 lines.sort(key=lambda bbox: bbox.y1) # sort by vertical positions
1400
1401 # cut off prefix from first line if start point is close to its top
1402 bboxf = lines.pop(0)
1403 if bboxf.y0 - start.y <= 0.1 * bboxf.height: # close enough?
1404 r = Rect(start.x, bboxf.y0, bboxf.br) # intersection rectangle
1405 if not (r.is_empty or r.is_infinite):
1406 lines.insert(0, r) # insert again if not empty
1407 else:
1408 lines.insert(0, bboxf) # insert again
1409
1410 if lines == []: # the list might have been emptied
1411 return lines
1412
1413 # cut off suffix from last line if stop point is close to its bottom
1414 bboxl = lines.pop()
1415 if stop.y - bboxl.y1 <= 0.1 * bboxl.height: # close enough?
1416 r = Rect(bboxl.tl, stop.x, bboxl.y1) # intersection rectangle
1417 if not (r.is_empty or r.is_infinite):
1418 lines.append(r) # append if not empty
1419 else:
1420 lines.append(bboxl) # append again
1421
1422 return lines
1423
1424
1425 def annot_preprocess(page: "Page") -> int:
1426 """Prepare for annotation insertion on the page.
1427
1428 Returns:
1429 Old page rotation value. Temporarily sets rotation to 0 when required.
1430 """
1431 CheckParent(page)
1432 if not page.parent.is_pdf:
1433 raise ValueError("is no PDF")
1434 old_rotation = page.rotation
1435 if old_rotation != 0:
1436 page.set_rotation(0)
1437 return old_rotation
1438
1439
1440 def annot_postprocess(page: "Page", annot: "Annot") -> None:
1441 """Clean up after annotation inertion.
1442
1443 Set ownership flag and store annotation in page annotation dictionary.
1444 """
1445 annot.parent = weakref.proxy(page)
1446 page._annot_refs[id(annot)] = annot
1447 annot.thisown = True
1448
1449
1450 def sRGB_to_rgb(srgb: int) -> tuple:
1451 """Convert sRGB color code to an RGB color triple.
1452
1453 There is **no error checking** for performance reasons!
1454
1455 Args:
1456 srgb: (int) RRGGBB (red, green, blue), each color in range(255).
1457 Returns:
1458 Tuple (red, green, blue) each item in intervall 0 <= item <= 255.
1459 """
1460 r = srgb >> 16
1461 g = (srgb - (r << 16)) >> 8
1462 b = srgb - (r << 16) - (g << 8)
1463 return (r, g, b)
1464
1465
1466 def sRGB_to_pdf(srgb: int) -> tuple:
1467 """Convert sRGB color code to a PDF color triple.
1468
1469 There is **no error checking** for performance reasons!
1470
1471 Args:
1472 srgb: (int) RRGGBB (red, green, blue), each color in range(255).
1473 Returns:
1474 Tuple (red, green, blue) each item in intervall 0 <= item <= 1.
1475 """
1476 t = sRGB_to_rgb(srgb)
1477 return t[0] / 255.0, t[1] / 255.0, t[2] / 255.0
1478
1479
1480 def make_table(rect: rect_like =(0, 0, 1, 1), cols: int =1, rows: int =1) -> list:
1481 """Return a list of (rows x cols) equal sized rectangles.
1482
1483 Notes:
1484 A utility to fill a given area with table cells of equal size.
1485 Args:
1486 rect: rect_like to use as the table area
1487 rows: number of rows
1488 cols: number of columns
1489 Returns:
1490 A list with <rows> items, where each item is a list of <cols>
1491 PyMuPDF Rect objects of equal sizes.
1492 """
1493 rect = Rect(rect) # ensure this is a Rect
1494 if rect.is_empty or rect.is_infinite:
1495 raise ValueError("rect must be finite and not empty")
1496 tl = rect.tl
1497
1498 height = rect.height / rows # height of one table cell
1499 width = rect.width / cols # width of one table cell
1500 delta_h = (width, 0, width, 0) # diff to next right rect
1501 delta_v = (0, height, 0, height) # diff to next lower rect
1502
1503 r = Rect(tl, tl.x + width, tl.y + height) # first rectangle
1504
1505 # make the first row
1506 row = [r]
1507 for i in range(1, cols):
1508 r += delta_h # build next rect to the right
1509 row.append(r)
1510
1511 # make result, starts with first row
1512 rects = [row]
1513 for i in range(1, rows):
1514 row = rects[i - 1] # take previously appended row
1515 nrow = [] # the new row to append
1516 for r in row: # for each previous cell add its downward copy
1517 nrow.append(r + delta_v)
1518 rects.append(nrow) # append new row to result
1519
1520 return rects
1521
1522
1523 def repair_mono_font(page: "Page", font: "Font") -> None:
1524 """Repair character spacing for mono fonts.
1525
1526 Notes:
1527 Some mono-spaced fonts are displayed with a too large character
1528 width, e.g. "a b c" instead of "abc". This utility adds an entry
1529 "/DW w" to the descendent font of font. The int w is
1530 taken to be the first width > 0 of the font's unicodes.
1531 This should enforce viewers to use 'w' as the character width.
1532
1533 Args:
1534 page: fitz.Page object.
1535 font: fitz.Font object.
1536 """
1537 def set_font_width(doc, xref, width):
1538 df = doc.xref_get_key(xref, "DescendantFonts")
1539 if df[0] != "array":
1540 return False
1541 df_xref = int(df[1][1:-1].replace("0 R",""))
1542 W = doc.xref_get_key(df_xref, "W")
1543 if W[1] != "null":
1544 doc.xref_set_key(df_xref, "W", "null")
1545 doc.xref_set_key(df_xref, "DW", str(width))
1546 return True
1547
1548 if not font.flags["mono"]: # font not flagged as monospaced
1549 return None
1550 doc = page.parent # the document
1551 fontlist = page.get_fonts() # list of fonts on page
1552 xrefs = [ # list of objects referring to font
1553 f[0]
1554 for f in fontlist
1555 if (f[3] == font.name and f[4].startswith("F") and f[5].startswith("Identity"))
1556 ]
1557 if xrefs == []: # our font does not occur
1558 return
1559 xrefs = set(xrefs) # drop any double counts
1560 maxadv = max([font.glyph_advance(cp) for cp in font.valid_codepoints()[:3]])
1561 width = int(round((maxadv * 1000)))
1562 for xref in xrefs:
1563 if not set_font_width(doc, xref, width):
1564 print("Cannot set width for '%s' in xref %i" % (font.name, xref))
1565
1566
1567 # Adobe Glyph List functions
1568 import base64, gzip
1569
1570 _adobe_glyphs = {}
1571 _adobe_unicodes = {}
1572 def unicode_to_glyph_name(ch: int) -> str:
1573 if _adobe_glyphs == {}:
1574 for line in _get_glyph_text():
1575 if line.startswith("#"):
1576 continue
1577 name, unc = line.split(";")
1578 uncl = unc.split()
1579 for unc in uncl:
1580 c = int(unc[:4], base=16)
1581 _adobe_glyphs[c] = name
1582 return _adobe_glyphs.get(ch, ".notdef")
1583
1584
1585 def glyph_name_to_unicode(name: str) -> int:
1586 if _adobe_unicodes == {}:
1587 for line in _get_glyph_text():
1588 if line.startswith("#"):
1589 continue
1590 gname, unc = line.split(";")
1591 c = int(unc[:4], base=16)
1592 _adobe_unicodes[gname] = c
1593 return _adobe_unicodes.get(name, 65533)
1594
1595 def adobe_glyph_names() -> tuple:
1596 if _adobe_unicodes == {}:
1597 for line in _get_glyph_text():
1598 if line.startswith("#"):
1599 continue
1600 gname, unc = line.split(";")
1601 c = int("0x" + unc[:4], base=16)
1602 _adobe_unicodes[gname] = c
1603 return tuple(_adobe_unicodes.keys())
1604
1605 def adobe_glyph_unicodes() -> tuple:
1606 if _adobe_unicodes == {}:
1607 for line in _get_glyph_text():
1608 if line.startswith("#"):
1609 continue
1610 gname, unc = line.split(";")
1611 c = int("0x" + unc[:4], base=16)
1612 _adobe_unicodes[gname] = c
1613 return tuple(_adobe_unicodes.values())
1614
1615 def _get_glyph_text() -> bytes:
1616 return gzip.decompress(base64.b64decode(
1617 b'H4sIABmRaF8C/7W9SZfjRpI1useviPP15utzqroJgBjYWhEkKGWVlKnOoapVO0YQEYSCJE'
1618 b'IcMhT569+9Ppibg8xevHdeSpmEXfPBfDZ3N3f/t7u//r//k/zb3WJ4eTv2T9vzXTaZZH/N'
1619 b'Junsbr4Z7ru7/7s9n1/+6z//8/X19T/WRP7jYdj/57//R/Jv8Pax2/Sn87G/v5z74XC3Pm'
1620 b'zuLqfurj/cnYbL8aEzyH1/WB/f7h6H4/70l7vX/ry9G47wzK/hcr7bD5v+sX9YM4i/3K2P'
1621 b'3d1Ld9z353O3uXs5Dl/7DT7O2/UZ/3Tw9zjsdsNrf3i6exgOm57eTsbbvjv/1w2xTnfDo5'
1622 b'fnYdjA3eV0vjt25zXkRJB36/vhKwN+kEw4DOf+ofsLuP3pboewGISO7bAxPkUU+EaUD7t1'
1623 b'v++O/3FTCESmcsILgQRuLhDs/w857lz6NsPDZd8dzmtfSP85HO8GcI53+/W5O/br3QkeJa'
1624 b'9NERmPKgE2Ue+73vgj97Ded5TH1pPDEFCT4/35RFFtAMORMezXb3dwiioCsYe77rABjjCO'
1625 b'jHs/nLs7mx3wuYFYX+HsEQyTfHg/DY/nVxa0rzmnl+6BVQfeegTyemSlOdjqczqJ0J9/ev'
1626 b'fp7tOH1ed/zj+2d/j+9eOHf7xbtsu75jcw27vFh19/+/jux58+3/304edl+/HT3fz9kq3i'
1627 b'w/vPH981Xz5/APR/5p/g9/+Qhb+/3bX/8+vH9tOnuw8f79798uvP7xAcwv84f//5XfvpL/'
1628 b'D97v3i5y/Ld+9//Msdgrh7/+Hz3c/vfnn3GQ4/f/iLifja492HFbz+0n5c/ARg3rz7+d3n'
1629 b'30ycq3ef3zO+FSKc3/06//j53eLLz/OPd79++fjrh0/tHRIHr8t3nxY/z9/90i7/AxIg1r'
1630 b'v2H+37z3effpr//PPN1CIF47Q2LUSdNz+3NjakdvnuY7v4/BcEGb4WyEPI+DMT++nXdvEO'
1631 b'n8iWFomaf/ztL8wZhPqp/e8vcAbm3XL+y/xHpPH/xlnDejXKHJTQ4svH9hdK/mF19+lL8+'
1632 b'nzu89fPrd3P374sDSZ/qn9+I93i/bTD/D+8wcWxOruy6f2L4jl89xEjkCQaZ9+4Hfz5dM7'
1633 b'k33v3n9uP3788uvndx/e/zu8/vThn8ggSDqH56XJ6Q/vTZKRVx8+/sZgmRemIP5y98+fWu'
1634 b'Ao8vc+z+bMjE/Iu8Vn7RBxIis/q7TevW9//Pndj+37RWuz/AND+ue7T+2/o+zefaKTdzbq'
1635 b'f84R7xeTdJYYJLOf7z4xq11N/osp2bt3q7v58h/vKLxzjtrw6Z2rOSbzFj+5rEd7+P84UL'
1636 b'xH8/6vO/lj2/6Pu7eX7d3P6C3Y2tb3u+7ua3dkA/yvu+w/JqyV6GeUt0/dy7nb36MjySZ/'
1637 b'MUMO3Hz5+LNycsdx54SB5wmN/XJvRh0z/vz1/PaCf4Zhd/rP9dPur/j7eDDtfIV+dX3+r7'
1638 b'vz63B36vb9w7AbDn/ddLseown7kr7bbU4YIhD6/03//e7JiM0O669/vbyg1/hPdKLd8WGN'
1639 b'PmnXoSs52h5200OGk/WW/fvdl0NvhpHTw3q3Pt59Xe8uCOARA8ydCcX433Z/rjfonfbrnf'
1640 b'hP5j9MJtM0mbf4XZT4XT9czt0Pk3S1ALFfPxyHA6g2A3WCz90Pq6qFO+dsskjdtzAB3B+7'
1641 b'rwwDeWi/reu0nbcOeMBostv1Dz9MpsuJwzbD+b5DcuGuKR32dFx/pcfGO9oOw7MZlAj64M'
1642 b'/9bmOAaTJ/WFuJF0t898eHXfdDNmV4JC77x133J8XONCDiTTWq5JkvNMMLNY9C1ZLNa82R'
1643 b'rIki9ULP50AZ/6pczOyn92DSE3IqRSZs7nc2+gmqKMi+O3an/sQkTQOpszcLsBTnsg2gSE'
1644 b'f/KskTQ4YaANrFPFn4b/ELIEo/Iu2jQkbg/QEtEJXe1Y6MtWP3sl3/MMlnqf08D4cBaclr'
1645 b'5KzEzHTuyXhZPyCXVhkcD0/DoXsmEwEfoWVQqsJ+Sg2eW9qniOGQFqHh3n+XCNMWCMLJ3b'
1646 b'c4BPB2vz5CYenXkKjI06Rhu8mSJlSxKmmQX+uHB6g1jC0ztEQ+TRqdISmC6A46TLiH/sfM'
1647 b'wBczE0mo4WrXHzoJpUyaKCvglLnpJC1XiEWSBN55eIHcDChLFpQ4TxZrHWkL2mUXwl6Yto'
1648 b'N6OLefEmyRLHy7mizwDT1yt1szryqhfCOa1AJJBtKVZFRtCd8WU3pATvFrbr5cHlo6Dome'
1649 b'tzoF0xmAbn3/vF2fgKgcbhbkKCCrCKBYETp0uZt+2siJ5pSGc92+kOVgbLVIOREE/rw+jc'
1650 b'JfNGSxGWBysYMmOzxrCU3qelSBOUV1VQCf456kXEGaqB4gykGJUKTJQupBnixZ9NNk+S+2'
1651 b'ihS/0kkCjOoD6ccjhCO3niVLKfYW367Y0xY90TIU6MwSVkRfVdMM6HFYsxzpPGobc0NLrV'
1652 b'4ky6htQIoOA9rLmWTeIupuh6aRZaij5vPp2LH15zO49PmEMH1niBrcCCWd60KgH00/Bmgp'
1653 b'kM8t9NzL/mm930scS/j7XYuHlr2MGiXkiwoDQvnESoFVyfKEarx1uSGFA7ehkULobywiRP'
1654 b'BNiqgAcbOCo9MFRwtGp1GVn6wSDuzTImllwJ65b2mcAPyAjZxvfcTpHN+2xC0bZboApKt6'
1655 b'joBDPZhbIgyyEeD7B7Sx9kZ1qTWqKgeUkvZ66MUI1N4eejGytzeG3kgUP/QumFyVWyD1+E'
1656 b'pSja9NICVYYqbrSkvzJV2Xo0WhQfIedV+EsGU0rd23hAogyuUKtNZ7kBjOxTEPBT9LS/Cv'
1657 b'BlfE32OqDgVzo+JFfWt3uqkhATv4OEhYCFtGXrRhR/jCY7Is4kuCVWavQ0QdiVoDqoiute'
1658 b'kS9K0eFjpDy3E8nc75EdVjKGbtgVmg+1KkWtQAVp/hpaPQM1SNl1O/YwryWeEJUS3gUkeb'
1659 b'wTnzDLP+DdtgG0jtClLrXh86SHu6mQoIb1r5HM1KWjmksEN7xQ9VsjVpEQ1ezvA7gUqMD+'
1660 b'97RcpruAv3Le0G8V2Oww/ZBDpq+40xQxPBh2/G6D1BqRSiKq7YJ5TJKjTdJlnpDjptk1U0'
1661 b'phVwrbvkabJy/S5Ut1UPnyELqgwIovM1Cm6jCoGgMDERdp6sJJ/K5EeKViU/Nqc/Lutj90'
1662 b'OeYwD8UVS6Kb7RNzMrc/sZhqsZmYenfh3EnCc/StfWJj9KniAe0WFSKFE/hpxYWEK0k5TA'
1663 b'wIh806Z72+hRd37UjZ50NJBBxu16o3UD+N1iHrjZ7LpRfab42+5KJ5gZH5eX8+WomxFq+Y'
1664 b'++BBALJnWqVgGIRywArlFjJgefUXkgf/142NpPKQ84le/KfdtYs1kD2gjLDJ0mP7Hg6uSn'
1665 b'tEb8P2TFYmW+p/xGo+B3kfK7SX7CQF4ZPE1++lUKGh3sT+tbAx3G5J/WN5WyDIzj5tQ/ae'
1666 b'cZYrMDKqraT6b8fWshK2gxGcINBb+0hBQ8uuifpPuHY4SlmwhqwU+qg6frKFcRttbIphPQ'
1667 b'R9WCwJesxfcF85bjZb9bX84siFWEiBYBh98kv1AF3jHTZ8k7PUvMVsm7v0F+TCjefdF4m7'
1668 b'wTJWDpvmXIAeBbSrZI3on2gcBCFrWWCAN8BEhYRFXlK5N3elStQapRdRVIP8hQ0huaNirZ'
1669 b'u6sBmN5NW8wn5kvaoqNFjZgn77qrpQeIFrXXInn3eFw/o62hZ8IU7Z2M0Qv3LREDiNQOJK'
1670 b'vXQZEej8mQoT9th+NZO0TxyYCL+ukInW4UZFS14AO1SrX3Jnk36ByH4DIyMjMHO/jMzJfq'
1671 b'MEsDhNLI0VCJyIAEUiopfEt7xzj2zk2XU9T0d9GQxPrzbdufT9GgMPWgrwuaWSZ/Y02eJ3'
1672 b'+L5nZp8rdQ+VaWkPaJucrfok6uTv42mog1yd+ijEP4kpx58ndG2SR/V0NNkfz976E/WiZ/'
1673 b'X99DZ3/uoxF+AtjV1Nx8q8JEqDd7qhkZYwUmB/byYoqG7OuuvwX63cnibJH8XQa0Gt8yoO'
1674 b'UlKJ9v0JT/Ho9fZKuWgX7i7/FYPwUQLU2skr9vdTKh0/19q9UBhOgHI0gSjz0QU8+WUGx/'
1675 b'jwoFJTAgF5SXemIhmYEhH066cZUEfEE2yc8syEXyM3s9aIU//4yuEtXlZ6815DN87+83Jq'
1676 b'fh3OdavsR3yDVyJNdSS8STlByRjPISnlz/szJfgWNp8VoGUoZiqH8/969RViOG35kMcOJs'
1677 b'RBqibJwnP0fZCI9+gol2Y79l3IBnya9F8gvza5n8oip+mfxihVqVUD7tt0yJVwRchW+TX0'
1678 b'ImZckvekjEGPeLSjJ0nV+iejSdJr9EMkMGEQvfVHGMioqq/cuFhbVI3lPWNnlvynaevPdl'
1679 b'Os2T974coS++D+WIye77IGJuibgc0dG8j8uRnqKkTA0tHsrkPSv4rnuk69kyeY+yEBW2Tt'
1680 b'6bQmvwGxUa4tGFBv3ofZQBSNjwqnMI8UiOgOmXJJep+5Y5AQCTQ8vkA3NolXzARD8tMvxK'
1681 b'qc+TD37AX+buWwIAACXpGM1y0I048Nbwi+C8ioAS+eBzH7J9YK7Bw8aPCTPIE8pgaglRG5'
1682 b'YR4KsW6t2HmysAy1oz/LxzmWlUD8Vx8JLgCPXzKWgAH3T/jXRhfPKVrJgYUlSXBcigutDv'
1683 b'rXxSsEROTCkjCMiMz1JUDQCnajBhkaqxAhD1zwXoPeodVNIPkQ7Skj6yUDBImU/J3LmllR'
1684 b'BtZiHJ0IWlo6x0IfrsahmsVlVtHvWMEcFdKTzwLroNeugP8WICa2u8mMDA9t3T2iWOn7rb'
1685 b'd1w/LmCKbejjcDnoalzNLX7uzzutF1ULh3v1BrV031vx8pkQwqZz3VrhQjV6CCNKFtuGJc'
1686 b'J+CXy7FQn0rh9c3zxhZTbfMqVtHSDFTRe+D0CUduDXzrX6WJH2vUThvn0GM8sNoOYxU+9B'
1687 b'4iuSX+EZWf+rFMw0+TU0X/B111iUya+R0rwCHaldcwA3p7hzeLXr2/ywCsMccRkI8fevR1'
1688 b'3P8+RXnf9Qtn49Gac1P3QmkOOSg+//ZnLS5L9DEsrkv6OQwBT3afKR7rPkY6R7LkD7bmCa'
1689 b'fPS9XVHjW8Ya5MXHEEsFIhpVyFb9RzoBqXOyNrRvkMU8kKIiFJAj1s4QiJqjgL0dmCdIRt'
1690 b'jbKlcLknFrTJFEPRoVbfIxyhXwJVf8tw8E/ut0hJ0uLx2tXMBryuQTczFPPq24YzeZYHqP'
1691 b'/hJU5qh0Sir31ITU1FM1qcJRufFXOiozVOV5JpTa+zO8mXdJnoncxM4YUpElI+VdlimozL'
1692 b'ssycu8SxQaKC81OltQXuqS6cu81IUJxUtdVKS81MWSlJe6oJyZl7poQOXisiUlLlekxOWc'
1693 b'lJe6YPqmIvWMlJe6pNRTL3XJtE+91IWhvNQlZZl6qUtKPfWylCyHqZelNPF5WUrmxFRkYe'
1694 b'yFl6Wgv0JykPlZSA4yzwrJQaa9EFmQPmll/ls3EYqw3r/0vsvHAPTJN8XSf0ceSgdKS0BB'
1695 b'qAaLzH7YvvITvb/51OsBtYVubaNDutDSa0vIXJTlGzX9jDU6kmtiaN/2WOU8GTmDt7gzhf'
1696 b'jR+jzSF2+AVgT05AxBbB9iCIUVzdcQ+zZy0SB5236vlk6Rov7JrLTOUYD9nyIAqkHUa4A7'
1697 b'PJ7Ha3DwLn0JXJwZlszn5slndhbT5POaSiyGgM92wQ6p+yzFCzQUHDLsc8j/mSVirR49/+'
1698 b'e4/6WnKHfnhpZCWCSfow1iOL+5+Tunw1AEiL07n6KNW8i6dbv3NT7d0LbgJ/WxCRQp8ymD'
1699 b'Lmlkh4SJqNWgXJIfzwyh4n/WvTemB5+jcoAIesERk97PUEgee6OwNwtDnXrW1npqiPPrQC'
1700 b'Gr5POxg47h1WhiCDtKH5Sxz6d4Z7EB4gsY4b12O7XkD+brIFSafGFxF8kXmY7M3bfkBwA/'
1701 b'uUCxfJHJRY5vKfa5JcJEotGA1INSoxID3aoUIWCl6aPufNEj9RSk0vQXgfQ+llXAJOYsYJ'
1702 b'KCmcKU2cAkwC7WlMm5NtUpAihpoTxKk4e0MnuYuW9xC0Cr9JiefPGThJX99Gofpn9fRpME'
1703 b'iqknCVB0v4wnCegqvkSThBZ0PElg9mpIZwTy7EpTgYxab6wgmGQIGvGX6zXS1oNK1a3oUj'
1704 b'cRZKWo7Cwr2SacF55I2T8Jy+QM03p6298PO+nAcnEgi6lN6jG9ntqMwRuBTb2bwIuEkPkI'
1705 b'0mhNnVI0/i/jheQJMd8ikR7MG9bcJdb9WBvga+MTlJGfv2MY+hLNJCoPSFWfJv9goy6Tf4'
1706 b'T22ST/UHUHU5N/RBOFDHS02gEHrsdpwIuKCuFG2yd18g9JHHi+rmFK90+KUSX/9KLWWfLP'
1707 b'INLCEjJSQ+5/qipSk1QjBKZq/1RJqOvkn77q15Pkn5GIiFNEqpL/oRh18j8h6mXyPzqmBU'
1708 b'gd0zz5n2ikz+Ges5tZm/xPFA8ClXjq5DfGM0t+k6506b6lwRPQpY6x5bcgVWuJkCFl8luo'
1709 b'sSljuOpuVsC06K2hpY+YJr9hHqA714bI5Va3h+B9hqLl/+aLP7efvktZQSi9wzEtQOu6Xo'
1710 b'GOhkfonL9FuYYsklzDt68wFOByuu+fdAbNHXbLYGJB3q4/n3e6LkNREfiWrzr5F8tpnvwr'
1711 b'Mq8qQfsRZ5aIGVa1dN8y/K8ASJE5whVZ2s4myb/sonPVmC9ReBztS2aWJf+KWmAF+ub2RE'
1712 b'3GDa23BW7VGoi+7XRa5gTGO2qLlKiO0vi7Gafl3Ih0kfxLazqzafKvqGgRsxQtv/2uVFMk'
1713 b'tEmEvrFe33cYbXZoTzM06bVvLC1Zm+4rnM0mxJ8uv6+P6zPczWtLH/eXZ65RzA1/v0Z3qc'
1714 b'C8BXi8yML5JAf9dYD2QwU4RNq0Gncx5hGooqbre2Zlb87D7NfHZ121VxFXBYhhVScUyb8f'
1715 b'Xob98Dj8kNN+ay2G2Ln7FkvnlQN0vqcO03ZLlcPEENs7igySfPBipgJRZAsZiZO6vJxYQl'
1716 b'Q4TEXWNwyxC41qq+SlZoghdqXRyBB5pjlict0kvkZAczefJoKH/T2qelpZyFKT1FFDRLoS'
1717 b'KJx3LtkMXCRBYzUABm0XwJQ+Qi7nyAG9pgzuZrN+VnWsIuTqKPJB6aFQ9G7OTfMAB70Rgu'
1718 b'iMSw0ZlidBmxaBWh4WF5G73fNw7FDvcq7srrvgAZE89v2EO/g/QOzCkvVsmtL4aGrIdII+'
1719 b'yFqqe7K2xs6enFlFwJHZxFrJeDK11p+ezOyevCdzu7ftyantXjxZ2A7Ok6XdhPdkZbfaPV'
1720 b'nbzVpPzqwpnCPzibVj82RqzdY8mdmNAk/mdg3Uk1NrU+bJwhqLebK000xPVnYm4snaWgZ6'
1721 b'cma3Wh05ndiJmCdTa9LsycxO/T2Z22m/J6fWLsaThR2kPVnaGbsnK2vw5snaGo94cmZtTB'
1722 b'xZTKwxkidTayDrycxaH3kyt1aWnpxao1VPFtZaxJOlHeg9Wdk9fk/WdlPUkzO73ebIcmKn'
1723 b'qJ5M7Ua0JzOrLnsyp8WNSFVOSYpUZeEarSMpVS4FWlKqXNJbUqpc0ltSqlxCrihVLiFXlK'
1724 b'qQoCpKlUvyK+ZVLsmvmFe5JL8yUknyKyOVJL8yUknyKyOVJL8yUkn51kYqyY2aUuVSvjWl'
1725 b'mkrya0o1FZlrSjWV5NeUairJrynVVJJfU6qpJL+mVFNJb02pppLeGaWaSnpnlGoq6Z0ZqS'
1726 b'S9MyOVpHdmpJL0zoxUkt6ZkUrSOzNSSXpnlGomCZxRqsInEADJXEhTglMhKVVRCEmpilJI'
1727 b'SlVUQlKqohaSUhUzISlVMReSUhWNkEYqn8A0NVL5FKWmdU9WQpZ2DuDJyppoerK2xjmORM'
1728 b'ai8ovMJmMLCcpkbCnJNxlbBZIRVT75NbpNBFUJaUL26a2NVEub3gy5nE1cg8y5MDxx4mO4'
1729 b'JWHLrqhyVs6ynAsJ4UvXrkGyVpTlRMicZCrklGQmZEEyF7IkORWyIlkIyYjKUsgZycqRU9'
1730 b'aKsqyFNELOhKQYbnAhyZDdeEGSQWVeyCmLsswyIRlUlgvJBGZTIRlyVgjJBGalkExgJkKm'
1731 b'TGAmQnKYLjMRksN0mc2FNFKJzJmRaiGkkWoppJGqFdJIJQnkMF3mEyEpVS7p5TBd5pJeDt'
1732 b'NlLunlMF3mkl4O02Uu6eUwXeaSXg7TZS7p5TBd5pJeDtNlLunNjVSSXo6t5VSE5NhaTkVI'
1733 b'jq3lVITk2FpORUiOreVUhGTrK6ciJOt5ORUh2dzKqUjFwbScilSFEUOkKowYUgqFEUNKoT'
1734 b'BiSCkURgwphcKIIaXAwbQsJIEcTMtCEsjBtCwkgZURw+dkwZ6qnE+FZFBVKySDqkshGdSs'
1735 b'FpIJnHsxClOfq5mQTFEtjk19nqVCMkXNXEgGtfRCFqYElz6fUQ+ohXrHJUuhaLyQJRNYLH'
1736 b'yRoZ2DXE6EpONlKmRJMhOyIhn8MqjlVMgZSRGDWVcsSyFTkpWQGclayJzkTEgjlSShMlI1'
1737 b'QhqpFkIaqZZCGqkkvZWRymd7ySG+aCW97EWLVtLLIb5oJb0c4otW0sshvmglvRzii1bSyy'
1738 b'G+aCW9HOKLVtLL/rloJb0c4otW0jszUkl60T+vmiyQBUmf/Ap97KqZBpJc6UUrdm7FaiIk'
1739 b'xVilQlKMlU9ghQ5q1Ug3UnGYKJqpkExvE7imIpVCMqJGxOAwUTS1kIyoqYRkehsvVc1hom'
1740 b'gyIVkKTSokS6HJhaRUi+CYUi2CYyPGTEgjhq8bdW7i9XWjnpqIVkIyooWXasZONXN+yzRD'
1741 b'B5WlTicHiSLLUjdBK9McXVCWujlXmRY04p9kCyGnJJdCFiRbR7LRYSh3jvO0NCOsczydcS'
1742 b'qUUWa/kcHqqldniiRanAG57Y/rp/Vh/UPOk7jraNoPifuwMsL5Sa+XRiBU76bYnKrGR5UR'
1743 b'dK9iNp5V1MbDeF2IXTpvUlnfMwwz0PSHRyA7h61ogQ4M/517jTZE990mAhcER7ZUTNKNlS'
1744 b'aqVP14pWkagSoxdP28PuOvybd5Fsjtevf42m/O2x9WKy5ByDoAR5Fd9+i6THxJMqldgN6s'
1745 b'n7rT1iwGvrJpWVdx6uvWgNv1/tvalFIIJB9xRh6ngW0WM4LHYsQZeawt24olwu/WyGyR1a'
1746 b'VtzzWYkVjZiDMK3bOfT5fjWnxxLA9w7GU10bxxRVjlmjuqECubCS8oqpDPmc3SP7hIeQqo'
1747 b'SdHLFg2Vfdxu1/1xWe9+yDJqDu64PXsdfdx+DlY4bg+mXm6lHrR/6Y6n9WHzAxdWAqmdTR'
1748 b'TuV2eN22BPjyw7qFbIHD48aWBK4Hm7PjxvL+ftGhWWRlHAuHaYcVWFn/fH9cNzdza2uJgt'
1749 b'1FeoN5lHxnEiq7jmCiN6ml3DytfUxWSiyPLMuba+QRuZuOxsrDDRgg/DGY575m2NNnG4bN'
1750 b'bns1/Eo2J1uJy+sjTDYm0A/VpfQHS/BzRcdoACfVmj2ML684TIsTv8kPFAwPploFgv0Uo9'
1751 b's1Bwu0rJ/v7lBbm6qlcrfh6H9cO2OyGXqSSS/lPqTa2B4Yi+74nFwWQZnJ1ht3sT9xDyuO'
1752 b'7UQiLbPpEAoJ8/PiAnuRJocpWdj9nbTNvZnJi50YF6RnSjQ2NpOXmNqnk8Dq/3w5n1fTa1'
1753 b'5GZ92m6GV9oeUI/xkC1NXmQhkCtRXm8i2OWFgAt5c79zgS+ngriwl7kgLujlRBAf8jITyA'
1754 b'S89AHbMGZ5IF0gs1mAfChUqD32uu2RGRDRuUNZb4i79ecioAzQoVlATZgOzgN8eXGYS+cW'
1755 b'Jf2t+xM1hPocES/fJJBIlUq2Q9x+TMYrWARHB3r0qeH6gsclNQ6TFGeKjgJdKQYE//r2Q1'
1756 b'bNWgUyKierT4zBJSqXmWfeCmSrxFQQqREuH02hzVJPbEyhFYG8PzHIeS0ISuJ+PQJ9zpUa'
1757 b'GB5dHVhIcJL4yiMis0OMTmAKBWGdHvrebm5wr7HVQLRf5jjeTLjStHZogzj2LzRg4+zQEv'
1758 b'5Yhmnx9gio0rxSh2mtYoxp1YLLJife8HZ65mgyF2q9456JjKRUDT3nBoY+B60yS0No0WAU'
1759 b'gnVjUcuFIAuh0zYKo5ivrkq2pdPb/uU8mCFAdWZoIWcesEAV9/nHPuUcGYaTKfGgjwo5Bs'
1760 b'5F6aFTkmrAI9vroeRptdPSQe0kvUNQ5y33B0OgnF5ervRRdPCXW9pihHttMQK1tgjGV2rk'
1761 b'Wz9Icdk4ugqH2frWH9wM8o0KD4sxqCMTg4oWBlf33KPFjxoNoYDcYyT2RvKFIqOaTNxJkv'
1762 b'FbyTq3tOSA4auKWk1In51aAb3gXivCS3KPbBz0doxaBRBVZhiD78N2ZprcRxeb5IaW8Qlu'
1763 b'O+pyp/7PcwcnWyoKGGXLEoF2D+sLO4ospzO9RYhQaRriNdGaZKxLohMGNtYhZ8ajSvOM9E'
1764 b'iXRM9qwG4/8r6YrYRzGnYY1DfCmhgZDsMQT2oWaJH3nc5HxqjtMljQ3dmur9xbU4LGQOuR'
1765 b'FRQTdLYzCc4h0kCGiYUBg0JvSGjZobahJt9vdb1akvY1xhC6yjgg1BkC9nh7gZLsdVaS1g'
1766 b'klvUMurHcPKDVzIh551B82eq4Ine6+V+YCTMEONdtXIJ6SNwBKCHVuQ6R0CAaHl6E/nKHv'
1767 b'QEF1SjBn+YbNEcSzzW93pOfpNVd5xqzfscF5uKAYY106/d/4WqtuvuPO69dp+r850CH55P'
1768 b'CWO8aipEU/G3jGo2ZmlnnsHs4em7vAjNvrzGnmN9g6a13Om57cFZm5u8Ch/Q7uH9kpZKXP'
1769 b'geDMZd3pjG4kK9nySZrb98bpmireVbqCRyehEUeLOR270EyTLYdn9E0Zs09fU1SBHlBTsw'
1770 b'JT4/toigdfwz1XNXrXP6ZI9aCrP7J20NUftMw70Gr+CLM8RIuy7oyWgnmrIey5yUnVBPL+'
1771 b'TH4egH2/IZIpRPfCyqsfajV2fqHnNAC6klUWtrUTYiwVbeVoFeIE0Y4iSTRDRFko0MqiES'
1772 b'1MnehGh8Gu0YAVZ6Ihq++tNBQNipF/E3fbJlGDRCTLCLGxNBFmC2weYVE8cRA2keju3frU'
1773 b'sk7CVRvW8iVrLeQMaUpLycKWcriKWc4OJ43RzXCBwm55JXn95imKbu6wGzHk5GECcbCj/B'
1774 b'yyiNlYjdzWuiCchiu5UEEvuh3A40W3A9KY/p251Jm5bxM/R3au9VtoQPCYtx+pss4Mdure'
1775 b'TJfcJg/Uh/LkQVsKloDVOIY58YPc01fh2yuNxLXSaOmgNJLehWPeNcjDhoP3YaP00jrVuM'
1776 b'v9icb8GkXkUC9TkPFysv0Lj0M+IMbh0a4lO0uwbFHZT11mCwu5KmIo9GZP3bGjEg3/Dfzr'
1777 b'pVskQe6kW+JbriLEFOlhfBXhDJDoapklwr2D5F6OO472iMRdQdiYr3AFIenQucGdRNjUnn'
1778 b'BpgQDGE5dV+dU/cXGHeZBb+vDoK9lyZRDdvtqJgYbd5nR+49JM5YLRdRNuotM/0PAetMIz'
1779 b'a0j72mEIXT0cEOoHAZ27U9C3b1NckvPwzLkHJtxpbsjAn1YE/vfLFVeRE82xnm+YCxdkaC'
1780 b'vpykR8+3LFBVnfv1yRWUUDa1bDbd9deEbKVA6/LpVVgWMGN2Gkwhj5KGeeEZbL5x6Kw2B1'
1781 b'2w4ImlM4M8hO5h7xQG2BPjhxnobOA0yku/EQrhnPVSpKh4/S4OBxClwoQX4HjKR36GUUKM'
1782 b'QRXbZx3/vL7ty/7N7Q2c0qh6FxgZo56mV34VrjrPD0AL1pZ+pWjs7dobxTnWMalw+MysMe'
1783 b'daKYsnQo3DTRTTxblMnofJBrqkuFu74HjW3XUXkzDZk6/Xr3tcM8iOPAIrPQhnfW7whMLM'
1784 b'Bp0tEiqUXkMBUx1Nbd5Z4TPvt1uvRnJ6yG3DIPbUoe9g/omUOXM0eTjHQ1+HJr6soRpNHH'
1785 b'JdgdD+ZoywQjn/nc88TX+vjGbfJUIAk2dc64AqCciH5TWNqqmlTome12xXCZjnkOp1Dmsj'
1786 b'buEdqTedxIceNLriBTkA4vEn2Ib1UuvEM/H574wNQS99JCqodtUwtFy0LOp78NT4szjVlu'
1787 b'ndyFK9ngkqS75MxCds1HhxgxXHgNsRd0XZxDUJrD0/HCdJp1c75NMFyOnLA8Hc36E1Qo82'
1788 b'DBAILG5o6YL3h5ETQqRzct78ChZuBoHsZmk7XkYs5rVNJA88Q7R09LLhcp2WmgM9JZoHPS'
1789 b'eaCnpKdCm9irldA/89JRKhCWbnnhDNQeT77nAf1JIfQHngadSHDtJ15VzKHJ0Z952XJaBZ'
1790 b'pnbUJmrHidoSlaSzLtqZA/GlLS+pOJS2T52fide/L9nPmaimgfjWcpg0+8b20i6fzEq1cm'
1791 b'gWvTIdn2ycop2frpi0mHRPbpN1MqUohfTGQS+j9MaMwF9/QGFYtZIE/rw4m6voZQKR+pXR'
1792 b'BDrRtN700ejeBoaTa75utdsTRmy2ba8gYehZvfcKADNvG+DEd7vsF3aqZCBdWL5Q9Pz08B'
1793 b'QtbJJBTFcLx863p7FyZChALQnalWcGkGnqHpvXELM6ONvqGMOk4F/HJEIA9vzGDUwrejuV'
1794 b'Ob+ZiSWrEvX9H0CMS9ZxmHj45VJNwaLafJJlLiSavFqBLkJtgIGNItTZnveImvaYmNl/ig'
1795 b'RAEd2wtMErdyZsxAomUzjzxxDWSSTdy32bmZZClJtSJWGjosiJFW05+S3tX0x0S8CyuVFG'
1796 b'5nl/ty+xlW9CIgrOk5eItA7f628XxnLGVGnLDyd8U/dU88Nek46Zgz8un5AXVAf+z/EFdT'
1797 b'BY4C8CxoB3sBZwocuXesOH2VAkfuHctu7Qtaa3Tkw/Mu9xflo9HoyIfjxTlXKnDk3rO2ps'
1798 b'o6cKLAkXvHYqfUCVgocOTesOImMJ8D00P/dGUBbQbisfP6MNpCmi4CJ8IOvApuZprn8SnI'
1799 b'Pa8sYPrFCMRM4+XQcZdFjvKYQX5aQ+r7nb8/lfWIy2/XRgrzWwy9KrQcO5DetbnJ0X5b4+'
1800 b'LIecP10or1rvZv0XN5RG1Sc1vb54tJ05NPUymUU5RXBLSOsiCAGLnayKNBlaLd8ovJGLMx'
1801 b'GzATzsux33ujBJNJPmFcf8k4OiqMnpWGNWHC1c4MWtl9GBzQImShAFGpy+vR/MOqQG6J0W'
1802 b'3kRP3l9XAedeOG9h23IXQP6oDQhRog9JGYtW3GFb2pIfpmIxP3Ajm6ifYxskSxM0vpWD0S'
1803 b'oiWid6YaQ8tiMOqbfQrm1L2szdJU2GVtrni06zFjmmOqvSrUpo6bOFwQQZPvtn1oOktDh9'
1804 b'EDFUPfQoJS0XtHC7LROYjZTeNosbspCdg9pKn9lCsDa8Z1GPbIVsiLn8sJXcHhsrfrbiEr'
1805 b'V8j/jvdkZxjr40yuEpXHhtBZ7ICQwwTcZhE+MR6/nblD5E/rFyPMnQacJrLXwxMFjogmgS'
1806 b'i6cOZvXifx1RNoklUS3TzhWvpUUNc8gk9pzAGK5NSFxNh1qZA+nwc3OYfaven5JhtEW1Xu'
1807 b'm3P5zDL4wpLdxs0y6NGb6D7EAmE9n7ZmUayYwUO0P4HqEJYqobFtwj30aEPRHBhJPchmBg'
1808 b'guomzWfokE3cKAmuW3MsjXCURb01sZC9I7M82fMA/Nt55I5g6LZpLeoVquE89iCuBD1tNF'
1809 b'Ojo8UUdF9R7U3iBrd1h4zJazQLryrBLfgl2J5wEYFKISt2IkGGxOvDgtzVNP/c4rUluh7G'
1810 b'KZq80mQ8/OwGJRkOCavCzzoHMyK/Fvw8YqNMYSO8ZEvzOc1wMS8qyP2LaCurUCRCOqPLzo'
1811 b'HEMSzuveLNMii8LSPOTQS/MctvTSPCU3r2kgT75ZzYCNnpQcTS5J2CXgOZ3ffmcjJUdXYz'
1812 b'qNVj+LVcIGARE6OWo+w/eReciTJJ1abIdbveS6SDq5ox7+7fq6X29fekCvtQt4ZchRXHG0'
1813 b'NYfhuhbV4Hv0uAeD1UutTM3D9i2+Z6GuAMrgObVEOM0914C8+LHSqIyxM43q2zErzZAXP1'
1814 b'KNRtde5pojb3tQelVCEFUfuwbX5zGk02eskTPuSY8q6aInPSwtR+Mhf6f3+hFOd2WHAz/6'
1815 b'3Q/0XJ1YuNf4VsUK/1H2w2u0No/y0YZX8B2dwYfckY07gnOrBnltP8MI74BQKdvWIlK0jD'
1816 b'0AbkeLSw52jSGrZql14HKxdAF0mEj7MKpUMN+2MdoIxAa+YXufWUzlhRdH5aSPYIs+4yoh'
1817 b'XFT/th0uyJfMQzS1sdY3HFMbi2KwGpD/L9verRzkWeZSKl1+NqldGNECqcNUh+/z1Seucp'
1818 b'FIyuqVAE59Wjkv/m6sykUu/V02qZwTbwBNcnwWgL5u3DqCzNVmeHUgI+N+1MHn4YBc1JcO'
1819 b'GNCf/AehX4nJkbBdt7frlFArOvNkTKgrc4dIRrQekDLOHCIJp59d/8JGl9Go3FMyscky1o'
1820 b'KgA+SekLdoKo/IWzTIAP0WTY6+db8xygiXK+23njmhgkZ6Bf2/cAA4je/gaMg5v506kwVw'
1821 b'F1myQzY9YmA21x18vLn71vFmxG5dNEfH5g2chh86CkY5ehSH0PhOeRTOwSbHPGHZhRdy0M'
1822 b'qGUMKIyN5OmzFp/HzYDSe7WDa3QHgzBoN+DInboo0ZXiFGBvjKMJ/g21+0hVl+F99qhUmC'
1823 b'NbZEP+U+o2bnMNGpSkerBrMg1H/FvP3AdGclivWo8w5+dC5PIZFOXB1I7Qox671IjuK3n/'
1824 b'xBBnLpLatzfjh9oi5JDEffQUIrtfTVoG0cegF2w/DCq9nmBKkbnpWk7D2vDHArh+mWP8ai'
1825 b'1VgGfTZG+xseX6BcSttCZtoZVsUPNRzVpKXU4Ms8VbRCXsqtL0v3LUM8cuaM2M/rxwH9jE'
1826 b'wMOXYoPFpvCbwb0LVLP/9bIu6LVG/WAHkVqbtlB1sp2BeExrTeBPzPB7PSxwVT+637hoXD'
1827 b'7JpqLiTNuyfcSgu03KnvwWhS4UE5P0MAUzXaDpgeEbMvO3dlf6reeFoZyla8mXGjH3yaEb'
1828 b'AqdNrMk0dqqmXyKKsNLb7VUGBoBHDYdj1XhyYz0OetWoVrLRCtwjksWmtrkke9PlMnj0F1'
1829 b'LJLH6MWpVfKobF7R2B4jbQjN6XFsBLvMiI1XyJc50dEKOTTVR730gNgxdlASHvt+fMRMZc'
1830 b'Lfnh8I4HHHD3gyAITpHyPVBtqIg0SzyQSRQQ8y0xq080MBnex2GMeHP63JoCVpw2jNF036'
1831 b'nteP9iCwp8Ia+hgLy+iBE5ZVAxYWkud2sThmKC8xWxZ753ZFN8JHvhx33+3tyWRPBWcOO1'
1832 b'wO9nSyp4ILh7109giyI4LxuIP4ikxvzyEHOrgiejydzRVMqB7diToTpvmPPeS2Vlck4kfL'
1833 b'GLRRy/PCfAUd09JKV24MEOrCVNE3NOW6NXyvKFvfVkeF7pMWSwNo7bdxSFB+LRLrvoXDgu'
1834 b'prkVs6rhVRq7jWbTTUWkgruBYRta62pKi3C0977da6Fx3PxqqHauvAq7agTDtDu+DBMvMm'
1835 b'Eb4jlQxtKBwhxFThcXgUexl2GsOjX/eBqvAIXXAv7CnZR3alvM474XPYLN+p+Qr5aGlVvn'
1836 b'MDhPLNFX2rfJeG78vX+tbF6ZFQnBaJi3PqsFCcFrlVnFYiXZzWbVScFrq1BFoZji5o61YK'
1837 b'2joIBd142he0dS8FbeXRBW0dxH3mUjDpNNMASa9ZWMzVERfQdtSaIZEomAjkuH7g3jFP9k'
1838 b'xJHR449ucJTxFiKvukTeRI+gOFBb69tRzxcLZ5viIZL9NjaH3iod5owGlmU6LxgNPMGLI2'
1839 b'vasMHSzvSGs1bgFaq3Ck7UuHTW4/dwjJKRCYMDlQ3cHfTgDF7x82iZ5DTJYg/VITkifqA2'
1840 b'RRzyEi5DBMl5YIzyEijNFziHDvnkNMzVfggI72CuBSL2EUGWiV5ob0sOcOV3QIq2A4x45v'
1841 b'ZjDkoAAuHC7IKnfI/vLHRu3CzpbEUVl5kpCXpq5II8A33nkeB9oGVggXRQzt162BY0r3FB'
1842 b'ld1qT1M49VZhBXsQxb1wUHhMpgAH1/wNwCoxsEWote3SGwsvhY50F9+N5bkwVZ10+KMWE3'
1843 b'3ppE/m/D5tTcUFphJGInfiXjVE8UIkC9uQAt8UlvLsxJa12a1brfdzt7A4v5DNpPBATVx8'
1844 b'FBiwAQbzsg0N1wxvRBXq6QK0NbzzqdOfHK2JgDoF6/gDKnGO6s7ERjaqLG/L1mOE/pLZ5u'
1845 b'x5EIXtRsnl7DKso5Uh3e+ITbaBRFC9d7IOhVn/QeSANautOM38G0EI3syOsl7eJPlfjlSx'
1846 b'Y1P/WyfpnojWLnwN+c6UhfjXJLhpszWwtEcjs/6jZNIh2NLjmUt57wXQWUIo0MR25vAF82'
1847 b'Ho+GSPE/HGUJgcms8sBwIVSVQF9VfILKAgUkkEO0mIc+hUdSwdEbFgWScuEEYD/4syDzJk'
1848 b'De5qux2Kk/PLlz5pN8FiC3OUo7zye9/dEw9ON6HzaY2Mu8hf3xWcL5O6b129uPrs7IiA0q'
1849 b'UHV1v9fQyU177jwJJ0bpSN91a+lwoy5pddhxSXJkBpIRG/d689ygYf9nRXrUB86nAPuz2m'
1850 b'WbJ9vIgmmlaL1MUtPhDrqkXs2ncLymRKRNLRBbqWTpnTFLCSw9K7bcheXGE2vLahXr2mNj'
1851 b'udFFKKlgz+vTcRQeqlnEvQ7Spep0eb6MWAVznja9ZqJ65MoKM/Tqyd0pM+v4MgzmEoP79f'
1852 b'HenJtvFh62p448vqBIoSbSs7L+ajJFm5udIiTLr5DHMRJs3zR6cJcd3OJRGLTi20zUie6K'
1853 b'I3NqU9sFSO+voKy+gvLpFRQiiOCx0BHzSuqIG4vtWN7eq0kVbS7MipBsOkbyyRgJYWt0LL'
1854 b'DmXcmrmbG44LhHnKtEb4NN0K7iN53RItSbzuhOgvZaWSK86VwkW/2mM/jRm865oSVkuO7s'
1855 b'bW+8UOXMfaTCfkZ2/AoTGw6I3wXNZSpUUFuIbW90sHoVrCIpeo3xYbtG7W3VzCvNOb8O0v'
1856 b'9h7rkdL5tZ7Dv3LTXzIuaOj4I3cyOG741HgtSaJxE2Bg2H6Iwr11OPApgplvhHNwI5OhRc'
1857 b'6DUqBqpP4tWKjjryJRmXc3Rve14CPIjWyvw7XtQwwVHJ2rGSpSxFQXpPpf3Ur6Ch+Prucn'
1858 b'2uqHH46PCMg8cncpYWDidyWguMTuTQmc5V9EvRCXVNRxnCaK2hK/Q+85lOFZGlmtgoIrRO'
1859 b'B4zbuoOvmrnD4xYOMLrmH/kZ6X4oUH2mpcKgAR32xS0MsNlHJ5RJ6+RrOko+ctPZ7VIX4W'
1860 b'c6U0RWKiLPFBFEd8A4+Q6+Sr7D4+QTPAzP24s3VMoomNvQ9zrzzEAPmnjhQgAUsG+xnWdq'
1861 b'mHL4SLMysoJd/ZS0fop+ZuhvA482ObPLgpA7lclqOpxPL7x5ydxdwYIxN1fw0NRW5g3oPH'
1862 b'VbQHHJPSjsIqNjtKT7Xl1klcN3dLC2UHRUfOgMoseFsuUyQlxmQeivXE9EOG8vW+508mpC'
1863 b'+62tuzw/2ojxDkWpzz2gdspKh/EdrYzHXXrq07OkFxOgJb+VlrRK1KWEdZVoe42MpFucga'
1864 b'C9vB+FcMOAVid9bHDTJvpdlKJMem3lAmH86qExRnIB5Vm9CpzH/tgFRpOoBUea3GJW0PmF'
1865 b'x3yluWQLZx5xkCsqUIwpmsnNY5oSlhFqjorlPC8zRs2sZ7WC6hlxuO1/vuzMoRERo4rdHL'
1866 b'm3EuTINdfkiCypRikzzxmjwp9CypcR/8+Hbse5ogQ9i/iP3GHFbNL7xqxVczHgHh54c4j4'
1867 b'Lm/yJfIR+yhiZVFxbddfg8BZxIH+HbIhysieBxj9syMsgKiwduiOjkHO+oon8cUsFFmILy'
1868 b'oU9kvCiRLGYf+B9uHCnsXsc8gSdJaaNYQqkEU18bDehyyJ0u0WnHOaSWiYx+9CgqNoMPI+'
1869 b'SI2Z5jHrBVolaoRENovZJ24hBFHicJXpFVId5eSpe+A5JhFoFjN3jyJPlIzT8NB35zeJLx'
1870 b'LW9nN8kjNGu6jSRfXgdB4enoWVxqzLJkQUVcjTJbTMOC72o191+1po9itXVKRAY9YwbIQT'
1871 b'Nbpv3XFgolRtM1Um9G0q01ljAkNVGVaYkNuqxiAtAVeJMbKGoJSwFDUwjKzWFIQSKovDVS'
1872 b'C9bVOmMG2KyjJRlpLI7KsnmKCiRvfZshw7jo9jpdTjI6XUwWOltLJwUEodMFJKgYp9I7JC'
1873 b'2zeSpcwlQeqVYeR0ZNSJeq4HS7QJPdCxt5Hs5LeOyNIhJtJXhpkowSuzOmRnP35Wj+345r'
1874 b'27E417E5II1DYkYPxOC2y0Q73+PU1uqujQ5ftgzAI/5ua5bIkc3V3ewgEL0GIgx6Hg+l3E'
1875 b'PDH3dQ7Hm3d1FoY9euIKVS/Sw5EBB/RB3vwPXfbB7IHxfH+KJnXQL7WVkEIdDQrU/cBDBD'
1876 b'zFkQbsHNP2CppCaC7Jw8EkAIo+ome0e35ZRhHPfbgVlUF89Rez8BYWkGLAvqTrr7zPqQu3'
1877 b'OfX6ofgCIonhHJviYE2iZuZLve+4mEeIt45i9wDYbNhR+7X+xHYKAYrSjApw1JWVJX9l4p'
1878 b'U7TNecMRaZeCHBp9N2rfd8IalsJRi+0mTRNXklQEU7U7A+UkDYvRPJjI8svtgjRzccwsFF'
1879 b'q8CoL7eeS1slV20p15heQAb+bdufT5H5RuFBOaymmFXyO1XzefJ7dHdKClrt4i1A+i07fu'
1880 b'sdO0uHDTvQ2tZ6kvzu9fUVv0Vfn1lCFqDQGf+OJno6df5MA3L5d3cMQ8qnWCXxBlYNutuH'
1881 b'tdmFoUdXArYGvLoTcGXg8bo4pFQLTTNGsB2dSWuS36NdziVpn0GG0DnkgJBFBOKrWxAgWk'
1882 b'3Oo/6/Rz0MCkYaBDJIzyKzhNeEolfByLA+bZ/7yPIyJRwkLEC6ATQnS3fjc9A3nyFsDMOm'
1883 b'igE82mcXnpUtABpgZIbVJDcssAw4MlBjpMogyzi5slcz6HjvdkEwvttwCUjneGHokOGkda'
1884 b'/BcMfmwVNguhdpFB0NQCUYLy+m15vbz/i+RlRzoG/dcDnsoQfsZbSqUmG8cNXqJaxj1dPA'
1885 b'Iif4qYVxOq2hU8TcGbjH4dirDp55cdr2mzUm/EMop4mGUcF69kz2CunYzag3XTHvwjVZlF'
1886 b'PvoxST5GrrxBTH9Q76KmGwLAYMtztjjnR8jnKWYX33kiI0o2e92N0mz9EFXjPSzmqD32K1'
1887 b'gYnvc+h2UGSxkQbZSnGEGvIcm1dOCai9SZRiZJqh6Sg5kCK+8BM5cGWQvEJ1Ys057NaHDR'
1888 b'OaQoF7jnqXkrQeKQoCvmEarq78Dgi13wBqH7E19Ggj0Tq62kmsDDzuIimhthmlq2AFMTOU'
1889 b'toIggor7fL38WwtnpGsLY6xtzz0j6NuNh0YaN50Oz1u5uhHTWQMMcqtUYYHL2p8pmeQWeQ'
1890 b'2epkT2Fzl1wtjsNVMzpgv647O+uYoZqcw8UDsiZR61OFJzNR3VHuRpfxzGG9WFQfddd9YH'
1891 b'JFnEgAMNmXt0Gs/j/C5bzxhllcfH7icOl8zm6GGQUQDe4akfTsExcjMertF565VtDPrP6m'
1892 b'QrCn18xxNSFg2IyP3rO55QrpENR05aPa8A4ZBkKdHUkKEF54qOygAVaECXE/IV2TSgw1cp'
1893 b'qhkYk3s685KA48Y9U466vSJnOPhDxxwqZSwv+R0SgIhOehLHruIc5CflF4yhzDzrBeMpmH'
1894 b'p5eK7pKDXI3a8SZgPqNVBtwmMm5SLZaSuGDKSzB4SWsBPDBeJa77R0mCeRfjat4m09eJPT'
1895 b'IuHhgKvnT1YLj3/vnZNVfe1ivPfWrqrI0Y1XT1bzaxfXwcy8o2tW41nfe/kEffmVi+tgbD'
1896 b'7IYDkleb8x+kTjvsUwZmYQljsfuDKfQdeKgKBtOTjoVh7wV7Is7L0rAZQbchzrztyMM+ar'
1897 b'AG+6GvPJGil9LbHrYWaxMEVzpf6tiN7Q3BcLE/jzrZBMhhlptuOsX65YL8f6fjuxYHdDsG'
1898 b'Vde+ZVRAvPuTW1WK7uEPL0zkwnnLtb46tyx5iOT2I7X7RIvd3mnyF3UFuN1RRi1UoQSK/0'
1899 b'5MhcpfSQI0pPY4n4lHG+BBqrQvBk7VWhCu60vaqjxWsVSLGsy1Eo3aO9clpf9jY38PiYO5'
1900 b'JL67EJDwXxS8zGpoEcjt6gLcuWc4NHNmrW59hALXNo8AuV3UDaOs1CsovFWM3xIYyQvDTR'
1901 b'XaCAGKK9QzpAtqH3tS877+Ij4CwermWxfsbjHgC+Xo+RaBe60ZyE7kcJ6NER5aacI7rd1w'
1902 b'FKb/+gTPLTgHo7ewXdWFFo8xts7xU8axbr1jEyzC+jU4dTJDGMrEukZ3jYcqvJ7dSCPTxR'
1903 b'gbcXimWVpw+DMeNbKFpsNDPeqetwc/VYhuox7MJlnxk6zYF7rJMUw6q/QMfsRZmrdVbttE'
1904 b'3ie3UyT/OIEeKAE5Tc8A35YM65oD7JaAwh3QML6RT+/NXlPFm706tBiOMsl3Qgl/1TTBlq'
1905 b'01XJsPLEBTMJyK1yyZLvFgtYf4ZMzxMeuENF3Os7WtrEL3hSB7Df+p7n1GFuF3jqyGBlun'
1906 b'RIdPVuTtAtHDBUfwkMY9N3wFg6XAFDmkq9Ots4nwoW3yNlcLUFTr/cskOn8UrjPNN/MKdX'
1907 b'Nab2Me8oB8LBnGqm1zsaDYZb550Xpq/vnuNYUHQe1eHXjYV9yLUlx2HWc+LQfrh+oPGpwv'
1908 b'1rGyyV/rzuMQnRTmcB9rFVBsJQG4u6CnAka+tw733m6Ctpl4aBrirO6CzAUR6nDvfhzh19'
1909 b'lbMTMt7W+0HyqwSiDRlaRUeGDEyTPYFIKQ6nN22jwXz4Q60dNQzmePKu0fO7WU+oYAwvrB'
1910 b'SgyPUYivDC3VhLlFEYN1ENRtMRVD9tFjdNDe07bKj4e70aCZ13f7UaiXZ+Q6FoW+t3rJ1M'
1911 b'HXqtgSzTwBo/SsKqOZojovfb63WMmt77b7HlGLJSr220qaJ1CbF22NOM9LEPOqkig0ZqwK'
1912 b'AektSjZsU0cikoFFjhkOfuEWNLwMsIj3sRz4tRhOSs0iokRs/MkQQz0qlrgaKdgsLwzajV'
1913 b'oI5wKe9q+SJz+GjxwsHjyfQ0iRcEWXsIvKCK62lzNfF4NMV23uMlQOgrBo0CwPRxHxnAkd'
1914 b'YtT9NRuTLmg7mB2iQCn9pcynF9A6FxhgHcTUWVpdwV1hg8SdLoE17xfezvI0tDdh0AA40u'
1915 b'iqP8rnuS2S6zQi0QIL5xi0QskX6Can61QDBDevUCQZ2RVgsEKAi9IsAmenNFgMPFEORZQp'
1916 b'5hL7oPQ6FGE4SrIkRJjfYp2of5DiwMMiEEqIR7rYEgIcF0DMSFtRM19ZL6D9XRIRWXh23Q'
1917 b'g6HLEXDHNkpk/+UxuEZnd/Fr2I0hAg+ZqtccapSKXnNoNR3lF7LkosqPArob0CcT1peLOs'
1918 b'FK6Q7KQp1FSyBu0ARPToE09sRzDZiLBkqTUGCP6BXttd18IM1A3Pt78RgzUOU180utkKBw'
1919 b'L2qJBFnydd89hfzFFHevnCM1rzEfwSv/y4SqGdrrQWttNUlM2cwBooNfbZlO8e1VLTrRqp'
1920 b'alg6pFWp/2mCeH6ByHpqNhtgBDnr9krDMAodDTRN/kMmlA2lYGBXOSHPzEE2PNIUw8MciH'
1921 b'c63LpSXiiSc0skM88aSnaFgtDC0ekDPRbYkINroeUdNRCiFa9wr1/w+rTtuH0A+q0kOU6A'
1922 b'TsjLRfWjeEXlp3QFhaJ4Aey+toLEK9TZwn5hYae4SJo8VhPJus4ITGIlcLtSuHj8YAB8fv'
1923 b'EuSFR+MwUgvHJtN5adEATC0wHoXK2uORBC7Q2GllwXP/3F3OAWZUutyQ29EFipqOyo0ezX'
1924 b'qJ1p+Z/Q71GiUKntO/Cc998SucGbe0ml2tDBCOXNeKvnWJV2b4fgJmfeuj6x4JR9ctEh9d'
1925 b'nzksHF23yK2j61YifXTduo3WPCykD6hbRA6oLywpZ8YnnvYH1K17OaBuY9UH1K2D+L6yTD'
1926 b'A5oF4GSCKbW8ztlCAgsxoCkeLVEDjTW2B5IKPBA6ULXcDMPqgXcCkMvadeIWGPFY3+4KsR'
1927 b'BfFEnW1O2nerhtD9qgNCx0oguEdU0WWZiCq6LFPTUWWmxwOGr/UzzcRVD8prWP0NDTlJ34'
1928 b'+wlIdB7aiWydUDg21rwaftBUKK02au0NEZ/ZVh3TqGUt2ZsyRkX/MMfGsZdpkF1tUMpDG8'
1929 b'8XSmduiNwIrAugqsNbzrRxahmGDU57MA6/5ApWbCRJzVlWwzRfPVJY/4dUAWw1mpSCtFHw'
1930 b'ZZL8TkIcL90VcTWL8xj/nZAJknZ69itZ7QQZkoeX3wbtcZU7DSAEdeO2kujK2Ni9Pl3t6p'
1931 b'Vk8tidERKiSB1AJs1NYF8+5VT6kQpOiXkFEpOfCrGzvS619vXYF1ofKHTI2uD0WeRteHaj'
1932 b'qq6RUZZ72DtLCIX8J0pF7zFChsHxHa37PHejKHE3JFR4cRNEMeIlkl9mIPax3lFFrMMRVq'
1933 b'3k0UVmFZAxf8kG/mDh5otPiQee1UkcHsxIDhch2QSh1EqEr5Q2t403pGS9rrGYbQeoYDgp'
1934 b'7RJgN1x1Uy+BMU6DSHsOucLZPhfn082jlT4Qlt7jjz4C3j2QbMIByC1iZcZLrjF1NIEF3D'
1935 b'mqYe0PILeGUFOrviaFNQw3WHOzJ8ix7ZWkIOd6ymGvALlMtUo0qBXM40w9+JuMw1qk1s0R'
1936 b'cN1/emYr6iTSFzCMXr4p3KXqSGlAMmKBGfR4hHGTWvykDqMkDo2oAZ/k2w8Kyun5wn3vqS'
1937 b'B/ftt5uc18ng7YtXyDxdHggjMmlB8vQOMgKNDIxXpI8shXlqPyWHG0srQdvcQpKrS0tH+e'
1938 b'lC9DnZMtjoqJLJPl7EjFF4uLI+hne9wz1Pbm/XI1khp5CdegkQgos9MNTGIb4wk7kcX5hJ'
1939 b'efbeomWCb8zsaNY6s58pH+Yt7bfet08tZOxb5SrIqrLocUAfoq0vG4ufoebqmlUtHe7MYq'
1940 b'FaDHtVnkvK09vEcJbpCHG+AKKVIriwSnKaRO+IG1KpyBXpoCFPAnnrbqc52V4/Nl5RKzpo'
1941 b'bOgbzIMqU2L2Ni9e5tWQfOx5YzbvW1+Q1Ap1ZYGgTxsgVqdTC+14UR+GqSFWrQ33lmZtUq'
1942 b'IVa+My0qsNcutGKJMKrW8bl6JuG3a4Dqp2pFe2jWN36pEym1SL7m3kCjadk2ZGwKvPqSX6'
1943 b'Iy+jZA0Vw2v215aQOt0uCakhg+6vTPvpz91tCsFFQ0BRAhWrcGiWNO2iAXmeoVEdN49GXz'
1944 b'OViI6Pm/369HDZWaQhct5SIKPgpKhv+n7PNHP01WgAj/5h81XtvuUCKoYyNveeOUz3BmMs'
1945 b'WsRFgq0xRRRsWFBboQj0mQboQ4PoQ4X79r0E+w0DqIPybFyRWTdKzT3mwXXPVqh4t3KexE'
1946 b'9+TAoBwn7lLGD3u9f11zeCCwE90hjk9DAcO7v3N9w6lNEo2Oe/xvQ43CQvfLZskrys1/uX'
1947 b'oDzWBuFZrmATlcGxnmPNQfpetcC3nz4Rf+rMzZ9ZigGBlLnyAoP7SzQPMy7VNIy0XsxOQf'
1948 b'dva0wH/CZUxuD0+jaduLPAxkh/9DTNlOzhYRvZQS+YuNFCPMNFxOxOWNHLRKvtTN2xO7gL'
1949 b'ajD+Chkf3V/mbWCZ94XRWAWwbxgvAqD7KeUuUnxVXKL3zhSmFHwVhH0BuQmAvnjZpcbfrZ'
1950 b'PNFD1Oz0rx7IPJtULsWZVKITpJrcKjNOkIJVFzDapU6VDse8ulQnS6DM6Z5qZ/NPO/DMCp'
1951 b'Cyf2Tbmfolt1KUpYkCfl7l+p7GeaamKjiGytiLBF6YDxqXgHX52Kd3h8Kp7gN+UKutmLXp'
1952 b'9FQoPCjBLSC6rQhuzNoaj50Qk4uAuXcUynQoVJDrHuW9ilyVF/rN3b2GUORjAzZhHFhxzm'
1953 b'ib6wlOGOzlUYKceLE01RGzS0fxPO6FJB1v7ozgs6unnB25yRxMcHKOnRPVDMVm2JoHXMPR'
1954 b'TVV3EoRkTGHRUBBNO6b612zxxmhwKqhtxZtFg0aqUO1KfxvcNIBh+LtJfMA2rPqDbYCTUF'
1955 b'kphZrzNINY4x8G/6B75NisYxN4milcDJ2O9gYAJw4r3XGe/OflFL50ht9EZQQ9r39obQnb'
1956 b'oDQq9OwLw5XPLD6NNF4s5FXO2zzoUz2mkVxnjte5GMz1hg9HbQaEXbOPUn0qqa1OEsdhe5'
1957 b'iSI+4mEktTbgc/P5El4qxlzdABeZnKeMYDiteX++N8eASvpiUs9fyHSV4tzho/Q6OF7/r0'
1958 b'qPxnlQWHhkwV1lSbyFPHXAKFucbzMgjkKYKpaEosDRPkDlgjoz+8+hRDAvsvjIOROpGzxD'
1959 b'1m2b9KhAmAOvR93YEAj3odEUG/OljQ9XBgnb2IWh7c73hCc6DGk3tUtHqFZnA5Rmn1lSjU'
1960 b'6oMtoD5o8vymYONSy6ngX1cuAhzcNTD83sT6pI/rIkSqp5HLSFt4h5ZuQTZhszLy/CYXQ6'
1961 b'N0m/iAFfisTpJ6ehvAf60R6OZ+WVuQPch5VLphyasbnkz8wfUgqiHrKbWSpY/vFS6ZfjsL'
1962 b'k8mOXaFYnfeXz1q7lFxTC5+N9t/G7BgtBLtzOWgjQkNeQxLJdmgoQF0txgmIPYY7F5pWg7'
1963 b'aUE2nEyLrPmhpwQpgV3/nWcOUT/U6ipyJrrNBfFEd7eAVmuEqMhqjXCe/EGtO03+kKM0Nb'
1964 b'/3ygCGgDp9l5EcGVmXxK4MjSui46N0DM1f1ea/00lErSPqQVNZFVEzTeW5pjidClRQaTwy'
1965 b'1os8/gfPlX0H/l/9XGlUETfWq4T1PT/Xzo+Hjtc6KI1xlfyhl0xRhqKLtZPkD2eCNMdn1D'
1966 b'HA3cBTlRjd8REUMUUGNcWA0X2AbWVfe43woGKNuP5+O4unMT7yZbkBM6S7Gsu6mAo08moZ'
1967 b'7rCBhWYCjdwaRpyaSqCRW8OQ+mqxOmAj15bj33y1WBOwkWvDifOnFGjk1jLc9f8Wmgg0cm'
1968 b'sY/p1XCxUCjdyCIZ3qInG10Ru5IKN8Wiis+U5rTWWFpvJUU6H2emTcejx+1Qg8I24ERHmR'
1969 b'j7E2xiTCU9IzpRoL74G0gronQJpVhPjnPRQs2zTBb7RwF1x6z0YeZwuE4T8T6n59Mq+wto'
1970 b'K4W2PThSDRQB+8mlGLw2EbQzKQ5XxJ3bP8zbMe8tHUgVQjYNpY+BbkA5op+mBNdQxgLrr1'
1971 b'6ZorjEtBWaWBKGVVwvVGqILH6Nz/ArTavZuA9NsbRSKbPjnxjdvwRKyOsCsZxt3IDK4dYc'
1972 b'oQbkVWIJcJp2asYqtETdIcrfcNJ0l8NwdpbaI2A61N1DQdWRkgK9ZmQxBjo1nCVIu/KXjO'
1973 b'SvSayRj3J7tTQuNOcx8ElYsy0W8spSD9rhamqcdgK4X5bnhLoUVcsVUU2WpHCYPKMZrTzw'
1974 b'zt92GKJpByJqdAfnaYQ/L5J6PQQd9qCKGwgsJUChIUJsTdPfGBHTtPZRE6mpsALOg6IGZL'
1975 b'YFVi0n1UKwB5asmgk08IjA4eM2BdbgvSb52x49UH5fL0btWucvxTt3fm3NwxMlVeKDoqXw'
1976 b'plTrcZiU/b8bBq0Xhcre3IGTNCfz1my8hR27EzZoz8OXYALe0H19qOoYKNfDuOH15rO4oK'
1977 b'NnJtOXGyqoCNXFtOGGJrO5AGcOTesWSQre1QGsCRe8uKM6sM2Mi14/iBtrbjqWAj15YjQ2'
1978 b'1tR1TBRq7JsZ2tXezPeIsdoF6pdJUFaBS7VuVlcXWoyRxeOvIFHW9o3gZSXUNfoQfTCyaY'
1979 b'eB3DoXkSA6cfKT9sOEv7GYyhGw3ou0AKMkbXUJiAzv0Dfbi5LATDfHt3tdiQOny02ODg8b'
1980 b'JCbuHRTawTi46Pi881HBsNzhxL3DogNpJnf0X0yjxx4fFo1cIJN178gU5g8WjlI18oNA7d'
1981 b'xRofZ19acLyOkbt8HZs/urQj5cd+ZIVZMiiurJuh2uyZ2bXs0THJmYOPvXfJgVCvjtSMRX'
1982 b'eEmo46QjTXnlZ0PEvJL23ZXxjE7UVZNv06y1UTZ0C0RjeLOFr0RcQJa57ZMheO223ImjaG'
1983 b'9Lm1WczSAWVkxbYCKQM/RydfMMs6aqPBAqlx5wzYqBZChYaGHIjmaYgoOj+A0ovOC2g6yn'
1984 b'NUI4giJwQgnOj48KOVreWCtNewUhL6Cg1y9bVEqaFH9xIxyOsTopOA+u16BekteAXf2kKc'
1985 b'3mD7rcRbPL2lCL7edoX4Z3/KdoZoQ9bPPKH7N/iOzh8gW6PzB5qO8h+hIRij+yjNLbNonL'
1986 b'xVTrTnq90l+2Y53InIrw93NskoTycB0TfuBfRWjubJdzP0BkvnZ55wqbLCj1bY6+QkCnvj'
1987 b'vrXOWBYAN0GnMqSrcvS7iZWzZk5svJbUMOTNaC2pWQDU+nlt6KCfk9Z3dDBqfQmHpiOrHs'
1988 b'YGfRn/b4cLYnzbdq9rA+3DyX4Kuu+ejZaTuu+wnBIjQfXzeNAOiGBK5Btsnlna22RMHb/f'
1989 b'8/+dXCmC6h/wS3hmLbfw3gfnaE9ODCmBW7Lv9enM0mHeS2Fp7cRB3oUVRc592hRcuk57qT'
1990 b'3oPVUO0I485t1YUWRfxIUh9Cw56VkPSD/rKVP3HVVFBK+mQitQ29c1LVNm9lNf3OmgG2Zz'
1991 b'y8ay/PO6qAhhSpVZQu6Yg5Z1iuZYGcWMpEoN7YcK6DpCRs7grUP13u30SIUm0D0Mdt8sd9'
1992 b'+jx9nmib+bccL9tFPXqaetckOPmmBmwKs2aN2OGyHK3j9iUdrPNNfEoyKyB0WEebYDxgtE'
1993 b'Dr5aH3K43j3PkhuPVtBdtBu8JKD6A5RjdK2WpqP+oAVj3z8MO7v41AQyrD4pMFosUrhsmU'
1994 b'4N9nXoURs5TjgBZosbeDS2oMp2+m7NLEtGpjEspK/mgnU2MH6GTWUHqHF6aZFggFdq4NYZ'
1995 b'lYl14Ed1F4B6QLO1iB7jlx4KhnYOik3tKg8G+zoH3bKwc6JqQw/nOsp/h2lzOgeJQd3c0W'
1996 b'JS1wrgjeqcFzGjc5HrHTjnJD7EMgmgnGKZKkyOsdQOdIZ4COzxLHflQ3E7baNVs4qAGoVL'
1997 b'0vrCtpoAbwSSa/NSh+jnkVaLMoLDnXqrBUvScPSzSPAw0bC+hK9wTyJZtr60D74yDUfRrB'
1998 b'K538I64ikMo6TlltzZFUlef2Fo9kCXvXJvlQmTBVodcEDQBwyww1R+px4RMbHoUQRj2/Yh'
1999 b'zkx0vduo25xaYNRvlha96jgri497ThaRvtKOgvDYoD0yaL+dmB4x6xLNxH5CVE1pIss00S'
2000 b'kidI8OGPe6Dr7qdR0ed7EEo6xiH7rlzceSKlbd3pxvmJmvoCJpOihIGjVfwxlwtriGxU/M'
2001 b'FC/LKzT4cLwh1INFaqCgl1lBlAhzDYSgHCzOGkUHV0StvlCj1vZP5jFRqtT8pCnKwsGmTi'
2002 b'l6dzmsz91ooYU8PZKhhukJeaPpaCRDTvW7i3o7ZmmB6MCzAfe9tc+hijHKKcY+nK6WdKYW'
2003 b'Hq3oWHRkPdI6MF7lKZNblh/zJDb6KAwdHyilxt6zz48WZmx4o/tLl8ktcxEmkqc82Ef0f4'
2004 b'YhyZBqwDTuwnBZBPKWvfqKbD9UGq96WHRAGBQNEA+JpYXCgGiAW8OhEUUPhsZlNBQaRA+E'
2005 b'BpBhcGYoGQSXjvRDoHEsA6CJTg9/hh0/MbwS6HLkfsDbBuPwHvU7NnefeWcyQuaCyPhYGc'
2006 b'iNjojL2XBnK/sZ7TQRs4c3K/epFekZ6oq+bhz1K1p4QeTcDT6pVrIwWDwec0d19O4eyi+6'
2007 b'E5KudKvUdNQqIeWw6zcXI6uxtV6/OQW/9ixjzh7zkCdcdBKTZGQk2l+4GIt+T35WNmlIhX'
2008 b'UhJNudC80m9lPXPAduzE6w+4yeWVOYPLM2TU6y1IQWbnRSPVlpHPbwwAswpp7a89zs0lF+'
2009 b'08vcyw394mHL1w4x2M9nzkV4HslzfEjPTzQSXHnKhNsK9bB+6eGJUXtwd6BxVOqpgf6XmS'
2010 b'P3JjTvFDWGzMKTJvCFp5zs3E70oYXzCddJKZ2bcIHRYLYDzWqjd1RpR3ZJ1rqiB++odo68'
2011 b'+bHHvZymbF5RQ8zcw5Ueb7Q4HYN1GMolWtKpSHu1yhBarTIAn6TQPTqHbaLxkjPXCYjGj1'
2012 b'XUE4uO1+0zC8c9e+mCGNkP5haNR4bSgqO+nU1IrwMiGnsqgs+RMyccFd1BhlI0ZziuG2Tp'
2013 b'ODfaI0RVFmH2Wx38recOCwdz2UmHQ7YcxS4PW6rVNEwjpbsTZHH0pqymo+5kmcSvhxYUht'
2014 b'q9tURLkbgLLyPh0B4ZrHlKC90IqsRGHQg2ZUsE8zZcXtfRvU6LhLbNUAr04dw5yYdneyQj'
2015 b'c5Q1VeB7UHJqNyNH2/JaOpjyklbbvhXJ0fvcGbGr17nz5BytCa5IjzTzBUPvmaYoRcvkHC'
2016 b'0frhQdnUmegHF+7bqdvuf8vOZBZxP0V6qXc34Y5ZRab6C2IzJoxgYM+ilIe1kn5s1nbZUP'
2017 b'hiyDFfjG6Mu3DdBXnMPqV4mMeNDPW6IqGiBe30eVNOjYQp7F+3D1OGTDPLLw1Wl7eDEXjy'
2018 b'bnsFiWWyK+q6VKgUZWCZRVnX+CLnCOVsYaQ8sCGmTQBw6mqAjdrccG5nSoLimfkxw941AS'
2019 b'u3Hp6zzzjPHFAZMFOVcPP1QGDQfcTcC3bjjAAOI5V0E3ZO35cO9ZvSs8U+hI/KlhxbV7Vl'
2020 b'vwRtRT4VxF3ZJ1fRtChaKJ7sUpFR01CjrcdS9bngvNeGZNSK9TmDh2PSft3WbQd7BNPOOP'
2021 b'jksHgcGkK4XTkLeUY8MQRXdpKFEtKUpY2aFTqpZ8KO1sXx1lhp3DhXOKDBfOGTBcOGfIk6'
2022 b'6GDZpi97UPM+pZY4Fo6kUwOuJQkPa9oiF0t+iA0C8aIPQ7+cTQI/uXBUEuNT1jpBndwViP'
2023 b'eNFFjJVm+tX+KLSrKxlRH3QvkzWGHlXTuQGv2ox1O66+jA99Qfdnfzqb+zdyCzzyMGLGd+'
2024 b'VA2ieCavtpTnqk9ntkxE/U7KxfzWZnwhlNaIUxnr42yXiX3uSNgUYzU+P0GM+WFoLJPGgS'
2025 b'IKmtTB60SqOvhLs2UybEHQ9Z8vPFnCYRdkaMVmOTVZtYb+r8SOUgASYWGMKBktoi6ogJS9'
2026 b'Ye2tF302eCnsx7cpzrhens4gY3TDENGyXDeXhuP4NXB6i5+MwiIQczDdyaj7vw/YzcBaAW'
2027 b'r50DPUufeSjM0x0Uz9RzD4a5uoNudUhOVD1fd66jGbvDbh0SLy1LT+eda+nnnJMwpZ8L4C'
2028 b'f1zotb7TNHUdoY4t2aJ7NB7RjSU7o06MPkLjg/Tyeprr9E1Y3u5kKdje7m0nQ0dhgGmtFV'
2029 b'I514xqiNenzcRLNkPDmoHDJqoHQoz7yFR7Wcoj+xkLNdyR01RORmuNzvnJPSeeARERajXV'
2030 b'azUDSDmFrQz+Yciozv9506PEShedIxDBulQ+LBxKAv0YtmlERd/eBOlFDm6FrxCsqtNmAp'
2031 b'QUerJJBUvwfNNhFdVYX+IrqqStNR2TIgxIPs//NMc9qnrbUca4uIIXdGs0FaXLktPRac1R'
2032 b'7a9xsHVQZ67M29Ms3SUGbZjxNVEnw8GB2o8WrutbDShd01hkAzRn+/8ATZwmlgj45m22GC'
2033 b'fUSf0Jkb5GiePf0uV7YCl991ok8Uz266sqZMOR+I/i5bImq/70bHhC4CqrWMGwjZHWv3o0'
2034 b'uTnGWRB6mn/ZA1803ZqXnSW+zOFeRNdhGC3Efo18SR5cd+/bRBsHziwRC7R16aPrXEkTtA'
2035 b'zdwSPMRPa1jagPLZWr4013NO5D7DRCoCwlTKwWEyRSCaNBjAGHZSceNnmmlCc7J7RYRVdA'
2036 b'eMN1gcfLXB4vB4g4XgNrrIDrmnVzPQcvUEe7Yi7W/BMIS+lccB4coOAvoE9czQ8RyQ88vr'
2037 b'KU3DJn41u2jYEcQa7MQAXoW1lNZhPRKUWCLeOKtG5NHNYKgP0c1gmo46FlSPy/g2D47Sl/'
2038 b'F1HosrMDoZjSx67XZflZ7ROEQGWu8kaGm5Q2SwNH4O57ewNZw7RDSGIp9OHSYaYOUBCZkB'
2039 b'8WauPONH0D8MqbSjmnSQOQ3kLc3IhOr1IuN1dLNO4bDvIboPmZCjdajaAkGDMkCsP2UWCt'
2040 b'qTAW7pTiYpWnMyLiO9ySC3tCYjtNaZjEspSMMO+tLMkV5bMo6lSI0c8m5OY7JQK0PGtVeF'
2041 b'HNEfN0bRnCa8RhnxXeR2tXlyMes5GaK9KLM/UuqylxqkuxqtXCYXubwMIYaFFUeEy8saDc'
2042 b'hKS5VEz4HmyWWzDt1HkYIOt41VlpSzIZDd2yFCRH3b2CKQ3jMmxIJJ9HnAJBlzhQXRVmmA'
2043 b'nQDpUkUjdxItS4DqpjAIKTeUQUptJmnI8C4xSH3tD8LR14lBd7i4C8qaif30V860M0uraC'
2044 b'muvqCsbSwdhbi0mFxQtgIdX1DGHNeQzhDk3ZUdMmTUtxSVye3lYXjVt1Ogz7+EO8yQqZKZ'
2045 b'6Ogu148YrzyoluQq43J08xOkj1RGlAVX4PytQcVK0eYS7QlTIJD2m2u3uqvJFe4vJ6Jb9x'
2046 b'TxnJ/s7cyy9QQlJxdaMRt8u2eRvsgLPCTQiqMtbzQonsg2158tCk/ox4ebMeh1SBO44fgL'
2047 b'HzAPc4jcn4bK8DI2xPeYO0kBEaL8ZQKsdT0v37+Mn8qGwnc1/E2L5Gr0m4+xaPBD3UAPtz'
2048 b'ZW8GrldBXgq1czG5S7f5KY/qP7rCoPSCeA6HVvh6yRboXfusVaOjRZ0le1LgN4y+45wr3F'
2049 b'cwRqW2cwbgWSJtdhaEwHkSZf2cWXyVfZSyvwrbfSLB0MlEjrW4or0NwsWJIRtgdyRZbFCA'
2050 b'hLkgYMS5KWNKe4oAE3QgWt2GDaz2pC5G0IL7uhZ/sahhkEqXo9qEHRS88YW78q3XI+JTlS'
2051 b'LRtiV5rlguhYsVwC1JkzA23ejeDuiu8TzAg6qRYCcBKrngabLCOOPo8yizjhjaI4LAfWAK'
2052 b'Pbb9vkq5/LIE16WWMFt2iC+uEkNHcL+TrkaV1/iJ3WR31XPObpDvNNRADdTgBGHS+qoJ6r'
2053 b'VxDImJjefGe8HTN1UjxTG602yf9isEoPOoB58lU6XVQlP/hVSGxQ+ZHjeiyeoeLogW01TV'
2054 b'5ZyFXy6rsVJPl1re4snYHUhzdWoPXhDU1H8i7IkGBqUOM+tG49qAMkeFZ2uAWF+2ou1uME'
2055 b'ncF+fbs9hCE169ewU8g4R89ImtBfw0uUYTV9GjNib3WZvKpnhpbJa2i5pSXETB3d8Ksaz2'
2056 b'uSaosN85BX1dKhO73q3axZChq+OSbwFuo0RSqixkoHIV+Rnk7dmwrJvKZUwyFNFvTFkAaQ'
2057 b'Rwox0CrAzWWAL2cOh07VHeOFmEn7HZ4qB2i/1278Cstk9T2mDmFqHaHb2huT/GJRRYi7NJ'
2058 b'zn4LjlZSqRclw7x8PrwV+kY5yEk3g8kn7lRrOXls2kfS+IRX7tRrNTz+b94ryja7SmVX6H'
2059 b'L4tRLs2G/m46Zjccab4LxPjzb+PxRl2H9jTYCAZcFhVnLgmnMw0Yy4mTWG0/lr48/7fFu/'
2060 b'r7TiStLhnQF7+X0GLsQjNRFHpBfDYBrVuNoaWZQOaoW0ce6SXXWQZa+9Z0pNQhQwbzMMmM'
2061 b'H5HdC1noSf1GUIY4pL9GeEbfTLmF/KrPysFV6L1RB98OZqK0Sjj3xHDzpxqB82Xypza3zp'
2062 b'JgT4lZ1p+6F4LTqBdqkj+jEx3QCf7kBUpNm0SWjui4xawRmfynkrXNEz4EBD30bb3ehA57'
2063 b'2ib6tnRouG8yM18mcnF6Rlz1ZFkSXaNuvOmlLNJ68JiC1uOGpqOByDAkmhTUfs3h1e+6Ut'
2064 b'yroSn3oI7iCozqwgJcrdqXcB7Ko7ZEGCaq5E3P9JG8qIAsLdPgInlTCuB0TtLcCB+GsGUW'
2065 b'wFg3ZF6Od4pXxvWtkbCMGaORcB5zxzvNqFgRf7TlDIXk7Xp7GlPwt6vdaegmb7eNKzD+vn'
2066 b'3HuALV9e2WccXMBGa3LIezXTcJGYc6oSoi029MU5nncZsmokZbQ16dDq8ZwHG9RRN4Q9sM'
2067 b'JhbzCI8fxjI8fXHZlBl5vLmCgwYHKDYETAUbH7VnVXasGGcFOPdhijKDDF55YIm4bYpmaj'
2068 b'/9agumUm+91oGRC1rwgvxgdIhY+sMb+mmMFWzD8eYYhYi6G6RtMA9mm48wT1NkmJYZMEzL'
2069 b'DBlNsTKH6PsyVk0KMaID4ag0QxC5Zji62deKjnqWkgypDSiwqzuvoe29XV163V6BUT+C/s'
2070 b'g8VmLPJ6AgBt1PGmFVh2ZieJNttIxJfgtv72KWJkvgLMmX4alDIe9ZAryXaR5D+oJRlCtt'
2071 b'4uZIpR+skDN6sIIoftrBShkGLiQhOvGNIC4qg9EJRAfAS0VHGVyQIVVpAup03z/pPrZxWD'
2072 b'+c+8c+ejQDQxp4u/4MPUTDVYBv+ZqRPS7GwoNa7CswKkbGrroVdowX3XuwJ9Xj5HJF2i8Y'
2073 b'r5JvHFvnyTd9WA36xjdZRCbPO2/wrS8cIK2MOmuSI6NOBnVt1FkZNBh1Gldjo04G16szXJ'
2074 b'mhR0e4JgC1jSdD+qN7xIRbHVhFCRs0visQvfW39fEPtSnPGN/M2adlaT9D1xABoXNwcOge'
2075 b'AGhtCSn1S+VVi28ZqWeWcCM1an0KwBp+8tO+sV4tzJcYVjraj9ezPPkWLeAgtpuWk2hS37'
2076 b'pbJ6NRAaITtgg/OmFL+mh2rybmK2z/WFrtX5UG8FtSltJ7Sh4Jm0oWiXeVbLB6s8gi0W6R'
2077 b'hfSukEXUzo8F9HkXi/jtHUuZZvT7wLfOqAusAngYDg7PJpNFwK0MwFD3ndEakhGdR0ShbD'
2078 b'vdnOYEzKK/vko+I6oLj+HcLr3KcG4U3zL5Fh0rQwWOjpWRPgzqPnBUQW0lwoYRDYwQNToR'
2079 b'A/fRiRjQ0s/D79gsABOib2GDDQmK7OEReGQPP0/+7a59v0z+H+SUGTTsMAEA'
2080 )).decode().splitlines()
2081
2082
2083 def get_tessdata() -> str:
2084 """Detect Tesseract-OCR and return its language support folder.
2085
2086 This function can be used to enable OCR via Tesseract even if the
2087 environment variable TESSDATA_PREFIX has not been set.
2088 If the value of TESSDATA_PREFIX is None, the function tries to locate
2089 Tesseract-OCR and fills the required variable.
2090
2091 Returns:
2092 Folder name of tessdata if Tesseract-OCR is available, otherwise False.
2093 """
2094 TESSDATA_PREFIX = os.getenv("TESSDATA_PREFIX")
2095 if TESSDATA_PREFIX != None:
2096 return TESSDATA_PREFIX
2097
2098 if sys.platform == "win32":
2099 tessdata = "C:\\Program Files\\Tesseract-OCR\\tessdata"
2100 else:
2101 tessdata = "/usr/share/tesseract-ocr/4.00/tessdata"
2102
2103 if os.path.exists(tessdata):
2104 return tessdata
2105
2106 """
2107 Try to locate the tesseract-ocr installation.
2108 """
2109 # Windows systems:
2110 if sys.platform == "win32":
2111 try:
2112 response = os.popen("where tesseract").read().strip()
2113 except:
2114 response = ""
2115 if not response:
2116 print("Tesseract-OCR is not installed")
2117 return False
2118 dirname = os.path.dirname(response) # path of tesseract.exe
2119 tessdata = os.path.join(dirname, "tessdata") # language support
2120 if os.path.exists(tessdata): # all ok?
2121 return tessdata
2122 else: # should not happen!
2123 print("unexpected: Tesseract-OCR has no 'tessdata' folder", file=sys.stderr)
2124 return False
2125
2126 # Unix-like systems:
2127 try:
2128 response = os.popen("whereis tesseract-ocr").read().strip().split()
2129 except:
2130 response = ""
2131 if len(response) != 2: # if not 2 tokens: no tesseract-ocr
2132 print("Tesseract-OCR is not installed")
2133 return False
2134
2135 # determine tessdata via iteration over subfolders
2136 tessdata = None
2137 for sub_response in response.iterdir():
2138 for sub_sub in sub_response.iterdir():
2139 if str(sub_sub).endswith("tessdata"):
2140 tessdata = sub_sub
2141 break
2142 if tessdata != None:
2143 return tessdata
2144 else:
2145 print(
2146 "unexpected: tesseract-ocr has no 'tessdata' folder",
2147 file=sys.stderr,
2148 )
2149 return False
2150 return False
2151
2152 %}